Linux IO多路复用 epoll
处理大批量句柄神器-epoll
select
最大并发限制:由于一个进程所打开的fd是有限制的,由FD_SETSIZE设置,默认是1024,因此select模型最大并发数
效率问题:每次进行select调用都会线性扫描整个fd集合,随着fd的增加,效率会下降。
内核/用户空间内存拷贝问题:select在解决fd消息传递给用户空间时采用了内存拷贝的方式。
epoll
支持一个进程打开较大数目的文件描述符(fd)
epoll则没有对描述数目的限制,它所支持的文件描述符上限是整个系统最大可以打开的文件描述符。
IO效率不会随着文件描述符的增加而线性下降
epoll不存在这个问题,它只会对活跃的socket进行操作,这是因为在内核实现中,epoll是根据每个fd上面的callback函数实现的。因此,只有活跃的socket才会主动去调用callback函数,其他idle状态socket则不会
使用mmap加速内核与用户空间的消息传递
epoll通过内核与用户空间mmap同一块内存来实现
epoll的高效性主要体现在以下三个方面:
(1)select/poll每次调用都要传递所要监控的所有fd给select/poll系统调用,这意味着每次调用select/poll时都要将fd列表从用户空间拷贝到内核,当fd数目很多时,这会造成性能低效。对于epoll_wait,每次调用epoll_wait时,其不需要将fd列表传递给内核,epoll_ctl不需要每次都拷贝所有的fd列表,只需要进行增量式操作。因此,在调用epoll_create函数之后,内核已经在内核开始准备数据结构用于存放需要监控的fd了。其后,每次epoll_ctl只是对这个数据结构进行简单的维护操作即可。
(2)内核使用slab机制,为epoll提供了快速的数据结构。在内核里,一切都是文件。因此,epoll向内核注册了一个文件系统,用于存储所有被监控的fd。当调用epoll_create时,就会在这个虚拟的epoll文件系统中创建一个file节点。epoll在被内核初始化时,同时会分配出epoll自己的内核告诉cache区,用于存放每个我们希望监控的fd。这些fd会以红黑树的形式保存在内核cache里,以支持快速查找、插入和删除。这个内核高速cache,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好想要的size的内存对象,每次使用时都使用空闲的已分配好的对象。
(3)当调用epoll_ctl往epfd注册百万个fd时,epoll_wait仍然能够快速返回,并有效地将发生的事件fd返回给用户。原因在于,当我们调用epoll_create时,内核除了帮我们在epoll文件系统新建file节点,同时在内核cache创建红黑树用于存储以后由epoll_ctl传入的fd外,还会再建立一个list链表,用于存储准备就绪的事件。当调用epoll_wait时,仅仅观察这个list链表中有无数据即可。如果list链表中有数据,则返回这个链表中的所有元素;如果list链表中没有数据,则sleep然后等到timeout超时返回。所以,epoll_wait非常高效,而且,通常情况下,即使我们需要监控百万计的fd,但大多数情况下,一次也只返回少量准备就绪的fd而已。因此,每次调用epoll_wait,其仅需要从内核态复制少量的fd到用户空间而已。那么,这个准备就绪的list链表是怎么维护的呢?过程如下:当我们执行epoll_ctl时,除了把fd放入到epoll文件系统里file对象对应的红黑树之外,还会给内核中断处理程序注册一个回调函数,其告诉内核,如果这个fd的中断到了,就把它放到准备就绪的list链表中。
如此,一棵红黑树、一张准备就绪的fd链表以及少量的内核cache,就帮我们解决了高并发下fd的处理问题。
总结一下:
执行epoll_create时,创建了红黑树和就绪list链表;
执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在则立即返回,不存在则添加到红黑树中,然后向内核注册回调函数,用于当中断事件到来时向准备就绪的list链表中插入数据。
执行epoll_wait时立即返回准备就绪链表里的数据即可。
参考:https://blog.csdn.net/zhaobryant/article/details/80557262
参考:https://blog.csdn.net/HDUTigerkin/article/details/7517390
参考:https://blog.csdn.net/men_wen/article/details/53456491