Linux系统下,Nginx会默认使用epoll作为事件驱动机制,使用epoll是Nginx性能高的一个重要原因。
本文对epoll工作机制稍作整理。
事件驱动
事件驱动又称作IO多路复用,epoll是Linux内核实现一种IO多路复用机制
- IO: 在Linux系统下,一切皆文件。文件可读/可写即称作IO,比如:普通文件、socket套接字、进程之间通信的管道pipe,都是可以IO的对象。Linux系统统一用文件描述符fd表示。
- 事件:
- 可读事件:当fd关联的内核读缓冲区可读时,则触发可读事件 (可写:内核缓冲区非空)
- 可写事件:当fd关联的内科写缓冲区可写时,则触发可写事件 (可读:内核缓冲区不满)
- 事件通知方式
- 主动通知:仅当有fd有事件发生时,内核主动通知上层应用有事件发生的fd集合
- 被动通知:上层应用向内核轮询检查fd集合中的fd是否有事件发生
epoll API介绍

epoll_create
- 原型:int epoll_create(int size)
- 功能:创建一个epoll实例,内核会返回一个表示epoll实例的fd
- 入参
- size: 原本表示所要监视fd的最多个数。现在Linux版中无意义,只要传值大于0的整数即可
- 返回值: epoll实例的fd
- 说明:调用该API,内核会产生一个epoll实例数据结构,然后返回一个文件描述符
epoll_ctl
- 原型: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
- 功能:添加/删除/修改要监听的事件
- 入参
- epfd: 即epoll_create创建的epoll实例描述符
- op: 添加、删除或修改
- EPOLL_CTL_ADD: 添加一个需要监视的fd
- EPOLL_CTL_DEL: 删除一个fd
- EPOLL_CTL_MOD: 修改一个fd
- fd: IO对象描述符
- event结构体
struct epoll_event { __uint32_t events; epoll_data_t data; };
- events字段:感兴趣的事件描述,可多选
- EPOLLIN:fd处于可读状态
- EPOLLOUT:fd处于可写状态
- EPOLLET:通知模式为边缘触发模式,相对于水平触发模式而言
- EPOLLONESHOT:第一次进行通知,之后不再检测
- EPOLLHUP: 本端产生一个挂断事件,默认检测事件
- EPOLLDHUP:对端产生一个挂断事件
- EPOLLPRI:由带外数据触发
- EPOLLERR:产生错误时触发,默认检测事件
- data字段:是一个union结构
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; };
- 如果不需要额外的信息,只需简单的赋值:
epoll_data.fd = fd
- 如果需要额外信息,则可以使用
void *ptr
, 这样epoll_wait返回时可以使用
- 如果不需要额外的信息,只需简单的赋值:
- events字段:感兴趣的事件描述,可多选
- 返回值: 如果返回-1表示调用失败
epoll_wait
- 原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
- 功能:阻塞等待已注册的事件发生,返回事件的数目,并将触发的事件写入events数组中
- 入参:
- epfd: 即epoll_create创建的epoll实例描述符
- events:用来记录被触发的事件数组,其大小应该和maxevents一致
- maxevents: 返回的events的最大个数
- timeout: 阻塞超时(以ms为单位)
- 如果传参-1, 则调用一直阻塞,直到有事件发生或捕获其他信号
- 如果传参0,则非阻塞检测是否有事件发生,无论有事件还是没事件,调用立即返回
- 如果传参大于0, 则最多等待timeout时间返回,如果等待期间有事件发生则立即返回
水平触发和边缘触发
水平触发
- 只要缓冲区有数据,则返回可读事件
- 只要缓冲区还不满,则返回可写事件
边缘触发
-
可读事件
- 当缓冲区数据为空变为非空时,则返回可读事件
- 当缓冲区接收到新数据时,即缓冲区待读数据变多时,返回可读事件
- 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时,返回可读事件
-
可写事件
- 当缓冲区由不可写变为可写时,则返回可写事件
- 当有旧数据被发送走,即缓冲区中的内容变少的时候,返回可写事件
- 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时,返回可写事件
代码示例
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <string.h>
#define MAX_EVENTS 5
#define READ_SIZE 10
int main() {
int running = 1, event_count, i;
size_t bytes_read;
char read_buffer[READ_SIZE + 1];
struct epoll_event event, events[MAX_EVENTS];
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
fprintf(stderr, "Failed to create epoll fd\n");
return 1;
}
event.events = EPOLLIN;
event.data.fd = 0;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event) == -1) {
fprintf(stderr, "Failed to add fd to epoll\n");
return 1;
}
while (running) {
printf("\nPolling for input...\n");
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, 30000);
printf("%d ready events\n", event_count);
for (i = 0; i < event_count; i++) {
printf("Reading fd '%d' -- ", events[i].data.fd);
bytes_read = read(events[i].data.fd, read_buffer, READ_SIZE);
printf("%zd bytes read.\n", bytes_read);
read_buffer[bytes_read] = '\0';
printf("Read '%s'\n", read_buffer);
if (!strncmp(read_buffer, "stop\n", 5)) {
running = 0;
}
}
}
if (close(epoll_fd)) {
fprintf(stderr, "Failed to close epoll fd\n");
return 1;
}
return 0;
}