epoll相关
IO事件通知机制。
1. 简介
跟poll(2)
类似,可以同时监测多个文件描述符上的事件。可以使用水平触发和边沿触发两种模式,可以同时监听大量fd,而且性能很好。
epoll_create(2)
创建一个epoll实例,并返回一个fd用于控制该实例。epoll_ctl(2)
添加感兴趣的fd到epoll中epoll_wait(2)
等待IO事件发生
1.1. 水平触发(LT)和边沿触发(ET)
加入epoll监听fd集中的fd可以采用LT和ET通知模式,在读事件发生时,如果没有一次性读完所有数据,采用LT的fd下次还将通过epoll触发读事件,而采用ET的fd下次不会再触发读事件。也就是说,如果使用ET,程序需要在一次读事件通知时,把系统缓存中的数据全部读出,否则剩下的数据很可能会一直留在系统缓存,而没有事件来通知。
因此使用ET时,需要注意两点:
- 使用非阻塞的fd
- 在
read(2)
或write(2)
返回EAGAIN时,才使用epoll等待事件,否则可以一直无阻塞的进行读写事件。
如果使用LT,就跟poll(2)
的语义基本一样了。
使用ET也可能因为多次收到数据,而产生多个事件,这样在处理该fd上的第一个事件时,下次循环还可能触发第二次事件,但因为第一次已经读完的数据,所以第二次可能就没数据可以读了。用户可以使用EPOLLONESHOT
标记告诉epoll触发一次事件后,将fd从监听集中移除,但这样需要用户下次使用的时候,自己加进去。
1.2. /proc接口
/proc/sys/fs/epoll/max_user_watches
规定了当前用户可以通过1个epoll实例注册监听的最大fd个数。每个注册的fd在32位内核上消耗大概90字节,64位上消耗大概160字节。默认个数是可用最小内存的1/25除以消耗的字节数。
2. 基本用法
2.1. 创建epoll
#include <sys/epoll.h>
int epoll_create(int size); // size被忽略,但必须大于0
int epoll_create1(int flags);
如果flags
为0,两个函数一样。flags
可以为EPOLL_CLOEXEC
,多线程中有用,一个线程创建epoll,另一个线程执行fork+execv,会出现竞争,使用这个flag可以避免竞争。
2.2。 操作监听fd集
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op
可以是:
- EPOLL_CTL_ADD: 注册监听fd
- EPOLL_CTL_MOD: 修改监听fd的事件
- EPOLL_CTL_DEL: 删除监听的fd
events
可以是:
- EPOLLIN: fd可读
- EPOLLOUT:fd可写
- EPOLLRDHUP:流式socket对端关闭或对端写关闭。
- EPOLLPRI:
read(2)
读取带外数据 - EPOLLERR: fd上有错误发生,epoll会一直监听该事件,不用手动设置
- EPOLLHUP: fd挂起,epoll会一直监听该事件,不用手动设置
- EPOLLET: 设置ET模式,默认是LT
- EPOLLONESHOT:fd只触发一次事件,下次需要重新注册
2.3. 等待事件
// timeout单位是ms
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);
这两个的区别跟select(2)
和pselect(2)
的一样。