文章目录
  1. 内核事件表
  2. epoll_wait函数
  3. LT与ET模式
    1. LT模式
    2. ET模式
  4. EPOLLONESHOT事件

内核事件表

epoll是Linux特有的I/O复用函数,epoll使用一组函数来完成任务。epoll将用户关心的文件描述符上的事件放在内核的事件表中,此时epoll需要使用一个额外的文件描述符来唯一标识内核中的这个事件表。该文件描述符由epoll_create函数来创建。

1
2
3
4
5
6
/*
引用方式: #include <sys/epoll.h>
size: 告知内核事件表需要多大
返回文件描述符: 成功 || 返回-1并设置errno: 失败
*/
int epoll_create(int size);

此函数返回的文件描述符用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。

下面的函数来操作epoll的内核事件表。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/epoll.h>
op: 指定操作类型; EPOLL_CTL_ADD(向事件表中注册fd上的事件) || EPOLL_CTL_MOD(修改fd上的注册事件) || EPOLL_CTL_DEL(删除fd上的注册事件)
fd: 要操作的文件描述符
event: 指定事件
返回0: 成功 || 返回-1并设置errno: 失败
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);

其中,epoll_event结构体定义如下。

1
2
3
4
struct epoll_event{
uint32_t events;
epoll_data_t data;
};
  • events成员指定事件类型;epoll支持的事件类型与poll基本相同,表示epoll事件类型的宏是在poll对应的宏前面加”E”,如epoll的可读事件类型的宏为EPOLLIN。此外,epoll有两个额外事件类型:EPOLLET与EPOLLONESHOT(它们对epoll的高效运作非常关键,将在之后详细介绍)。
  • data存储用户数据;epoll_data_t结构体的定义如下。
1
2
3
4
5
6
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;

epoll_data_t结构体是一个联合体。如果使用fd,则指定事件所从属的文件描述符;如果使用ptr,则指定与fd有关的用户数据。若要同时使用ptr与fd,可考虑在ptr指定的用户数据内包含fd。

epoll_wait函数

epoll_wait函数在一段超时时间内等待一组文件描述符上的事件。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/epoll.h>
events: 用于存放内核事件表中的就绪事件
maxevents: 指定监听事件的数目最大值
timeout: 以毫秒为单位的超时值, timeout=-1: epoll_wait调用一直阻塞直到某个事件发生 || timeout=0: epoll_wait调用立即返回
返回就绪文件描述符总数: 成功 || 返回-1并设置errno: 失败
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

由于epoll在内核中维护一个事件表,每次epoll_wait调用都直接从内核事件表中取得用户注册的事件,而无需反复从用户空间读入这些事件;同时epoll_wait函数仅将事件就绪的文件描述符存入events数组中,从而网络程序索引就绪的文件描述符的时间复杂度为O(1)。

LT与ET模式

epoll对文件描述符的操作有两种模式:LT(电平触发)模式与ET(边沿触发)模式。

LT模式是默认的工作模式,LT模式下的epoll相当于一个效率较高的poll。

注:很多时候网络程序调用完select/poll/epoll_wait都会处理就绪的文件描述符,这时使用select/poll的网络程序需要遍历所有的文件描述符再进行相应的判断操作(对于select:使用FD_ISSET函数;对于poll,将(fds+i)->revents与POLLIN按位与),而epoll_wait可以直接遍历就绪的文件描述符。

网络程序向epoll内核事件表注册一个文件描述符上的EPOLLET事件后,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高效工作模式。

LT模式

对于采用LT模式的文件描述符,当epoll_wait函数检测到该文件描述符上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件;当应用程序下次调用epoll_wait时,epoll_wait还会再次向应用程序通知此事件,直到此事件被处理。

ET模式

ET模式只支持非阻塞的socket文件。

注:如果文件描述符是阻塞的,那么读或写操作将会因为没有后续事件而一直处于阻塞状态

当epoll_wait函数检测到该文件描述符上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件!

注:在这个套接字中新的事件到来之前,网络程序都无法再从epoll_wait中获取该事件。因此这次必须处理完该事件。

ET模式在很多程度上降低了一个epoll事件被重复触发的次数,因此效率要高于LT模式。

EPOLLONESHOT事件

若希望一个socket连接在任一时刻最多被一个线程处理,可向socket文件描述符注册EPOLLONESHOT事件。

对于注册了EPOLLONESHOT事件的socket文件描述符,操作系统最多触发该文件描述符上注册的一个可读/可写/异常事件,且只触发一次,除非网络程序使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。因此,一个线程处理完注册了EPOLLONESHOT事件的socket文件描述符后,需要重置该文件描述符上注册的EPOLLONESHOT事件。