epoll 源码路径:从epoll_ctl 到ep_poll 的就绪唤醒

select 在高连接数下 CPU 飙高、或 epoll_wait 空转/漏事件:需要看清内核 就绪链表与回调 ,而不是只背 API。本文沿 epoll_create1epoll_ctl(ADD) → 文件就绪回调 → ep_poll/epoll_wait 走通 fs/eventpoll.c 主路径,便于用 strace/perf 验证。

源码锚点

路径 作用
fs/eventpoll.c do_epoll_createdo_epoll_ctlep_pollep_send_events
include/uapi/linux/eventpoll.h epoll_eventEPOLLIN/EPOLLET 等用户 UAPI
include/linux/eventpoll.h 内核侧声明
fs/select.c 经典 select/poll 实现(对比用)
net/ipv4/tcp.c socket 通过 ->poll / wait queue 上报就绪

核心用户结构:

c 复制代码
/* include/uapi/linux/eventpoll.h */
struct epoll_event {
	__poll_t events;
	__u64 data;
};

内核侧(概念):每个 epoll 实例有红黑树存监控项(epitem),就绪队列串起「已就绪」项;目标文件通过 epoll 回调挂到其 wait queue。

c 复制代码
/* 用户态典型调用 */
int epfd = epoll_create1(EPOLL_CLOEXEC);
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
n = epoll_wait(epfd, events, maxevents, timeout);

调用链

text 复制代码
epoll_create1(flags)
  → do_epoll_create
      → 分配 eventpoll 对象,匿名 inode + file

epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event)
  → do_epoll_ctl
      → 红黑树插入 epitem(key=fd 对应的 file)
      → ep_insert
          → 对目标 file 调用 f_op->poll,挂 epoll 回调
          → 若已就绪 → 链入 rdllist(就绪链表)

目标 fd 数据到达 / 可写
  → sock 等子系统 wake_up
      → epoll 回调 ep_poll_callback
          → epitem 进入就绪链表,唤醒正在 ep_poll 等待的进程

epoll_wait(epfd, ...)
  → do_epoll_wait → ep_poll
      → 若就绪链表空则睡眠至超时/唤醒
      → ep_send_events 拷贝到用户 events[]

水平触发(默认)vs 边沿触发(EPOLLET):

text 复制代码
LT:只要条件仍满足,每次 wait 都可再次报到
ET:仅状态变化时报到;必须一次读到 EAGAIN,否则可能饿死

重点知识

1. 为何 epoll 能撑高并发

select/poll 每次把整张 fd 集合从用户拷进内核并线性扫描;epoll 用红黑树索引监控集,就绪用链表,热路径只处理就绪子集 。连接数上万时差异明显。代价是:每 fd 需先 epoll_ctl,且语义(尤其 ET)要正确。

2. data 字段是用户私有,内核不解释

epoll_event.data 常存 fd 或对象指针。关闭 fd 前必须 EPOLL_CTL_DEL(或依赖 DUP/CLOEXEC 生命周期约定),否则可能出现 野事件(fd 号复用后事件张冠李戴)。

3. ET + 非阻塞是标配

边沿触发下,若 read 未读到 EAGAIN 就去 epoll_wait,剩余数据可能不再通知。生产网络库普遍:SOCK_NONBLOCK + EPOLLET + 循环读到 EAGAIN。LT 更易用,但高活跃 fd 会重复报到。

4. 配置与观测

bash 复制代码
# 系统调用轨迹
strace -e epoll_create1,epoll_ctl,epoll_wait ./your_server
# 句柄与上限
ulimit -n
cat /proc/sys/fs/epoll/max_user_watches   # 监控项上限(若存在)
# 性能
perf record -e syscalls:sys_enter_epoll_wait -a -- sleep 5
ss -s                                    # 连接规模旁证

对比实验:同连接数下分别用 pollepoll 看 CPU;或故意 ET 不读干,观察是否「假死」。

5. 设计意图与踩坑

  • 意图:把「谁就绪」从 O(n) 扫描变成就绪队列消费。
  • epoll_waitmaxevents 过小,一轮取不完,逻辑上要循环到取尽或配合 LT。
  • :把阻塞 fd 塞进 epoll 却假设一定非阻塞 → read 卡死工作线程。
  • EPOLLONESHOT 用后忘记 EPOLL_CTL_MOD 重新武装。
  • :在多线程间乱共享同一 epfd 无同步(内核允许,但用户状态机要自己保证)。

Checklist

  • 能指出 do_epoll_ctl / ep_poll 位于 fs/eventpoll.c,并口述就绪唤醒链
  • 说明 LT 与 ET 差异,并在 ET 下写到「读至 EAGAIN」的循环
  • strace 看到 create/ctl/wait;关闭连接路径有 DEL 或等价生命周期管理
  • 对比理解 select/poll 全量扫描与 epoll 就绪链表的复杂度差异
  • 检查 ulimit -nmax_user_watches,避免 EMFILE/监控项耗尽
  • 使用 EPOLLONESHOT 时有明确的重新 MOD 路径
相关推荐
文人sec10 小时前
grant 之后要跟着 flush privileges 吗?要不要使用分区表?
数据库·mysql·adb·数据分析
Omics Pro10 小时前
上海AI Lab孙思琦×高张阳:虚拟细胞代码库智能体
数据库·人工智能·算法·机器学习·自然语言处理
旺仔不是程序员10 小时前
相关子查询与性能优化:PostgreSQL 逐行执行的代价与 JOIN 重写
数据库·后端·sql
YangYang9YangYan11 小时前
2026 电商数据运营校招 JD 梳理|岗位任务、技能与面试考点汇总
大数据·数据库·数据分析
Sun 328511 小时前
读懂集中式主备的集群状态与节点角色
数据库·opengauss·集群·节点角色·磐维数据库·运行状态
dog25011 小时前
写一个时延固定的拥塞控制
网络
努力努力再努力wz11 小时前
【Redis进阶系列】:从主从复制到 Sentinel,一文建立故障检测、Leader 选举与 Failover 的完整心智模型
数据库·redis·缓存
酣大智11 小时前
华为 VRRP的抢占延迟时间
网络·tcp/ip·vrrp
达梦数据11 小时前
DMDRS搭建部署简介与运行环境
数据库
Omics Pro11 小时前
1个月2轮融资!长寿虚拟细胞
数据库·人工智能·算法·机器学习·自然语言处理