epoll
1. epoll简介:
epoll
是Linux内核的可扩展I/O事件通知机制。于Linux 2.5.44首度登场,它设计目的旨在取代既有POSIX select
与poll
系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能(举例来说:旧有的系统函数所花费的时间复杂度为O(n),epoll
的时间复杂度O(log n))。epoll 实现的功能与 poll 类似,都是监听多个文件描述符上的事件。
epoll
与FreeBSD的kqueue
类似,底层都是由可配置的操作系统内核对象建构而成,并以文件描述符(file descriptor)的形式呈现于用户空间。epoll
通过使用红黑树(RB-tree)搜索被监控的文件描述符(file descriptor)。
在 epoll 实例上注册事件时,epoll 会将该事件添加到 epoll 实例的红黑树上并注册一个回调函数,当事件发生时会将事件添加到就绪链表中。
2. 方法:
- epoll_event定义:
struct epoll_event{ __unit32_t events; // epoll事件 epoll_data_t data; // 用户数据 };
events:描述事件类型,和poll支持的事件类型基本相同(两个额外的事件:EPOLLET和EPOLLONESHOT,高效运作的关键);
data成员:存储用户数据;
- 在内核中创建
epoll
实例并返回一个epoll
文件描述符:
int epoll_create(int size);
在最初的实现中,调用者通过 size
参数告知内核需要监听的文件描述符数量。如果监听的文件描述符数量超过 size, 则内核会自动扩容。
现在 size 已经没有这种语义了,但是调用者调用时 size 依然必须大于 0,以保证后向兼容性。
- 向 epfd 对应的内核
epoll
实例添加、修改或删除对 fd (要操作的文件描述符)上事件 event 的监听:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op 可以为 EPOLL_CTL_ADD
, EPOLL_CTL_MOD
, EPOLL_CTL_DEL
分别对应的是操作类型:添加新的事件,修改文件描述符上监听的事件类型,从实例上删除一个事件。
如果 event 的 events 属性设置了 EPOLLET
flag,那么监听该事件的方式是边缘触发。
- epoll_wt() 可以让内核去检测就绪的事件,并将就绪的事件放到就绪列表中并返回(只用于输出检测到就绪的事件),通过返回的事件数组做进一步的事件处理:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
返回:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno;
timeout:指定epoll的超时时间,单位是毫秒。当timeout为-1是,epoll_wait调用将永远阻塞,直到某个时间发生。当timeout为0时,epoll_wait调用将立即返回。
maxevents:指定最多监听多少个事件,必须大于0;
events:检测到事件,将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中;
3. epoll的两种触发模式:
使用脉冲信号来解释LT和ET可能更加贴切。Level是指信号只需要处于水平,就一直会触发;而edge则是指信号为上升沿或者下降沿时触发。
- 水平触发 LT:
- LT(Level - Triggered)是epoll默认的工作方式,并且同时支持 Block 和 Nonblock Socket。在这种做法中,内核检测到一个文件描述符就绪了,然后可以对这个就绪的 fd 进行 IO 操作,如果不作任何操作,内核还是会继续通知。
- LT模式下的读操作:只要缓冲内容存在,就一直返回读就绪;
- LT模式下的写操作:只要缓冲区还不满,就一直返回写就绪。
- 边沿触发 ET:
- ET(Edge - Triggered)是高速工作方式,只支持 Nonblock socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 检测到。然后它会假设你知道文件描述符已经就绪,就不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 进行 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(只有最初变化时的那一次就绪通知)。 ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
- ET模式下的读操作:当缓冲区由不可读变为可读的时候(即缓冲区由空变为不空);当有新数据到达时(即缓冲区中的待读数据变多);当缓冲区有数据可读且应用进程对相应的描述符进行
EPOLL_CTL_MOD
修改EPOLLIN
事件时。 - ET模式下的写操作:当缓冲区由不可写变为可写时。当有旧数据被发送走,即缓冲区中的内容变少的时候。当缓冲区有空间可写,且应用进程对相应的描述符进行
EPOLL_CTL_MOD
修改EPOLLOUT
事件时。