LINUX epoll实现原理介绍

本文深入介绍了Linux内核中的epoll机制,包括epoll_create、epoll_ctl和epoll_wait三个API接口的用法。文章详细阐述了epoll相对于select和poll的优势,特别是epoll内核层的实现原理,如eventpoll结构、epitem、ep_pqueue等数据结构以及它们之间的关联。epoll通过红黑树存储监听的文件描述符,当事件发生时,能高效地返回有事件的文件描述符,减少了遍历的开销。此外,文章还探讨了epoll的水平触发和边缘触发模式的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

好久没有更新了,今天更新一篇epoll的介绍文档,后续继续更新linux 设备驱动。

针对linux支持select、poll、epoll等IO多路复用机制,本篇文章主要介绍epoll的实现原理。本篇文章主要分

为如下几部分:

  1. Epoll api接口说明;
  2. epoll内核层实现原理说明(eventpoll)。

Epoll api接口说明

epoll向应用层提供三个api接口,分别为epoll_create、epoll_ctrl、epoll_wait,下面分别说明这三个接口的用处。

epoll_create

 int epoll_create(int size);

该接口用于创建一个epoll描述符,其中size表示该epoll描述符可监控文件描述符的最大数目。从 linux2.6.8版本以后,该值

已不再使用,但需要传递一个大于0的值。

既然size值已不起作用,那是不是epoll可监听任意多的文件描述符呢?

一个epoll可监听的文件描述符的最大值,由系统中max_user_watches决定的,可通过修改该值确定系统中每一个epoll可监

听的文件个数,路径如下所示。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

该接口实现向epoll中增加/修改/删除待监控的文件句柄。其中op表示类型主要有

EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL;epfd为epoll描述符;fd为待监听的文件描述符;event则表示监听文件描述符的哪些事件(可读、可写等),传递的数据指针等,具体定义如下两个结构体。

typedef union epoll_data {

               void        *ptr;

               int          fd;

               uint32_t     u32;

               uint64_t     u64;

           } epoll_data_t;

           struct epoll_event {

               uint32_t     events;      /* Epoll events */

               epoll_data_t data;        /* User data variable */

           };

 

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

该接口用于等待监听的事件触发,其中epfd为epoll描述符;events、maxevents分别表示所有已经准备好的事件描述符

相关联的 epoll_event以及单次返回的最大监听事件个数;timeout为未监听到事件的超时返回时间,单位为毫秒。

以上就是epoll提供的api接口,通过这三个接口即可实现事件监听操作。

epoll内核层实现原理说明(eventpoll)

select、poll、epoll均是i/o多路复用机制,相比于select、poll,epoll有哪些优势呢?

  1. select、poll由于其内核中并没有存储注册监听的描述符,因此当事件到达后,应用程序需要遍历所有文件描述符,确认哪些文件描述符的事件到达;而针对epoll,其内部存储所有监听的描述符(通过epoll_ctl系统调用,每个监听的文件描述符对应一个struct epitem变量),因此当监听的文件描述符事件到达后,epoll仅返回有事件到达的文件描述符信息。

下面我们通过数据结构及处理流程分析epoll的实现机制(本文并不详细介绍代码实现,仅介绍大致的处理流程)。

数据结构介绍

针对epoll,内核实现文件为eventpoll.c,涉及的数据结构包括struct eventpoll、struct epitem、struct ep_pqueue、、struct epoll_filefd、struct eppoll_entry,其中:

  1. struct eventpoll、struct epitem分别对应epoll fd、监听的文件描述符,其中epitem作为红黑树节点插入到struct eventpoll中;
  2. struct ep_pqueue、struct poll_table_struct、struct eppoll_entry则用于将epitem与struct file的poll接口结合,并借助等待队列,实现对文件描述符事件监听。当该文件描述符可读/可写时,在该文件描述符的中断处理函数中,则可以wakeup等待队列(struct eppoll_entry),并执行等待队列成员的回调函数ep_poll_callback,从而唤醒epoll fd的线程,并从eventpoll的rdlist中获取有事件到达的文件描述符,并发送到应用层中。

