epoll
epoll 是 Linux 下的「高性能 IO 多路复用器」,专门用来同时监听大量文件描述符(socket、管道、设备等)的读写事件,不阻塞、CPU 占用极低。
一、为什么要用 epoll?
传统的 select/poll 有致命缺陷:
- 监听上限低(select 最多 1024 个)
- 每次都要遍历所有描述符,连接多了极慢
- 内核到用户空间数据拷贝频繁
总结
- select/poll:每次全量遍历、全量拷贝、效率低、有上限 → 高并发不行
- epoll:内核事件驱动、只返回活跃连接、无上限 → 高并发神器
缺陷2解释
缺陷 1:每次都要把整个 fd 集合从用户空间拷贝到内核
你监听 1000 个 socket:
- 每次调用 select/poll,都要把这 1000 个 fd 发给内核
- 内核遍历一遍
- 再把结果拷贝回来
高并发下,拷贝 + 遍历 非常浪费 CPU。
epoll 不需要拷贝:内核里自己维护一个监听列表,你只需要增删改,不用每次全量重发。
缺陷 2:每次都要遍历所有 fd,不管有没有事件
比如你监听 10000 个连接:
- 只有 1 个 socket 有数据
- select/poll 仍然要把 10000 个全部扫描一遍
连接越多,越慢,O (n) 复杂度。
epoll 只返回有事件的 fd,不用遍历,O (1) 复杂度。
缺陷 3:select 有硬上限 1024
源码里有个宏:
c
运行
#define __FD_SETSIZE 1024意味着一个 select 最多监听 1024 个 socket。想改就要重新编译内核,非常麻烦。
poll 没有上限,但效率问题依然存在。
epoll 无硬上限。
缺陷 4:无法告诉用户 “具体哪个 fd 就绪”
select/poll 返回后,你不知道谁就绪,只能从头到尾循环检查一遍,非常蠢。
epoll 直接返回一个数组,里面只有就绪的 fd
select /poll/epoll 核心对比图

