深刻剖析epoll

select的不足

在IO多路复用中select(poll)有诸多限制,不少人会说select的缺点是在Linux内核中,select所用到的FD_SET是有限的,(内核中有个参数__FD_SETSIZE定义了每一个FD_SET的句柄个数)。可是更重要的缺点却触及不到:并发

  1. select 采用的是轮询机制,select被调用时fd_set会被遍历,致使其时间复杂度为O(n)。
  2. 内核/用户空间拷贝问题:当有事件发生,且select轮询完后,fd_set会从内核态拷贝到用户态

当并发上来后,轮询的低效率和频繁的内核态用户态切换会致使select的性能急剧降低:
图 1. libevent的benchmark函数

epoll的内部调用流程

图 2. epoll的内部调用流程

(0) 红黑树是在mmap出的内存上的,减小了用户空间和内核空间的拷贝
(1) epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,知道rdlist不空时进程才被唤醒。
(2) 文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),致使相应fd上的回调函数ep_poll_callback()被调用。
(3) ep_poll_callback将相应fd对应epitem加入rdlist,致使rdlist不空,进程被唤醒,epoll_wait得以继续执行。
(4) ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
(5) ep_send_events函数,它扫描txlist中的每一个epitem,调用其关联fd对用的poll方法。以后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。
(6) 若是这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其从新加入回rdlist。不然(ET模式)不在加入rdlist。性能

ET模式与LT模式的不一样

ET和LT模式下的epitem均可以经过插入红黑树时的回调(ep_poll_callback)方式加入rdlist从而唤醒epoll_wait,但LT模式下的epitem还能够经过txlist(ep_send_events)从新加入rdlist唤醒epoll_wait。因此ET模式下,fd就绪,只会被通知一次,而LT模式下只要知足相应读写条件就返回就绪(经过txlist加入rdlist)。spa

2.1 直接回调插入:fd状态改变才会触发。

对于读取操做:blog

(1) 当buffer由不可读状态变为可读的时候,即由空变为不空的时候。进程

(2) 当有新数据到达时,即buffer中的待读内容变多的时候。事件

对于写操做:ip

(1) 当buffer由不可写变为可写的时候,即由满状态变为不满状态的时候。内存

(2) 当有旧数据被发送走时,即buffer中待写的内容变少得时候。资源

2.2 txlist(ep_send_events):fd的events中有相应的事件(位置1)即会触发。

对于读操做:

(1) buffer中有数据可读的时候,即buffer不空的时候fd的events的可读为就置1。

对于写操做:

(1) buffer中有空间可写的时候,即buffer不满的时候fd的events的可写位就置1。

3 总结

与select相比,epoll的回调机制使得资源可以直接使用在有活动的事件上,而不用线性轮询全部的事件。同时 epoll经过内核与用户空间mmap同一块内存,减小了用户空间和内核空间的数据交换,解决了select的重要痛点。

另外一方面,LT是epoll的默认操做模式,当epoll_wait函数检测到有事件发生并将通知应用程序,而应用程序不必定必须当即进行处理,这样epoll_wait函数再次检测到此事件的时候还会通知应用程序,直到事件被处理。

而ET模式,只要epoll_wait函数检测到事件发生,通知应用程序当即进行处理,后续的epoll_wait函数将再也不检测此事件。所以ET模式在很大程度上下降了同一个事件被epoll触发的次数,所以效率比LT模式高。

相关文章
相关标签/搜索