
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- 前言
- [1 ~> epoll 整体定位与诞生背景](#1 ~> epoll 整体定位与诞生背景)
-
- [1.1 两个问题引入epoll](#1.1 两个问题引入epoll)
- [1.2 前代方案的核心瓶颈](#1.2 前代方案的核心瓶颈)
- [1.3 epoll 技术定位](#1.3 epoll 技术定位)
- [1.4 内核版本说明](#1.4 内核版本说明)
- [2 ~> epoll 三大核心系统调用](#2 ~> epoll 三大核心系统调用)
-
- [2.1 epoll_create:创建 epoll 实例](#2.1 epoll_create:创建 epoll 实例)
-
- [2.1.1 函数原型](#2.1.1 函数原型)
- [2.1.2 参数与返回值](#2.1.2 参数与返回值)
- [2.1.3 功能本质](#2.1.3 功能本质)
- [2.2 epoll_ctl:管控监听事件(增/删/改)](#2.2 epoll_ctl:管控监听事件(增/删/改))
-
- [2.2.1 函数原型](#2.2.1 函数原型)
- [2.2.2 参数详解](#2.2.2 参数详解)
- [2.2.3 返回值](#2.2.3 返回值)
- [2.2.4 功能本质](#2.2.4 功能本质)
- [2.3 epoll_wait:等待并获取就绪事件](#2.3 epoll_wait:等待并获取就绪事件)
-
- [2.3.1 函数原型](#2.3.1 函数原型)
- [2.3.2 参数详解](#2.3.2 参数详解)
- [2.3.3 返回值](#2.3.3 返回值)
- [2.3.4 功能本质](#2.3.4 功能本质)
- [2.4 epoll操作函数知识图谱](#2.4 epoll操作函数知识图谱)
- [3 ~> 核心数据结构与事件宏](#3 ~> 核心数据结构与事件宏)
-
- [3.1 struct epoll_event 事件结构体](#3.1 struct epoll_event 事件结构体)
-
- [3.1.1 结构体完整定义](#3.1.1 结构体完整定义)
- [3.1.2 字段说明](#3.1.2 字段说明)
- [3.2 标准事件宏(比特位标识)](#3.2 标准事件宏(比特位标识))
-
- [3.2.1 基础读写事件](#3.2.1 基础读写事件)
- [3.2.2 异常事件](#3.2.2 异常事件)
- [3.2.3 模式控制标志](#3.2.3 模式控制标志)
- [3.3 ~> 内核核心结构体(底层原理)](#3.3 ~> 内核核心结构体(底层原理))
-
- [3.3.1 struct eventpoll](#3.3.1 struct eventpoll)
- [3.3.2 struct epitem](#3.3.2 struct epitem)
- [3.4 epoll的结构相关知识图谱](#3.4 epoll的结构相关知识图谱)
-
- [3.4.1 结构](#3.4.1 结构)
- [3.4.2 细节](#3.4.2 细节)
- [4 ~> epoll 内核完整工作原理](#4 ~> epoll 内核完整工作原理)
-
- [4.1 核心组件分工](#4.1 核心组件分工)
- [4.2 完整工作流程](#4.2 完整工作流程)
- [4.3 与 select/poll 本质差异](#4.3 与 select/poll 本质差异)
- [4.4 epoll函数的二次理解](#4.4 epoll函数的二次理解)
- [4.5 epoll工作原理知识图谱](#4.5 epoll工作原理知识图谱)
- [5 ~> epoll 基础服务端代码实战(Version1版本)](#5 ~> epoll 基础服务端代码实战(Version1版本))
-
- [5.1 工程依赖说明](#5.1 工程依赖说明)
- [5.2 服务端类实现(EpollServer.hpp)](#5.2 服务端类实现(EpollServer.hpp))
- [5.3 主函数(Main.cc)](#5.3 主函数(Main.cc))
- [5.4 编译脚本(Makefile)](#5.4 编译脚本(Makefile))
- [5.5 代码逻辑与现存缺陷](#5.5 代码逻辑与现存缺陷)
-
- [5.5.1 执行流程](#5.5.1 执行流程)
- [5.5.2 已知缺陷](#5.5.2 已知缺陷)
- [5.5.3 运行测试](#5.5.3 运行测试)
- [5.6 Version1版本代码知识图谱](#5.6 Version1版本代码知识图谱)
- [6 ~> 重难点与面试高频考点总结](#6 ~> 重难点与面试高频考点总结)
-
- [6.1 基础概念考点](#6.1 基础概念考点)
- [6.2 模式核心考点](#6.2 模式核心考点)
- [6.3 代码实操考点](#6.3 代码实操考点)
- [6.4 性能对比面试题](#6.4 性能对比面试题)
- [6.5 底层原理延伸考点](#6.5 底层原理延伸考点)
- 结尾

前言
一、 开头部分
文章导入语
在 Linux IO 多路转接技术体系中,select 与 poll 受限于内核轮询遍历、数据拷贝、文件描述符集合维护等性能瓶颈,无法支撑海量并发场景。epoll 作为 Linux 内核专属的高性能多路转接方案,在 2.5.44 版本内核正式推出,重构了事件监听与就绪通知的底层逻辑,依托红黑树、就绪队列与内核回调机制,彻底解决了前代方案的性能缺陷,成为 Linux 平台高并发网络服务的首选技术。本文将从诞生背景、核心 API、内核底层原理、事件模式、完整代码实战、问题分析等维度,系统性梳理 epoll 全套知识,串联理论与工程落地,构建完整的知识闭环。
文章框架思维导图
bash
epoll 高性能多路转接 知识总览
├─ 基础认知
│ ├─ 诞生背景:解决 select/poll 遍历、拷贝、性能短板
│ ├─ 技术定位:Linux 专属多路转接、事件通知机制
│ └─ 内核版本:Linux 2.5.44 引入,2.6 版本成熟
├─ 三大核心系统调用
│ ├─ epoll_create:创建 epoll 实例、epoll 句柄
│ ├─ epoll_ctl:对 fd 执行增/删/改监听事件
│ └─ epoll_wait:阻塞等待就绪事件,获取结果
├─ 核心数据结构
│ ├─ struct epoll_event:事件描述、联合体存储fd/自定义数据
│ ├─ 事件宏:EPOLLIN / EPOLLOUT / EPOLLERR / EPOLLHUP 等
│ └─ 内核结构体:eventpoll、epitem(红黑树节点+就绪队列节点)
├─ 内核底层原理
│ ├─ 核心组件:红黑树(托管监听fd)、就绪队列(存储就绪节点)
│ ├─ 回调机制:套接字底层回调触发节点入就绪队列
│ └─ 整体工作流程:创建实例 → 注册监听 → 等待就绪 → 事件处理
├─ epoll 两种工作模式
│ ├─ LT 水平触发:默认模式,数据未读完持续通知
│ └─ ET 边缘触发:仅状态变化时通知,需非阻塞IO配合
├─ 特殊事件标志
│ ├─ EPOLLET:开启边缘触发
│ └─ EPOLLONESHOT:单次事件触发,触发后失效
├─ 代码实战(基础epoll服务端)
│ ├─ 工程结构与基础组件依赖
│ ├─ 完整 C++ 服务端代码实现
│ ├─ 编译脚本与运行测试
│ └─ 代码缺陷与优化方向
├─ 技术对比
│ └─ epoll VS select / poll:性能、数据结构、工作逻辑差异
└─ 重难点 & 面试高频考点
1 ~> epoll 整体定位与诞生背景
1.1 两个问题引入epoll

1.2 前代方案的核心瓶颈
select 与 poll 是传统 IO 多路转接实现,二者存在共性性能问题,也是 epoll 诞生的核心动因:
- 内核全量遍历:无论文件描述符是否就绪,内核都需要遍历整个监听集合检测事件,文件描述符数量越多,遍历耗时越高。
- 用户态与内核态数据拷贝:每次系统调用都需要将完整的监听集合在用户态和内核态之间拷贝,海量 fd 场景下开销剧增。
- 参数维护成本 :
select需要反复重置 fd 集合;poll虽解耦入参出参,但仍无法规避遍历与拷贝问题。
1.3 epoll 技术定位
epoll 是 Linux 内核专属、专为大批量文件描述符 设计的 IO 多路转接方案,本质依旧是事件等待 + 就绪通知 机制。它并非简单改造 poll,而是重构了底层数据结构与事件触发逻辑,是 Linux 2.6 及以上版本中综合性能最优的多路转接实现。
1.4 内核版本说明
epoll 首次随 Linux 2.5.44 内核发布,在 Linux 2.6 系列内核完成功能迭代与稳定性优化,目前所有主流 Linux 发行版均完整支持该技术。
2 ~> epoll 三大核心系统调用
epoll 拆分了监听注册、事件等待两类行为,提供三组独立系统调用,分别负责创建实例、管控监听 fd、等待就绪事件。所有接口依赖头文件 <sys/epoll.h>。
2.1 epoll_create:创建 epoll 实例
2.1.1 函数原型
c
#include <sys/epoll.h>
int epoll_create(int size);
2.1.2 参数与返回值
- 参数 size :早期内核用于提示内核预期监听的文件描述符总数,用于预分配内存。自 Linux 2.6.8 起,该参数被内核忽略,但传参必须保证大于 0。内核可动态扩容红黑树,不再依赖该数值限制容量。
- 返回值 :调用成功返回一个非负整数,即 epoll 句柄(文件描述符) ;调用失败返回
-1,并设置errno。 - 补充说明 :Linux 后续推出
epoll_create1(int flags),废弃了size参数,推荐在新项目中使用。
2.1.3 功能本质
调用该函数后,内核会创建一个独立的 epoll 实例,内部初始化红黑树 (存储所有被监听的文件描述符)与双向就绪队列(存储已触发事件的节点)。由于 Linux 一切皆文件,epoll 实例也以文件描述符的形式对外暴露。
2.2 epoll_ctl:管控监听事件(增/删/改)
2.2.1 函数原型
c
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
2.2.2 参数详解
- epfd :由
epoll_create返回的 epoll 句柄,指定操作目标 epoll 实例。 - op :操作类型,支持三种枚举值:
EPOLL_CTL_ADD:向 epoll 实例中新增待监听的文件描述符与对应事件。EPOLL_CTL_DEL:从 epoll 实例中删除指定文件描述符,停止监听。EPOLL_CTL_MOD:修改已注册文件描述符的监听事件类型。
- fd:需要被管控的目标文件描述符(套接字、管道等)。
- event :
struct epoll_event结构体指针,描述需要监听的事件与用户自定义数据。
2.2.3 返回值
调用成功返回 0,失败返回 -1 并设置错误码。
2.2.4 功能本质
该函数是用户态与内核红黑树的交互入口,完成对监听 fd 的增删改操作,同时会修改套接字底层的回调函数指针,为后续事件自动触发做准备。
2.3 epoll_wait:等待并获取就绪事件
2.3.1 函数原型
c
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
2.3.2 参数详解
- epfd:epoll 实例句柄。
- events :用户态数组首地址,输出型参数。内核会将就绪的事件与 fd 信息填充到此数组中。
- maxevents :
events数组的最大元素个数,限制单次获取的事件数量,防止内存越界。 - timeout :超时时间,单位为毫秒,行为规则与
poll完全一致:timeout < 0(常用-1):永久阻塞,直到有事件触发才返回。timeout = 0:非阻塞,立即检测并返回结果。timeout > 0:限时阻塞,等待指定毫秒数,超时无事件则返回。
2.3.3 返回值
- 返回值
> 0:实际就绪的事件数量,取值范围[1, maxevents]。 - 返回值
= 0:等待超时,无任何事件就绪。 - 返回值
< 0:调用出错。
2.3.4 功能本质
该函数仅检测内核的就绪队列 :若队列非空,内核将队列中的就绪节点拷贝到用户态数组并返回;若队列为空,则根据 timeout 规则阻塞进程。检测队列的时间复杂度为 O(1)。
2.4 epoll操作函数知识图谱

3 ~> 核心数据结构与事件宏
3.1 struct epoll_event 事件结构体
该结构体用于定义监听事件、存储用户数据,是 epoll 三大接口的核心载体。
3.1.1 结构体完整定义
c
#include <sys/epoll.h>
// 联合体:四选一存储自定义数据
union epoll_data {
void *ptr; // 通用指针,可挂载自定义对象
int fd; // 常用:存储文件描述符
uint32_t u32; // 32位无符号整型
uint64_t u64; // 64位无符号整型
};
typedef union epoll_data epoll_data_t;
// epoll 事件主体结构
struct epoll_event {
uint32_t events; // 事件位图,使用比特位标识监听/就绪事件
epoll_data_t data;// 用户数据,内核不会修改此字段
};
3.1.2 字段说明
- events:比特位图,组合多个事件宏,作为入参时表示用户需要监听的事件,作为出参时表示内核返回的就绪事件。
- data :联合体类型,内核全程不会篡改该字段。工程中最常用
data.fd存储当前关联的文件描述符,也可通过ptr挂载业务对象实现面向对象编程。
3.2 标准事件宏(比特位标识)
所有事件宏均为无符号整型比特位,通过位运算组合使用。分为基础读写事件、异常事件、工作模式标志三大类。
3.2.1 基础读写事件
| 宏定义 | 含义 | 使用场景 |
|---|---|---|
| EPOLLIN | 读事件就绪 | fd 缓冲区有数据可读、TCP 对端正常关闭连接 |
| EPOLLOUT | 写事件就绪 | fd 发送缓冲区有空闲空间,可写入数据 |
| EPOLLPRI | 高优先级数据可读 | 接收 TCP 带外数据,工程极少使用 |
3.2.2 异常事件
这类事件无需用户主动在 events 中注册,内核会自动监听并在事件触发时返回:
- EPOLLERR:文件描述符发生底层错误。
- EPOLLHUP:连接挂起,如管道对端关闭、TCP 连接异常断开。
3.2.3 模式控制标志
用于修改 epoll 的工作逻辑,通过位或 | 与基础事件组合使用:
- EPOLLET:开启**边缘触发(ET)**模式,epoll 默认是水平触发(LT)。
- EPOLLONESHOT :单次触发模式。事件触发后该 fd 自动失效,如需继续监听必须调用
EPOLL_CTL_MOD重新配置。
3.3 ~> 内核核心结构体(底层原理)
3.3.1 struct eventpoll
epoll 实例的顶层内核结构体,一个 epoll_create 调用对应一个该结构体实例:
c
struct eventpoll {
spinlock_t lock; // 自旋锁,保护并发访问
struct mutex mtx; // 互斥锁,保护增删改操作
wait_queue_head_t wq; // epoll_wait 等待队列
struct list_head rdllist; // 就绪队列(双向链表,存储就绪节点)
struct rb_root rbr; // 红黑树根节点(存储所有监听fd节点)
struct epitem *ovflist; // 溢出链表,临时存储迁移中的节点
struct user *user; // 所属用户信息
};
3.3.2 struct epitem
红黑树与就绪队列的共用节点,一个被监听的 fd 对应一个 epitem 对象,同一节点可同时挂载在红黑树与就绪队列:
c
struct epitem {
struct rb_node rbn; // 红黑树节点
struct list_head rdllink; // 就绪队列链表节点
struct epoll_filefd ffd; // 关联的文件描述符
struct epoll_event event; // 绑定的事件信息
struct eventpoll *ep; // 归属的 epoll 实例
// 其他辅助队列、引用计数等字段
};
3.4 epoll的结构相关知识图谱
epoll的结构分成下面两部分来阐述:

3.4.1 结构

3.4.2 细节

4 ~> epoll 内核完整工作原理
4.1 核心组件分工
- 红黑树 :由
eventpoll->rbr指向,存储所有用户通过epoll_ctl注册的文件描述符与监听事件。红黑树作为平衡二叉搜索树,基于 fd 作为键值,保证 fd 增、删、查操作时间复杂度为 O(logN)。 - 就绪队列 :由
eventpoll->rdllist指向,双向链表结构。当 fd 事件触发时,对应的epitem节点会被自动移入该队列。epoll_wait仅遍历该队列,无全局遍历开销。 - 回调机制 :Linux 套接字结构体
struct sock中预置sk_data_ready、sk_write_space等回调函数指针。当网卡收到数据、缓冲区状态变化时,内核硬件中断会触发套接字回调,将对应epitem节点加入就绪队列。
4.2 完整工作流程
- 创建实例 :调用
epoll_create,内核分配eventpoll结构体,初始化空红黑树与空就绪队列,返回 epoll 句柄。 - 注册监听 :调用
epoll_ctl(EPOLL_CTL_ADD),内核创建epitem节点插入红黑树,同时设置套接字回调函数。 - 事件触发 :网卡接收数据/缓冲区状态变化,触发套接字回调,将
epitem节点移入就绪队列。 - 等待事件 :调用
epoll_wait,内核检测就绪队列。队列非空则拷贝事件到用户态数组并返回;队列为空则阻塞进程。 - 事件处理:用户遍历返回的事件数组,执行业务读写逻辑。
- 销毁监听 :fd 关闭前,调用
epoll_ctl(EPOLL_CTL_DEL)从红黑树中删除节点,释放内核资源。
4.3 与 select/poll 本质差异
- 数据结构:select/poll 使用数组/位图,epoll 使用红黑树+就绪队列。
- 事件检测:select/poll 内核全局遍历所有 fd;epoll 仅检测就绪队列,由回调主动推送就绪事件。
- 数据拷贝:select/poll 每次调用全量拷贝监听集合;epoll 仅拷贝少量就绪事件,拷贝开销极低。
4.4 epoll函数的二次理解

4.5 epoll工作原理知识图谱

5 ~> epoll 基础服务端代码实战(Version1版本)
本节基于 C++ 实现LT 模式、仅监听读事件 的 epoll TCP 服务端,代码复用通用套接字、日志组件,完全基于前文的 select/poll 代码架构改造,同时修正原始代码中的语法错误与逻辑漏洞。
5.1 工程依赖说明
项目依赖公共基础组件:Socket.hpp(TCP 套接字封装)、Logger.hpp(日志工具)、InetAddr.hpp(网络地址封装)、Mutex.hpp(互斥锁),下文仅展示核心业务代码、主函数与编译脚本。
5.2 服务端类实现(EpollServer.hpp)
cpp
#pragma once
#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include <cstring>
#include <unistd.h>
#include "Socket.hpp"
#include "Logger.hpp"
#include "InetAddr.hpp"
// 单次最大接收事件数
static const int gmax_events = 128;
// 默认服务端口
static const uint16_t gdefault_port = 8080;
class EpollServer
{
public:
// 构造函数:初始化监听套接字、创建epoll实例
EpollServer(uint16_t port = gdefault_port)
: _port(port),
_listensockfd(std::make_unique<TcpSocket>())
{
// 1. 创建、绑定、监听套接字
_listensockfd->BuildSocketMethod(_port);
// 2. 创建epoll实例,size传任意正数即可
_epfd = epoll_create(10);
if (_epfd < 0)
{
LOG(LogLevel::FATAL) << "epoll_create failed";
exit(EXIT_FAILURE);
}
LOG(LogLevel::INFO) << "listen fd: " << _listensockfd->Socketfd()
<< " , epoll fd: " << _epfd;
}
// 析构函数:关闭epoll句柄
~EpollServer()
{
if (_epfd >= 0)
{
close(_epfd);
}
}
// 事件派发主入口
void Dispatcher()
{
// 1. 初始化事件结构体,注册监听套接字到epoll
struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN; // 监听读事件
ev.data.fd = _listensockfd->Socketfd();
// 添加监听套接字
int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensockfd->Socketfd(), &ev);
if (ret != 0)
{
LOG(LogLevel::FATAL) << "epoll_ctl add listen fd failed";
exit(EXIT_FAILURE);
}
LOG(LogLevel::DEBUG) << "add listen fd to epoll success";
// 就绪事件存储数组
struct epoll_event rev_events[gmax_events];
int timeout = -1; // 永久阻塞,生产环境推荐
// 事件循环
while (true)
{
// 等待就绪事件
int ready_num = epoll_wait(_epfd, rev_events, gmax_events, timeout);
if (ready_num == 0)
{
LOG(LogLevel::INFO) << "epoll wait timeout";
continue;
}
else if (ready_num < 0)
{
LOG(LogLevel::ERROR) << "epoll_wait error";
break;
}
// 处理所有就绪事件
EventHandler(rev_events, ready_num);
}
}
private:
// 统一事件处理器
void EventHandler(struct epoll_event* revs, int ready_num)
{
for (int i = 0; i < ready_num; ++i)
{
uint32_t events = revs[i].events;
int fd = revs[i].data.fd;
// 读事件就绪
if (events & EPOLLIN)
{
// 监听套接字:新连接到来
if (fd == _listensockfd->Socketfd())
{
Accepter();
}
// 普通客户端套接字:数据可读
else
{
IOHandler(fd);
}
}
}
}
// 接收新客户端连接
void Accepter()
{
InetAddr client_addr;
// 接受连接,epoll已检测就绪,不会阻塞
int client_fd = _listensockfd->Accepter(&client_addr);
if (client_fd < 0)
{
LOG(LogLevel::ERROR) << "accept new client failed";
return;
}
LOG(LogLevel::INFO) << "new client connected, fd: " << client_fd
<< " , addr: " << client_addr.StringAddress();
// 将新客户端fd注册到epoll,监听读事件
struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN;
ev.data.fd = client_fd;
int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, client_fd, &ev);
if (ret != 0)
{
LOG(LogLevel::ERROR) << "add client fd to epoll failed";
close(client_fd);
return;
}
}
// 客户端IO读写处理
void IOHandler(int fd)
{
char buffer[1024] = {0};
ssize_t n = recv(fd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
// 读取到有效数据,回显内容
LOG(LogLevel::INFO) << "client data: " << buffer;
std::string echo = "echo: " + std::string(buffer);
send(fd, echo.c_str(), echo.size(), 0);
}
else if (n == 0)
{
// 客户端正常断开连接
LOG(LogLevel::INFO) << "client disconnect, fd: " << fd;
// 先从epoll删除监听,再关闭fd(fd必须合法才能删除)
epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
else
{
// 读写异常
LOG(LogLevel::WARNING) << "recv error, fd: " << fd;
epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
}
private:
uint16_t _port;
int _epfd; // epoll实例句柄
std::unique_ptr<TcpSocket> _listensockfd; // 监听套接字
};
5.3 主函数(Main.cc)
cpp
#include "EpollServer.hpp"
#include <memory>
int main()
{
// 创建并启动epoll服务端
std::unique_ptr<EpollServer> svr = std::make_unique<EpollServer>();
svr->Dispatcher();
return 0;
}
5.4 编译脚本(Makefile)
bash
# 编译目标
epoll_server: Main.cc
g++ -o $@ $^ -std=c++17
# 清理规则
.PHONY: clean
clean:
rm -f epoll_server
5.5 代码逻辑与现存缺陷
5.5.1 执行流程
- 初始化阶段:创建监听套接字、epoll 实例,将监听 fd 注册到 epoll。
- 事件循环:循环调用
epoll_wait等待就绪事件。 - 事件分发:区分监听 fd(接收新连接)与客户端 fd(读写数据)。
- 连接销毁:客户端断开/异常时,先从 epoll 移除 fd,再关闭文件描述符。
5.5.2 已知缺陷
- 仅实现读事件监听,未处理
EPOLLOUT写事件。 - 基于字节流 TCP,未做报文分包处理,存在粘包问题。
- 当前为 LT 模式,未实现 ET 边缘触发,不支持极限高并发。
- 固定事件数组大小,未实现动态扩容。
5.5.3 运行测试
timeout = -1:永久阻塞,无事件时进程休眠,CPU 占用为 0。timeout = 0:非阻塞轮询,持续循环检测,CPU 占用极高。timeout = 2000:2 秒限时阻塞,超时打印日志。
5.6 Version1版本代码知识图谱

6 ~> 重难点与面试高频考点总结
6.1 基础概念考点
- epoll 三大函数分工 :
epoll_create创建实例、epoll_ctl管控监听 fd、epoll_wait等待就绪事件。 - size 参数演变 :Linux 2.6.8 之后
epoll_create的 size 参数失效,仅要求传正数。 - 内核组件:红黑树存储监听 fd,就绪队列存储就绪节点,回调函数实现事件主动推送。
- 事件宏区分 :
EPOLLERR、EPOLLHUP无需手动注册,内核自动监听。
6.2 模式核心考点
- LT 与 ET 区别:LT 数据未读完持续通知;ET 仅状态变化时通知一次,ET 必须搭配非阻塞 IO。
- EPOLLONESHOT 作用:单次触发,常用于多线程模型,避免同一 fd 被多线程同时处理。
- select/poll 与 epoll 模式对应:二者仅支持 LT 水平触发。
6.3 代码实操考点
- fd 销毁顺序 :调用
EPOLL_CTL_DEL时要求 fd 必须合法,因此先删监听、后关闭 fd。 - ET 模式编码要求:循环读写直到缓冲区为空/写满,必须使用非阻塞文件描述符。
- epoll_data 联合体用法 :优先使用
data.fd存储 fd,data.ptr用于挂载自定义业务对象。
6.4 性能对比面试题
- epoll 性能优势根源:无全局遍历、仅拷贝就绪事件、内核回调主动推送,海量 fd 场景下性能碾压 select/poll。
- epoll 并非全场景最优:当监听 fd 数量极少、连接活跃度极高时,select/poll 与 epoll 性能差距极小。
- 内核数据结构优势 :红黑树保证增删查
O(logN),就绪队列保证事件检测O(1)。
6.5 底层原理延伸考点
- 节点复用 :
epitem节点同时挂载红黑树与就绪队列,无需重复创建对象。 - 回调触发链路 :网卡中断 → 套接字回调函数 → 节点加入就绪队列 →
epoll_wait唤醒进程。 - epoll 句柄本质 :Linux 下的普通文件描述符,使用完毕必须手动
close释放资源。
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!"
"技术之路难免有困惑,但同行的人会让前进更有方向。" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