epoll 完美解决:
- 无上限监听
- 只返回有事件的描述符,不用遍历
- 内核与用户空间共享内存,零拷贝
- 支持水平触发 LT + 边缘触发 ET
LT 水平触发:只要缓冲区还有数据,就一直提醒你
ET 边缘触发:只有数据 “刚到来那一刻” 提醒你一次
安卓源码里用哪个
几乎全用 ET(边缘触发)比如:
- InputReader 触摸事件
- Looper 消息循环
- Binder 线程
- 网络框架
原因:减少唤醒次数 → 更少 CPU → 更流畅
二、epoll 3 个核心 API
1. epoll_create1()
创建一个 epoll 实例,返回epoll 文件描述符
// 头文件
#include <sys/epoll.h>
// 创建 epoll 实例(Android 源码标准用法)
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
// EPOLL_CLOEXEC:进程 fork 后自动关闭这个 fd,防止泄露
2. epoll_ctl()
增 / 删 / 改 epoll 监听的文件描述符(核心操作)
int epoll_ctl(
int epfd, // epoll 实例 fd
int op, // 操作类型:ADD(添加)、DEL(删除)、MOD(修改)
int target_fd, // 要监听的目标 fd(socket/管道等)
struct epoll_event *event // 要监听什么事件
);
操作类型:
EPOLL_CTL_ADD:添加监听EPOLL_CTL_DEL:删除监听EPOLL_CTL_MOD:修改监听事件
监听事件(常用):
EPOLLIN:目标 fd 有数据可读EPOLLOUT:目标 fd 可以写数据EPOLLERR:发生错误EPOLLET:边缘触发(高性能模式)
3. epoll_wait()
阻塞等待事件发生,返回有事件的 fd 列表
三、数据结构:struct epoll_event
epoll 用它来描述「要监听什么事件 + 关联数据」
typedef union epoll_data {
void *ptr;
int fd; // 最常用:存目标文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; // 要监听的事件(EPOLLIN等)
epoll_data_t data; // 用户数据(一般存目标fd)
};
四、完整可运行代码
这是Android 源码中标准的 epoll 用法模板
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// 最大同时处理事件数
#define MAX_EVENTS 10
int main() {
// ======================================
// 步骤1:创建 epoll 实例
// ======================================
// EPOLL_CLOEXEC:子进程不继承,防止fd泄露(Android 强制要求)
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
perror("epoll_create1 失败");
exit(1);
}
printf("epoll 实例创建成功,fd = %d\n", epoll_fd);
// ======================================
// 步骤2:创建一个测试用管道(模拟要监听的IO)
// ======================================
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
perror("pipe 创建失败");
exit(1);
}
int read_fd = pipe_fd[0]; // 读端
int write_fd = pipe_fd[1]; // 写端
// ======================================
// 步骤3:配置要监听的事件
// ======================================
struct epoll_event event;
memset(&event, 0, sizeof(event));
// 监听:可读事件 + 边缘触发(ET,Android 高性能首选)
event.events = EPOLLIN | EPOLLET;
// 把要监听的 fd 存进去,epoll_wait 会返回给我们
event.data.fd = read_fd;
// ======================================
// 步骤4:将目标fd添加到epoll监听列表
// ======================================
if (epoll_ctl(
epoll_fd, // epoll 实例
EPOLL_CTL_ADD, // 操作:添加监听
read_fd, // 要监听的fd
&event // 监听事件配置
) == -1) {
perror("epoll_ctl 添加失败");
close(epoll_fd);
exit(1);
}
printf("成功添加 fd %d 到 epoll 监听\n", read_fd);
// ======================================
// 步骤5:往管道写点数据(触发可读事件)
// ======================================
const char *test_data = "hello epoll!";
write(write_fd, test_data, strlen(test_data));
// ======================================
// 步骤6:循环等待事件(Android 源码主循环逻辑)
// ======================================
struct epoll_event events[MAX_EVENTS]; // 存放触发的事件
while (1) {
printf("\n等待事件中...\n");
// 阻塞等待事件,-1=永久等待
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (n == -1) {
perror("epoll_wait 失败");
break;
}
// 遍历所有触发的事件(只遍历有事件的,极快!)
for (int i = 0; i < n; i++) {
int trigger_fd = events[i].data.fd; // 拿到触发的fd
uint32_t trigger_events = events[i].events; // 触发的事件类型
// 判断:是不是可读事件
if (trigger_events & EPOLLIN) {
printf("触发事件:fd %d 有数据可读\n", trigger_fd);
// 读取数据
char buf[1024];
ssize_t len = read(trigger_fd, buf, sizeof(buf)-1);
if (len > 0) {
buf[len] = '\0';
printf("读取到数据:%s\n", buf);
}
}
}
}
// 关闭资源
close(read_fd);
close(write_fd);
close(epoll_fd);
return 0;
}
五、Android 源码里真实的 epoll 用法
1. Android Input 系统(触摸 / 按键)
1. Android Input 系统(触摸 / 按键)
源码路径:frameworks/native/services/inputflinger/InputReader.cpp
// 简化版源码
void InputReader::loopOnce() {
// 用 epoll 监听所有输入设备(触摸屏、按键)
int eventCount = epoll_wait(mEpollFd, mPendingEvents, MAX_EVENTS, -1);
for (int i = 0; i < eventCount; i++) {
int fd = mPendingEvents[i].data.fd;
// 读取触摸/按键事件
InputDevice* device = getDeviceByFd(fd);
device->process();
}
}
2. Android init 进程(系统启动核心)
源码路径:system/core/init/epoll.cpp
// init 进程用 epoll 监听所有子进程、设备、socket
int Epoll::RegisterHandler(int fd, Handler* handler, uint32_t events) {
epoll_event ev = {};
ev.events = events;
ev.data.ptr = handler;
return epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev);
}
六、2 种触发模式(面试 + 源码必问)
1. 水平触发 LT(默认)
- 只要 fd 有数据,一直触发
- 简单、不容易丢数据
- 适合新手
2. 边缘触发 ET(Android 源码首选)
- 只有状态变化时才触发一次(比如从无数据 → 有数据)
- 必须一次性读完所有数据
- 性能极高,Android 系统全用这个
七、epoll 核心流程总结
epoll_create1→ 创建 epoll 实例- 配置
epoll_event→ 监听什么事件 epoll_ctl(ADD)→ 把 fd 加入监听epoll_wait→ 阻塞等事件- 遍历返回的事件 → 处理读写
epoll_ctl(DEL)→ 不需要时删除监听- 关闭 fd → 释放资源
总结
- epoll 是 Android/Linux 高性能 IO 监听核心
- 只靠 3 个 API:
create1→ctl→wait - Android 源码全用边缘触发 (ET)
- 只处理有事件的 fd,不浪费性能
更多推荐

所有评论(0)