https://blog.csdn.net/mmshixing/article/details/51848673linux
首先说一下传统的I/O多路复用select和poll,对比一下和epoll之间的区别:服务器
举个例子:假若有100万用户同时与一个进程保持TCP链接,而每一时刻只有几十或者几百个tcp链接是活跃的(即能接收到TCP包),那么在每一时刻进程只须要处理这100万链接中的有一小部分。网络
select和poll这样处理的:在某一时刻,进程收集全部的链接,其实这100万链接中大部分是没有时间发生的。所以,若是每次收集事件时,都把这100万链接的套接字传给操做系统(这首先就是用户态内存到内核内存的大量复制),而由操做系统内核寻找这些连接上没有处理的事件,将会是巨大的浪费。架构
而epoll是这样作的:epoll把select和poll分为了两个部分,并发
一、调用epoll_creat创建一个epoll对象。socket
二、调用epoll_ctl向epoll对象中添加这100万个链接的套接字。tcp
三、调用epoll_wait收集发生事件的链接。=》重点是在这里,调用epoll_wait收集全部发生的事件的链接,并将事件放在一个链表中,这样只需到该链表中寻找发生链接的事件,而不用遍历100万链接!这样在实际收集事件时,epoll_wait效率会很高。函数
三个系统调用函数都是用C进行封装,在《深刻理解Nginx》P310中由函数详细说明,下面简单介绍一下。spa
首先要调用epoll_create创建一个epoll对象。参数size是内核保证可以正确处理的最大句柄数,多于这个最大数时内核可不保证效果。操作系统
epoll_ctl能够操做上面创建的epoll,例如,将刚创建的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,再也不监控它等等。
epoll_wait在调用时,在给定的timeout时间内,当在监控的全部句柄中有事件发生时,就返回用户态的进程。
那么epoll是如何实现以上想法的呢?
当某一个进程调用epoll_creat方法时,linux内核会建立一个eventpoll结构体,这个结构体中有两个成员的使用与epoll的使用方式密切相关。
epoll为什么如此高效:
当咱们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然能够飞快的返回,并有效的将发生事件的句柄给咱们用户。这是因为咱们在调用epoll_create时,内核除了帮咱们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储之后epoll_ctl传来的socket外,还会再创建一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据便可。有数据就返回,没有数据就sleep,等到timeout时间到后即便链表没数据也返回。因此,epoll_wait很是高效。
并且,一般状况下即便咱们要监控百万计的句柄,大多一次也只返回不多量的准备就绪句柄而已,因此,epoll_wait仅须要从内核态copy少许的句柄到用户态而已,如何能不高效?!
那么,这个准备就绪list链表是怎么维护的呢?当咱们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上以外,还会给内核中断处理程序注册一个回调函数,告诉内核,若是这个句柄的中断到了,就把它放到准备就绪list链表里。因此,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。
如此,一颗红黑树,一张准备就绪句柄链表,少许的内核cache,就帮咱们解决了大并发下的socket处理问题。执行epoll_create时,建立了红黑树和就绪链表,执行epoll_ctl时,若是增长socket句柄,则检查在红黑树中是否存在,存在当即返回,不存在则添加到树干上,而后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时马上返回准备就绪链表里的数据便可。
最后看看epoll独有的两种模式LT和ET。不管是LT和ET模式,都适用于以上所说的流程。区别是,LT模式下,只要一个句柄上的事件一次没有处理完,会在之后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。
这件事怎么作到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时咱们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,而后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,若是不是ET模式(就是LT模式的句柄了),而且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。因此,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即便socket上的事件没有处理完,也是不会次次从epoll_wait返回的。
1.支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有必定限制的,由FD_SETSIZE设置,默认值是2048。对于那些须要支持的上万链接数目的IM服务器来讲显然太少了。这时候你一是能够选择修改这个宏而后从新编译内核,不过资料也同时指出这样会带来网络效率的降低,二是能够选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面建立进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,因此也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目能够cat /proc/sys/fs/file-max察看,通常来讲这个数目和系统内存关系很大。
2.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之上了。
3.使用mmap加速内核与用户空间的消息传递
这点实际上涉及到epoll的具体实现了。不管是select,poll仍是epoll都须要内核把FD消息通知给用户空间,如何避免没必要要的内存拷贝就很重要,在这点上,epoll是经过内核于用户空间mmap同一块内存实现的。而若是你想我同样从2.5内核就关注epoll的话,必定不会忘记手工 mmap这一步的。
4.内核微调
这一点其实不算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网卡驱动架构。