epoll实现细节——源码解析


我对于网络协议比较有兴趣,同时最近也有一些面试候选人希望从事服务器偏底层的工作,所以关于IO复用技术以及实现细节就成了考点,虽然我(斗胆)问了epoll是如何实现这么一个问题,但是汗颜的是我也没有看过源码,只是看过一些网络资料而已,本着做技术要务实的态度,这两天抽空看了看3.4的源码,总结一下epoll的实现,如有任何错误之处欢迎大家指正。


本文不讨论epoll相对于其它IO复用技术的优势,也不讨论preactor以及reactor的概念。


  • 等待队列

epoll实现中大量使用了linux等待队列,等待队列有很多用途,可用于中断处理、休眠等待。进程可以在某些资源上进行等待,并由资源管理程序在适当的时候唤醒。

epoll中使用等待队列的方式主要有两类:

一类是epoll_wait调用时,如果没有就绪事件,则当前进程将自己block在一个等待队列上(eventpoll->wq)

另一类是将target fd加入target file的等待队列上,等待就绪事件的回调

本文不详细介绍等待队列的细节,以上知识足够理解epoll的实现思路

  • epoll实现

epoll暴露给用户的接口一共三个epoll_create, epoll_wait, epoll_ctl


首先是epoll_create,epoll_create1会调用ep_alloc,其中比较关键的是init两个wait_queue


init_waitqueue_head(&ep->wq);

init_waitqueue_head(&ep->poll_wait);


ep->wq之前介绍过,用来block当前进程,并等待就绪事件唤醒,而第二个ep->poll_wait用于嵌套epoll的用法,这个之后解释

epoll_create中比较关键的地方是epoll自己也是一个fd实现的,这样至少有两个好处:

一是可以在内核维护一些信息,这些信息在多次epoll_wait之间是保持的(保存的是eventpoll结构)

第二点是epoll本身也可以被poll/epoll

源码如下,其中ep是eventpoll结构,记录了epoll所有的信息,包括上面提到的两个wait_queue


file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,

O_RDWR | (flags & O_CLOEXEC);


再来看看epoll_ctl,以EPOLL_CTL_ADD为例,一个正常的流程如下图(以tcp ipv4 socket为例):

epoll实现细节——源码解析_第1张图片

最后的结果就是:如果这个socket有事件就绪,则会调用ep_poll_callback函数,这个函数负责将事件加入就绪队列并唤醒epoll_wait


//加入就绪队列

if (!ep_is_linked(&epi->rdllink))

list_add_tail(&epi_rdllink, &ep->rdllist);

...

//唤醒epoll_wait

if (waitqueue_active(&ep->wq))

wake_up_locked(&ep->wq);


这个函数代码不多,很好理解,最后是epoll_wait函数:

epoll实现细节——源码解析_第2张图片

总结一下就是如果有就绪事件,那么将信息拷贝到用户空间,如果没有那么block在ep->wq上,等待ep_poll_callback的唤醒

最后一个比较有意思的地方是,EPOLLET的实现,其实EPOLLET只是在__put_user(将相关事件拷贝回用户空间)之后,

不再重新将自己插入rdllist中,反之为水平触发


            else if (!(epi->event.events & EPOLLET)) {
                /*
                 * If this file has been added with Level
                 * Trigger mode, we need to insert back inside
                 * the ready list, so that the next call to
                 * epoll_wait() will check again the events
                 * availability. At this point, no one can insert
                 * into ep->rdllist besides us. The epoll_ctl()
                 * callers are locked out by
                 * ep_scan_ready_list() holding "mtx" and the
                 * poll callback will queue them in ep->ovflist.
                 */
                list_add_tail(&epi->rdllink, &ep->rdllist);
            }


概括一下,epoll会通过将eppoll_entry(代表需要监控的file)的wait(wait_queue_t类型)加入target file的wait_queue中,当事件就绪并与用户关心的事件有交集时会通过这个queue的回调(ep_poll_callback)将就绪事件加入rdllist,并通过ep->wq唤醒epoll_wait


最后说一下eventpoll结构中的poll_wait等待队列,它用来做epoll的嵌套用法,因为epoll本身是可以poll的,所以它需要一个自己的wait queue

比如可以这么用:

epoll实现细节——源码解析_第3张图片



整个程序有一个大的epoll,它监听其它三个epoll,而其它的三个epoll分别监听tcp,timer,signal

类似的源码中也有一个例子:

*   dfd = socket(...);
 *   efd1 = epoll_create();
 *   efd2 = epoll_create();
 *   epoll_ctl(efd1, EPOLL_CTL_ADD, dfd, ...);
 *   epoll_ctl(efd2, EPOLL_CTL_ADD, efd1, ...);
即如果efd1被dfd唤醒,则efd1会唤醒等待在自己poll_wait queue上的其它进程(这里是efd2)


果然看懂源码很有成就感


你可能感兴趣的:(C,network)