- 条件触发: 只要输入缓冲有数据就会一直通知该事件
- 边缘触发: 输入缓冲收到数据时仅注册1次该事件,即使输入缓冲中还留有数据,也不会再进行注册
- 水平触发(
level-triggered
,也被称为条件触发):只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你) - 边缘触发(
edge-triggered
): 每当状态变化时,触发一个事件
举个读socket
的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification
通知应用程序可读。应用程序读了50个字节,然后重新调用api
等待io
事件。
这时条件触发的api
会因为还有50个字节可读从而立即返回用户一个read ready notification
。而边缘触发的api
会因为可读这个状态没有发生变化而陷入长期等待。
因此在使用边缘触发的api
时,要注意每次都要读到socket
返回EWOULDBLOCK
为止,否则这个socket
就算废了。而使用条件触发的api
时,如果应用程序不需要写就不要关注socket
可写的事件,否则就会无限次的立即返回一个write ready notification
。大家常用的select
就是属于条件触发这一类,长期关注socket
写事件会出现CPU
100%的毛病。
epoll的优点:
- 支持一个进程打开大数目的
socket
描述符(FD
)
select
最不能忍受的是一个进程所打开的FD
是有一定限制的,由FD_SETSIZE
设置,默认值是2048
。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache
方案),不过虽然linux
上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll
则没有这个限制,它所支持的FD
上限是最大可以打开文件的数目,这个数字一般远大于2048
,举个例子,在1GB
内存的机器上大约是10
万左右,具体数目可以cat /proc/sys/fs/file-max
察看,一般来说这个数目和系统内存关系很大。
IO
效率不随FD
数目增加而线性下降
传统的select/poll
另一个致命弱点就是当你拥有一个很大的socket
集合,不过由于网络延时,任一时间只有部分的socket
是”活跃”的, 但是select/poll
每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll
不存在这个问题,它只会对”活跃”的socket
进行 操作—这是因为在内核实现中epoll
是根据每个fd
上面的callback
函数实现的。那么,只有”活跃”的socket
才会主动的去调用 callback
函数,其他idle
状态socket
则不会,在这点上,epoll
实现了一个”伪”AIO
,因为这时候推动力在os
内核。在一些 benchmark
中,如果所有的socket
基本上都是活跃的—比如一个高速LAN
环境,epoll
并不比select/poll
有什么效率,相反,如果过多使用epoll_ctl
,效率相比还有稍微的下降。但是一旦使用idle connections
模拟WAN
环境,epoll
的效率就远在select/poll
之上了。
- 使用
mmap
加速内核与用户空间的消息传递。
这点实际上涉及到epoll
的具体实现了。无论是select
,poll
还是epoll
都需要内核把FD
消息通知给用户空间,如何避免不必要的内存拷贝就 很重要,在这点上,epoll
是通过内核于用户空间mmap
同一块内存实现的。而如果你想我一样从2.5内核就关注epoll
的话,一定不会忘记手工 mmap
这一步的。
- 内核微调
这一点其实不算epoll
的优点了,而是整个linux
平台的优点。也许你可以怀疑linux
平台,但是你无法回避linux
平台赋予你微调内核的能力。 比如,内核TCP/IP
协议栈使用内存池管理sk_buff
结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)
的大小 — 通过echo XXXX>/proc/sys/net/core/hot_list_length
完成。再比如listen
函数的第2个参数(TCP
完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI
网卡驱动架构。 ?