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

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

源码锚点

路径 作用
fs/eventpoll.c do_epoll_create、do_epoll_ctl、ep_poll、ep_send_events
include/uapi/linux/eventpoll.h epoll_event、EPOLLIN/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                                    # 连接规模旁证

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

5. 设计意图与踩坑

  • 意图:把「谁就绪」从 O(n) 扫描变成就绪队列消费。
  • 坑 :epoll_wait 的 maxevents 过小,一轮取不完,逻辑上要循环到取尽或配合 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 -n 与 max_user_watches,避免 EMFILE/监控项耗尽
  • 使用 EPOLLONESHOT 时有明确的重新 MOD 路径
相关推荐
香蕉大香蕉(06敬畏市场)4 分钟前
MySQL 单点详解(03)
java·数据库·mysql·面试·八股·大厂实习
赤生云电竞5 分钟前
云游戏边缘节点部署:机房选址与网络拓扑优化实践——接入延迟、VXLAN与GPU虚拟化选型
数据库·游戏
lazy H15 分钟前
【计算机网络】第 15 篇:Java BIO、NIO 与高并发网络通信——从 Socket 理解 I/O 模型
网络·后端·tcp/ip·计算机网络·nio·408
垆边人似月.31 分钟前
RDMA 文件传输踩坑(四):从固定延时改造为 READY/ACK 协议
linux·服务器·网络
Y001 小时前
PostgreSQL 权限管理踩坑记:从 42501 到默认权限和 RLS
数据库·postgresql
乐维_lwops1 小时前
汽车集团网管平台建设案例:乐维网管工具实现跨区域网络统一监控与链路可视化
网络·网管平台·it监控·链路·统一监控
呆萌很1 小时前
达梦(DM8)数据库常用SQL
数据库·sql
跟太阳说早安2 小时前
极简数据库SQL
数据库·sql
前端 贾公子2 小时前
第 5 章:Codex 从可运行程序到可交付工具
java·前端·数据库
杨云龙UP2 小时前
MySQL 主从复制管理:暂停、恢复、清理主从关系及命令执行位置
运维·数据库·mysql·主从配置·主从命令·reset replica·清除复制