工作队列和等待队列
工作队列指处在运行态的进程排成的队列
等待队列指一些对此socket感兴趣的进程组成的队列,事实上这个队列只是对进程的引用,当socket可读或可写时,会唤醒等待队列中的进程
当进程A运行了socket,操作系统会创建一个由文件系统管理的socket对象
这个socket对象包含了发送缓冲区、接收缓冲区与等待队列等成员。等待队列是个非常重要的结构,它指向所有需要等待该socket事件的进程。
当程序执行到recv时,操作系统会将进程 A 从工作队列移出,将进程A的引用添加到该socket的等待队列中

Epoll设计思路

- 相比与select,epoll不会再每次都需要重添加队列再进行epoll_wait
- select需要每次将集合数据从用户态拷贝到内核态进行监控
- 当有socket可读可写时,select还需要遍历fd集合。epoll采用就绪列表,不用遍历
epoll原理
如下图所示,当某个进程调用epoll_create 方法时,内核会创建一个 eventpoll 对象(也就是程序中 epfd 所代表的对象)。eventpoll 对象也是文件系统中的一员,和socket一样,它也会有等待队列,创建此eventpoll的进程会被添加到此等待队列
如果进程通过epoll监视sock1,2,3,就会将eventepoll加入到三个socket的等待队列

当socket收到数据后,中断程序会操作 eventpoll 对象,而不是直接操作进程。

当socket收到数据后,中断程序会给 eventpoll 的**“就绪列表”**添加socket引用。

因此当某个加入epoll的socket可读可写后,内核会将此socket加入到epoll的rdlist,并通知epoll的等待队列上的进程
LT与ET
平时大家使用 epoll 时都知道其事件触发模式有默认的 level-trigger 模式和通过 EPOLLET 启用的 edge-trigger 模式两种。从 epoll 发展历史来看,它刚诞生时只有 edge-trigger 模式,后来因容易产生 race-cond 且不易被开发者理解,又增加了 level-trigger 模式并作为默认处理方式。
二者的差异在于 level-trigger 模式下只要某个 fd 处于 readable/writable 状态,无论什么时候进行 epoll_wait 都会返回该 fd;而 edge-trigger 模式下只有某个 fd 从 unreadable 变为 readable 或从 unwritable 变为 writable 时,epoll_wait 才会返回该 fd。
通常的误区是:level-trigger 模式在 epoll 池中存在大量 fd 时效率要显著低于 edge-trigger 模式。
但从 kernel 代码来看,edge-trigger/level-trigger 模式的处理逻辑几乎完全相同,差别仅在于 level-trigger 模式在 event 发生时不会将其从 ready list 中移除,略为增大了 event 处理过程中 kernel space 中记录数据的大小。
然而,edge-trigger 模式一定要配合 user app 中的 ready list 结构,以便收集已出现 event 的 fd,再通过 round-robin 方式挨个处理,以此避免通信数据量很大时出现忙于处理热点 fd 而导致非热点 fd 饿死的现象。统观 kernel 和 user space,由于 user app 中 ready list 的实现千奇百怪,不一定都经过仔细的推敲优化,因此 edge-trigger 的总内存开销往往还大于 level-trigger 的开销。
一般号称 edge-trigger 模式的优势在于能够减少 epoll 相关系统调用,这话不假,但 user app 里可不是只有 epoll 相关系统调用吧?为了绕过饿死问题,edge-trigger 模式的 user app 要自行进行 read/write 循环处理,这其中增加的系统调用和减少的 epoll 系统调用加起来,有谁能说一定就能明显地快起来呢?
实际上,epoll_wait 的效率是 O(ready fd num) 级别的,因此 edge-trigger 模式的真正优势在于减少了每次 epoll_wait 可能需要返回的 fd 数量,在并发 event 数量极多的情况下能加快 epoll_wait 的处理速度,但别忘了这只是针对 epoll 体系自己而言的提升,与此同时 user app 需要增加复杂的逻辑、花费更多的 cpu/mem 与其配合工作,总体性能收益究竟如何?只有实际测量才知道,无法一概而论。不过,为了降低处理逻辑复杂度,常用的事件处理库大部分都选择了 level-trigger 模式(如 libevent、boost::asio等)
结论:
•epoll 的 edge-trigger 和 level-trigger 模式处理逻辑差异极小,性能测试结果表明常规应用场景 中二者性能差异可以忽略。
•使用 edge-trigger 的 user app 比使用 level-trigger 的逻辑复杂,出错概率更高。
•edge-trigger 和 level-trigger 的性能差异主要在于 epoll_wait 系统调用的处理速度,是否是 user app 的性能瓶颈需要视应用场景而定,不可一概而论
Epoll是Linux下的一种高效I/O事件通知机制,它通过就绪列表优化了对大量文件描述符的监控。与select不同,Epoll在边缘触发(ET)和水平触发(LT)模式下工作,其中LT模式是默认且更易用的,而ET模式可能导致处理热点fd时其他fd被饿死,需要配合用户空间的策略。文章指出,两种模式的性能差异主要体现在epoll_wait的处理速度,实际应用中应根据场景选择合适模式。
9495





