0%

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

1
2
3
4
#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集

1
2
3
4
5
6
7
8
9
10
11
12
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. 等待事件

1
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)的一样。