epoll
一、epoll说明
- epoll 是在 Linux 2.6 才引进的,而且它并不适用于其它 Unix-like 系统。它提供了一个与select 和 poll 函数相似的功能;
- select 可以在某一时间监视最大达到 FD_SETSIZE 数量的文件描述符, 通常是由在 libc 编译时指定的一个比较小的数字;
- poll 在同一时间能够监视的文件描述符数量并没有受到限制,即使除了其它因素,更加的是我们必须在每一次都扫描所有通过的描述符来检查其是否存在己就绪通知,它的时间复杂度为 O(n) ,是缓慢的;
- epoll不存在这个问题,它只会对活跃的socket进行操作,这是因为在内核实现中,epoll是根据每个fd上面的callback函数实现的,而且fd以
红黑树和链表
的结构存储。因此,只有活跃的socket才会主动去调用callback函数,其他idle状态socket则不会。在这一点上,epoll实现了一个伪AIO,其内部推动力在内核; - 无论是select,poll还是epoll,它们都需要内核把fd消息通知给用户空间。因此,如何避免不必要的内存拷贝就很重要了。对于该问题,epoll通过内核与用户空间mmap同一块内存来实现。
所以在高并发场景epoll更加适用,当然在并发量比较小的情况下,还是select和poll更合适。
二、函数原型
1 | //创建一个epoll的句柄 |
epoll_ctl()
int epfd
epoll_create函数的返回值。
int op
表示动作类型。有三个宏来表示:
1 | EPOLL_CTL_ADD //注册新的fd到epfd中; |
int fd
需要监听的fd。
struct epoll_event *event
需要监听的事件。
1 | // 保存触发事件的某个文件描述符相关的数据 |
Epoll events
1 | EPOLLIN //表示对应的文件描述符可读(包括对端Socket); |
返回值
- 成功时,返回大于0;
- 失败时,返回-1;
epoll_wait()
int epfd
epoll_create函数的返回值。
struct epoll_event *events
分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据赋值到这个event数组中,不会去帮助我们在用户态分配内存)。
int maxevents
maxevents告诉内核这个events数组有多大,这个maxevents的值不能大于创建epoll_create时的size.
int timeout
是一个用
毫秒
表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。如果值为-1,epoll就永远都不会超时。如果整数值为32个比特,那么最大的超时周期大约是30分钟。
- INFTIM //永远等待
- 0 //立即返回,不阻塞进程
- 正值 //等待指定数目的毫秒数
返回值
- 成功时,epoll()返回结构体中revents域不为0的文件描述符个数;
- 如果在超时前没有任何事件发生,poll()返回0;
- 失败时,poll()返回-1;
三、epoll工作模式
LT模式(Level Triggered,水平触发)
该模式是epoll的缺省工作模式,其同时支持阻塞和非阻塞socket。内核会告诉开发者一个文件描述符是否就绪,如果开发者不采取任何操作,内核仍会一直通知。ET模式(Edge Triggered,边缘触发)
该模式是一种高速处理模式,当且仅当状态发生变化时才会获得通知。在该模式下,其假定开发者在接收到一次通知后,会完整地处理该事件,因此内核将不再通知这一事件。注意,缓冲区中还有未处理的数据不能说是状态变化,因此,在ET模式下,开发者如果只读取了一部分数据,其将再也得不到通知了。正确的做法是,开发者自己确认读完了所有的字节(一直调用read/write直到出错EAGAGIN为止)。
Nginx默认采用的就是ET(边缘触发)。
四、epoll流程小结
- 执行epoll_create时,创建了红黑树和就绪list链表;
- 执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在则立即返回,不存在则添加到红黑树中,然后向内核注册回调函数,用于当中断事件到来时向准备就绪的list链表中插入数据。
- 执行epoll_wait时立即返回准备就绪链表里的数据即可。
五、伪代码
1 | struct epoll_event event; |