使用要点
边缘模式(ET)与水平模式(LT)区别
下面内容来自linux man page
The epoll event distribution interface is able to behave both as edge-triggered (ET) and as level-triggered (LT). The difference between he two mechanisms can be described as follows. Suppose that this scenario happens:
1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.
2. A pipe writer writes 2 kB of data on the write side of the pipe.
3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.
4. The pipe reader reads 1 kB of data from rfd.
5. A call to epoll_wait(2) is done.
If the rfd file descriptor has been added to the epoll interface using the EPOLLET (edge-triggered) flag, the call to epoll_wait(2) done in step 5 will probably hang despite the available data still present in the file input buffer; meanwhile the remote peer might be expecting a response based on the data it already sent. The reason for this is that edge-triggered mode delivers events only when changes occur on the monitored file descriptor. So, in step 5 the caller might end up waiting for some data that is already present inside the input buffer. In the above example, an event on rfd will be generated because of the write done in 2 and the event is consumed in 3. Since theread operation done in 4 does not consume the whole buffer data, the call to epoll_wait(2) done in step 5 might block indefinitely.
简单来说,如果使用ET模式,对于同一个事件,内核只会上报一次。如果代码没有读取完所有可读取的数据,然后继续调用epoll_wait那么是不会收到事件的。ET模式一般要设定成setnonblocking模式,并且等到读取数据返回EAGAIN或者
如果使用LT模式,则调用epoll_wait后会继续有事件触发。LT模式与poll保持一样的语义,区别是比poll的性能更好一些。
读取数据要点
- 对于流式连接,例如pipe、fifo、tcp等,通过判定read的返回值比指定读取的小,来判断缓冲区中的数据已经读完了
- 对于数据报,例如udp等,则通常需要需要持续读取到EAGAIN返回,才能判断缓冲区数据已经读完了
- 每次从epoll_wait返回后都需要调用epoll_ctl往epoll中继续增加事件
惊群问题解决
- EPOLLEXCLUSIVE 当多个fd指向同一个文件,并且都被添加到epoll中,那么就可能产生惊群问题:一旦这个文件有被写入,那么read事件会触发给多个fd。加了这个标志后,保证至少一个能收到事件。
ET模式下的饥饿现象
某一个文件,可能来的数据非常多,持续的多,那么这个文件会不停的进行read(按照我们前面说的返回EAGAN或者返回小于指定大小的思路)。由于整个运行时单线程的,那么其他fd就会一直等待,造成了所谓的饥饿现象
epoll的饥饿模式如何避免?linux man page中给出的答案时,不直接在epoll_wait返回后直接读取数据,而是将可读的fd放到一个list里面。然后再对这个list中的fd分别执行read,可以通过限制每个fd一次read的字节数来控制切换到其他fd,当一个fd 读完后从list删除掉。
处理使用 epoll ET 模式下文件描述符出现饥饿的情况_epoll wait防饥饿-CSDN博客
句柄数限制
/proc/sys/fs/epoll/max_user_watches 中记录系统范围内,限制加入到epoll中的句柄数,实际上是限制句柄相关的内存(64位下160字节,32位下90字节)最多只能占用内存的4%。
内核实现原理
epoll_wait是如何实现的
- 用户调用epoll_wait,会通过syscall指令执行系统调用
- 系统调用统一入口根据系统调用号走到内核对应的sys_epoll_wait处理函数(内核用call指令 执行)
- 然后回判断epoolfd里面关联的文件句柄是否有可读的句柄
- 如果暂时没有那么epoll_wait线程就会等待在这些句柄的queue上
- 一旦这些文件句柄(例如socket),有数据进来后,内核的ksoftirqd线程会尝试唤醒线程,并从上次线程切换的位置宠幸执行sys_epoll_wait
注意:
- epoll_ctl会将epollfd加入到io句柄的等待队列中