如下是这几个数据结构之间的关联,增加了进程描述符及进程描述符中文件资源管理等结构体的管理(进程是资源分配的

基本单位,因此epoll fd、同进程下各线程打开的文件描述符均由进程描述符task_struct管理)。

  1. Epoll fd的文件描述符的私有数据指针指向struct event_poll对象;
  2. 注册到epoll fd的文件描述符,通过struct epoll_filefd,完成与epitem的关联;而epitem则作为一个节点插入event poll管理的红黑树中;
  3. 借助file->f_ops->poll--->poll_wait、epitem对应的eppoll_entry、ep_ptable_queue_proc,将epitem对应的等待队列成员(eppoll_entry->wait)添加到文件描述符的等待队列头中(即eppoll_entry->whead);则当文件描述符事件到达时,则文件描述相关的设备可通过wake_up接口,唤醒epitem,从而调用epitem对应等待队列的回调函数ep_poll_callback中;而ep_poll_callback则将该epitem添加到eventpoll的rdlist中,然后唤醒eventpoll的等待队列;即将epoll_wait唤醒,遍历eventpoll的rdlist,获取所有事件到达的文件描述符及事件类型,然后将信息返回给应用层。

相关处理流程分析

针对epoll,主要涉及epoll_create、epoll_ctl、epoll_wait等接口,下面分别说明这几个接口的实现。

epoll_create

主要用于创建一个event poll fd,并将文件描述符与event poll进行关联。同时该文件描述符支持的操作接口定义为eventpoll_fops

epoll_ctl

该接口主要实现监听文件描述符的添加、修改及删除操作。下图主要以epoll_ctl接口增加一个监听文件描述符进行说明:

针对添加监听文件描述符,主要包括如下几个内容:

  1. 确认传递的epoll fd、监听fd是否有效(如是否epoll类型、监听文件描述符是否提供poll接口等);
  2. 内部调用ep_insert进行监听添加:
    1. 创建epitem,并调用init_poll_funcptr,初始化该epitem对应的poll_table;
    2. 调用ep_item_poll,执行该文件描述的poll接口(即file->f_ops->poll),在文件描述符的poll接口中,会调用poll_wait,将该epitem添加到文件描述符对应的等待队列头中(poll_wait调用poll_table->_qproc,即上面的ep_ptable_queue_proc),同时poll_wait中也会检测该文件描述符当前是否有事件到达。
  3. 调用ep_rbtree_insert,将该epitem添加到event poll管理的红黑树中,同时将该epitem与struct file关联,即将epitem添加至file->f_ep_links链表中。

epoll_wait

epoll_wait主要执行如下几个方面的内容:

  1. 若当前event poll的rdlist不为空,则调用ep_send_events,将事件到达的文件描述符及相应信息返回至应用层;
  2. 若当前没有事件到来,则将执行该epoll_wait的线程加入到等待队列中,并释放线程的调度权;
  3. 若线程被唤醒,且rdlist不为空,则调用ep_send_events,将事件到达的文件描述符及相应信息返回至应用层;
  4. 若线程超时唤醒,且已超过等待最大超时时间仍未事件到来,则返回失败;

epoll_wait主要执行以上几步。

我们知道epoll支持水平触发模式和边缘触发模式,是如何实现的呢?

针对ep_send_events接口,遍历event_poll的rdllist链表,从该链表上最多获取maxevents个已触发的事件,并将已触发事件对应的信息拷贝至应用层。对于水平触发的文件描述符,则将该epitem重新添加到event_poll的rdllist链表中。

以上就是epoll_create、epoll_ctl、epoll_wait接口的实现,再解释几个问题:

针对一个监听文件描述符,若直接关闭该文件描述符,还需要调用epoll_ctl接口,将该文件描述符从event poll中删除吗?

答案是不需要,还记得file->f_ep_links链表吗,当关闭文件描述符时,在__fput接口中会调用eventpoll_release接口,将该文件描述符相关的epitem从eventpoll中删除,因此对于监听的文件描述符,可以直接调用close关闭文件,不需要再调用epoll_ctl删除对应的epitem了。

epoll 仅仅监听socket对应的文件描述符吗?

epoll可以监听任何支持poll接口的文件描述符,可以是串口设备、socket、字符设备文件等。

以上就是内核中epoll实现的简要说明,如有问题,还请大家指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值