好久没有更新了,今天更新一篇epoll的介绍文档,后续继续更新linux 设备驱动。
针对linux支持select、poll、epoll等IO多路复用机制,本篇文章主要介绍epoll的实现原理。本篇文章主要分
为如下几部分:
- Epoll api接口说明;
- 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有哪些优势呢?
- 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,其中:
- struct eventpoll、struct epitem分别对应epoll fd、监听的文件描述符,其中epitem作为红黑树节点插入到struct eventpoll中;
- 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管理)。
- Epoll fd的文件描述符的私有数据指针指向struct event_poll对象;
- 注册到epoll fd的文件描述符,通过struct epoll_filefd,完成与epitem的关联;而epitem则作为一个节点插入event poll管理的红黑树中;
- 借助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接口增加一个监听文件描述符进行说明:
针对添加监听文件描述符,主要包括如下几个内容:
- 确认传递的epoll fd、监听fd是否有效(如是否epoll类型、监听文件描述符是否提供poll接口等);
- 内部调用ep_insert进行监听添加:
- 创建epitem,并调用init_poll_funcptr,初始化该epitem对应的poll_table;
- 调用ep_item_poll,执行该文件描述的poll接口(即file->f_ops->poll),在文件描述符的poll接口中,会调用poll_wait,将该epitem添加到文件描述符对应的等待队列头中(poll_wait调用poll_table->_qproc,即上面的ep_ptable_queue_proc),同时poll_wait中也会检测该文件描述符当前是否有事件到达。
- 调用ep_rbtree_insert,将该epitem添加到event poll管理的红黑树中,同时将该epitem与struct file关联,即将epitem添加至file->f_ep_links链表中。
epoll_wait
epoll_wait主要执行如下几个方面的内容:
- 若当前event poll的rdlist不为空,则调用ep_send_events,将事件到达的文件描述符及相应信息返回至应用层;
- 若当前没有事件到来,则将执行该epoll_wait的线程加入到等待队列中,并释放线程的调度权;
- 若线程被唤醒,且rdlist不为空,则调用ep_send_events,将事件到达的文件描述符及相应信息返回至应用层;
- 若线程超时唤醒,且已超过等待最大超时时间仍未事件到来,则返回失败;
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实现的简要说明,如有问题,还请大家指正。