---
title: epoll 与事件驱动
url: https://doc.liz6.com/systems-programming/05-io-and-file-systems/03-epoll-and-event-driven-programming
locale: zh
area: systems-programming
tags:
- systems-programming
- IO与文件系统
date: 2026-06-30
modified: 2026-06-30
description: '覆盖: epoll_create1/epoll_ctl/epoll_wait → LT vs ET → timerfd/signalfd/eventfd → 与 select/poll 对比 → epoll 内部实现要点 适用: Linux 2.6+'
---

# 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

```c
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 模式正确写法

```c
// 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 伙伴

```c
// 统一事件循环: 把所有异步事件都变成 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 对比

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

## epoll 内部实现

```c
// 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*
