世上本没有光,佛说要有光,于是便有了光,随即有了太阳。
行文思路:
- 概述epoll的设计思想
- 代码分析:
- 大致熟悉核心结构
- 从3个核心系统调用入手代码流程(同时深入对数据结构的理解)
- 分析几个重要函数
- 其他tech datails
- ET LT
- 惊群效应
设计思想
我们希望能实现下述功能:
进程睡眠,一批fd上某个fd有事件发生的时候,进程被唤醒来处理。
select/poll的设计逻辑:
- 将进程同时挂载到所有fd相关的睡眠队列上;
- 任意一个fd的底层机制有事件触发后都会唤醒该进程;
- 该进程被唤醒后,知道有事件发生了,但是不知道有什么事件发生在哪里,于是遍历所有的fd进行判断。
epoll的设计逻辑:
- 进程睡在一个专门的队列上;
- 往所有要监听的fd上挂一个函数,当这些fd底层有事件发生的时候,调用这个函数;
- 这个函数做两件事情:
- 将该fd加入到一个ready list中;
- 唤醒对应的进程;
小结:我们希望的是同一个进程能够同时监听多个fd,select/poll方式同时睡眠在所有fd的睡眠队列上;epoll方式选择睡眠在一个专门的队列上,同时向所有的fd挂载一个回调函数,在回调函数函数中完成一些更复杂的操作。
相关结构
//详细见fs/eventpoll.c注释, 最核心结构
/* eventpoll 对象,我们前文描述的专门的睡眠队列,ready list
这些per-eventpoll的结构都应该组织在这个结构中*/
struct eventpoll;
/* epoll机制监听的事件项,根据用户空间传递的信息,监听的fd,监听的事件组织起来的 */
struct epitem;
/* 挂载在sk_wq上用于被唤醒的结构,其中包含了一个重要的回调函数(ep_table_queue_proc)。*/
struct eppoll_entry;
这三个结构就能实现上述设计思想中的描述。更细节部分可以见参考。更好的是do not talk more, show me the code。
其他结构
epoll_event //显然
ep_pqueue
一个临时量,用于将epitem结构与一个回调函数函数临时组织在一起。代码分析
从3个系统调用入手的的代码分析
epoll_create
epoll创建的eventpoll本身也要被纳入到vfs管理,epoll_create主要做两件事:
- 创建struct eventpoll结构;
- 将其纳入到vfs中管理(分配file接哦股,fd等)
-
epoll_ctl(ADD)
对于EPOLL_CTL_ADD是创建一个eppoll_entry结构,将其挂载到sk_wq上(当然是针对socket文件)。 -
epoll_wait
睡眠,然后被唤醒,ep_poll_callback()函数唤醒的。
重要函数
-
ep_ptable_queue_proc()
最重要的作用是往eppoll_entry结构上挂入ep_poll_callback()。后续又会将epoll_entry结构改到sk_wq等睡眠队列上,从而建立起sk_wq被唤醒的时候能够调用到epoll机制。 -
ep_poll_callback()
从sk_wq被唤醒的时候调用该函数,该函数可能通过ep_wq来唤醒调用ep_poll_wait睡眠的进程。 -
ep_send_event_proc()
epoll机制很多的重要逻辑都是在这个函数中实现的,譬如:oneshot, ET/LT逻辑等。
在ep_send_events_proc()中,会检查epitem是不是oneshot模式的,如果是的,第一次唤醒还是正常处理的,但是会将epitem中监听的事件选项清0,那么后续唤醒的时候,执行ep_item_poll()获取监听事件的时候,就获取的是空事件的。即确保了该监听项的oneshot。至于监听项的删除与更改则依赖于EPOLL_CTL_MOD EPOLL_CTL_DEL命令了。
总结:ep_poll_callback() 以及 epoll_wait()的后半部分可以说是epoll机制各种逻辑的实现关键。特别是通过epoll_wait()进入的ep_send_events_proc()函数。
其他tech details
参考
https://blog.csdn.net/dog250/article/details/50528373
https://blog.csdn.net/qq_36347375/article/details/91177145
https://cloud.tencent.com/developer/article/1481046
https://www.jianshu.com/p/ca2992be4722
其他
-
某个entity的wait list上挂载的不仅仅是一个进程,而是一个wait entity。本质就是上面有一个回调函数,在被唤醒的时候去调用。调用这个函数可能会唤醒某个进程,也可能执行一些更复杂的操作。
-
-
事件含义分析
解析各种事件含义,分析其是如何在epoll机制中实现的。 -
EPOLLIN 可读事件,底层transport层触发,数据到来的时候,可以通过检查buffer触发。
-
EPOLLOUT 可写事件,底层的transport层触发,但是没有很清楚是由谁来触发唤醒操作的。在不可写到可写的转变时刻会触发,是边缘触发类型的。
-
EPOLLRDHUP 对端断开连接
-
EPOLLPRI 有带外数据到来
-
EPOLLERR 发生错误
-
EPOLLHUP 文件描述符被挂断
//上述6种事件的触发,根据具体情况是不同的,我们可以自己选择。自己在传输层判断事件,自己触发。一般来说,我们使用sock sk中的一些函数就可以进行唤醒事件触发操作。sk->sk_data_ready(), sk->sk_write_space()等函数。详情要见各种不同的域:譬如TCP域。参见https://cloud.tencent.com/developer/article/1481046。 -
EPOLLET 边缘触发
-
EPOLLONESHOT 只监听一次,阅读源码已经清楚这个机制了
//ET与ONESHOT逻辑都是在ep_send_events_proc()逻辑中实现的。详情见源码注释 -
EPOLLWAKEUP 与系统的电源管理自动睡眠有关
//EPOLLWAKEUP还不清楚,与系统自动休眠有关。 -
EPOLLEXCLUSIVE 避免惊群问题。在ep_epoll_callback()中实现了该机制的。