我对于网络协议比较有兴趣,同时最近也有一些面试候选人希望从事服务器偏底层的工作,所以关于IO复用技术以及实现细节就成了考点,虽然我(斗胆)问了epoll是如何实现这么一个问题,但是汗颜的是我也没有看过源码,只是看过一些网络资料而已,本着做技术要务实的态度,这两天抽空看了看3.4的源码,总结一下epoll的实现,如有任何错误之处欢迎大家指正。
本文不讨论epoll相对于其它IO复用技术的优势,也不讨论preactor以及reactor的概念。
epoll实现中大量使用了linux等待队列,等待队列有很多用途,可用于中断处理、休眠等待。进程可以在某些资源上进行等待,并由资源管理程序在适当的时候唤醒。
epoll中使用等待队列的方式主要有两类:
一类是epoll_wait调用时,如果没有就绪事件,则当前进程将自己block在一个等待队列上(eventpoll->wq)
另一类是将target fd加入target file的等待队列上,等待就绪事件的回调
本文不详细介绍等待队列的细节,以上知识足够理解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为例):
最后的结果就是:如果这个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函数:
总结一下就是如果有就绪事件,那么将信息拷贝到用户空间,如果没有那么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,它监听其它三个epoll,而其它的三个epoll分别监听tcp,timer,signal
类似的源码中也有一个例子:
果然看懂源码很有成就感