本页目录

epoll 与事件驱动

覆盖: epoll_create1/epoll_ctl/epoll_wait → LT vs ET → timerfd/signalfd/eventfd → 与 select/poll 对比 → epoll 内部实现要点 适用: Linux 2.6+

概述

epoll 是 Linux 上高性能事件驱动编程的标准接口。nginx、HAProxy、Redis 都基于 epoll 构建。与 select/poll 的根本区别在于:epoll 是 O(1) 等待(只返回活跃 fd),而 select/poll 每次都要 O(n) 扫描整个 fd 集合。

epoll API

int epfd = epoll_create1(EPOLL_CLOEXEC);  // 创建 epoll 实例

struct epoll_event ev = {
    .events   = EPOLLIN | EPOLLET,       // 关注可读, edge-triggered
    .data.fd  = listen_fd,               // 用户数据 (可以是 ptr)
};
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);  // 注册 fd

struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout_ms);

for (int i = 0; i < nfds; i++) {
    if (events[i].events & EPOLLIN)
        handle_read(events[i].data.fd);
}

epoll_event.events

EPOLLIN:       可读 (数据到达或连接到达)
EPOLLOUT:      可写 (发送缓冲区有空间)
EPOLLERR:      错误 (自动, 不需要设)
EPOLLHUP:      挂断 (对端关闭)
EPOLLRDHUP:    对端关闭写半连接 (4.17+)
EPOLLET:       Edge-Triggered (见下)
EPOLLONESHOT:  一次性 (触发后自动移除,直到重新 EPOLL_CTL_MOD)
EPOLLEXCLUSIVE: 排他唤醒 (减少惊群, 4.5+)

LT vs ET: 关键区别

LT (Level-Triggered, 默认):
  只要 fd 还处于可读/可写状态 → 每次 epoll_wait 都返回
  → 简单: 可以只读部分数据, 剩余下次再读
  → 风险: 如果没读完, 下次 epoll_wait 还会返回 → 循环

ET (Edge-Triggered):
  只在状态变迁时通知 (不可读→可读, 不可写→可写)
  → 高效: 不会重复通知
  → 要求: 必须读/写到 EAGAIN (全部处理完)
  → 必须用 O_NONBLOCK (否则 read 可能阻塞)

ET 模式正确写法

// ET 模式下必须循环读:
while (1) {
    ssize_t n = read(fd, buf, sizeof(buf));
    if (n > 0) {
        process(buf, n);
    } else if (n == 0) {
        close(fd);  // EOF
        break;
    } else {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            break;  // 全部读完
        // handle error
        break;
    }
}

timerfd / signalfd / eventfd: epoll 伙伴

// 统一事件循环: 把所有异步事件都变成 fd → epoll 统一管理

int epfd = epoll_create1(0);

// 1. 网络 socket
epoll_add(epfd, listen_fd, EPOLLIN);

// 2. timerfd: 定时器
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
epoll_add(epfd, tfd, EPOLLIN);

// 3. signalfd: 信号
sigset_t mask; sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
int sfd = signalfd(-1, &mask, SFD_NONBLOCK);
epoll_add(epfd, sfd, EPOLLIN);

// 4. eventfd: 线程间通知
int efd = eventfd(0, EFD_NONBLOCK);
epoll_add(epfd, efd, EPOLLIN);

// 单一线程: epoll_wait → 统一处理所有事件
while (1) {
    int nfds = epoll_wait(epfd, events, MAX, -1);
    for (int i = 0; i < nfds; i++) {
        if      (events[i].data.fd == listen_fd) accept();
        else if (events[i].data.fd == tfd)       handle_timer();
        else if (events[i].data.fd == sfd)       handle_signal();
        else if (events[i].data.fd == efd)       handle_notification();
        else                                      handle_client(events[i].data.fd);
    }
}

与 select/poll 对比

selectpollepoll
fd 数量FD_SETSIZE (1024)无上限无上限
等待O(n) scanO(n) scanO(1) 活跃数
状态存储每次传入 (栈)每次传入 (栈)内核维护 (RB-tree)
边缘触发有 (ET)
1万 idle conn~100μs~100μs~1μs

epoll 内部实现

// fs/eventpoll.c
// epoll 实例包含:
//   1. 红黑树 (rbr): 注册的 fd 列表 (epoll_ctl ADD → 插入)
//   2. 就绪链表 (rdllist): 活跃的 fd (epoll_wait 返回的)
//
// 事件到达 → 驱动/协议栈调 ep_poll_callback:
//   → 检查事件匹配? → 加入 rdllist
//   → 如果有等待者 (epoll_wait) → 唤醒

参考

  • man: epoll(7), epoll_create1(2), epoll_ctl(2), epoll_wait(2)
  • 源码⁠: fs/eventpoll.c
  • LWN: "The epoll API", "Edge triggered epoll"

关键词: epoll, EPOLLET, EPOLLONESHOT, edge-triggered, timerfd, signalfd, eventfd, event loop