epoll详解
epoll详解
epoll 是 Linux 提供的 I/O 多路复用机制,
用于在一个进程中同时监听多个文件描述符(如 socket),当某些文件描述符发生 I/O 事件时,内核会通知应用程序进行处理。
epoll三个核心函数
int epoll_create(int size);
在内核中创建一个 eventpoll 对象,并返回对应的文件描述符 epfd。
size 参数在早期 Linux 内核中表示:预计要监听的 fd 数量,但在现代 Linux 内核中已经没有实际作用,只要大于 0 即可。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用来管理 epoll 实例里监听的文件描述符。
常见操作类型:
EPOLL_CTL_ADDEPOLL_CTL_MODEPOLL_CTL_DEL
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
/*
调用后执行的操作
1 检查 rdllist
2 如果为空,则把当前线程加入 wq 睡眠
3 如果有 ready 事件,则从 rdllist 取 epitem
4 把事件拷贝到用户态的 events 数组
5 返回实际事件个数
*/
等待事件发生,并把就绪事件拷贝到用户态。
epoll_wait 是否会阻塞,取决与timeout。
timeout = -1,一直阻塞,直到有事件
timeout = 0,不阻塞,立即返回
timeout > 0,最多等 timeout 毫秒
epoll的工作原理

内核部分源码eventpoll结构
struct eventpoll {
/*
* This mutex is used to ensure that files are not removed
* while epoll is using them. This is held during the event
* collection loop, the file cleanup path, the epoll file exit
* code and the ctl operations.
*/
struct mutex mtx;
/* Wait queue used by sys_epoll_wait() */
wait_queue_head_t wq;
/* Wait queue used by file->poll() */
wait_queue_head_t poll_wait;
/* List of ready file descriptors */
struct list_head rdllist;
/* Lock which protects rdllist and ovflist */
spinlock_t lock;
/* RB tree root used to store monitored fd structs */
struct rb_root_cached rbr;
/*
* This is a single linked list that chains all the "struct epitem" that
* happened while transferring ready events to userspace w/out
* holding ->lock.
*/
struct epitem *ovflist;
/* wakeup_source used when ep_send_events or __ep_eventpoll_poll is running */
struct wakeup_source *ws;
/* The user that created the eventpoll descriptor */
struct user_struct *user;
struct file *file;
/* used to optimize loop detection check */
u64 gen;
struct hlist_head refs;
u8 loop_check_depth;
/*
* usage count, used together with epitem->dying to
* orchestrate the disposal of this struct
*/
refcount_t refcount;
#ifdef CONFIG_NET_RX_BUSY_POLL
/* used to track busy poll napi_id */
unsigned int napi_id;
/* busy poll timeout */
u32 busy_poll_usecs;
/* busy poll packet budget */
u16 busy_poll_budget;
bool prefer_busy_poll;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/* tracks wakeup nests for lockdep validation */
u8 nests;
#endif
};
epitem结构:epoll内核中的节点结构,将epoll实例和一个监听fd进行绑定,并记录该fd的事件信息。
struct epitem {
union {
/* RB tree node links this structure to the eventpoll RB tree */
struct rb_node rbn;
/* Used to free the struct epitem */
struct rcu_head rcu;
};
/* List header used to link this structure to the eventpoll ready list */
struct list_head rdllink;
/*
* Works together "struct eventpoll"->ovflist in keeping the
* single linked chain of items.
*/
struct epitem *next;
/* The file descriptor information this item refers to */
struct epoll_filefd ffd;
/*
* Protected by file->f_lock, true for to-be-released epitem already
* removed from the "struct file" items list; together with
* eventpoll->refcount orchestrates "struct eventpoll" disposal
*/
bool dying;
/* List containing poll wait queues */
struct eppoll_entry *pwqlist;
/* The "container" of this item */
struct eventpoll *ep;
/* List header used to link this item to the "struct file" items list */
struct hlist_node fllink;
/* wakeup_source used when EPOLLWAKEUP is set */
struct wakeup_source __rcu *ws;
/* The structure that describe the interested events and the source fd */
struct epoll_event event;
};
epoll_event结构:用来描述事件的结构体,包括事件的类型和连接信息。
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED;
简单示例
/*
回声服务器
*/
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#define MAX_EVENTS 100
#define PORT 8888
int main(){
// 1 创建 socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
bind(listenfd, (sockaddr*)&addr, sizeof(addr));
listen(listenfd, 5);
// 2 创建 epoll
int epfd = epoll_create(1);
epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
while (1){
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++){
int fd = events[i].data.fd;
// 新连接
if (fd == listenfd){
int connfd = accept(listenfd, NULL, NULL);
ev.events = EPOLLIN;
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else{
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
if (len <= 0){
close(fd);
}
else{
write(fd, buf, len); // 回显
}
}
}
}
}
常见面试问题
1、select、poll、epoll的区别
监听文件描述符的限制:
- select有上限值,默认是1024
- poll 没有固定上限,理论上只受系统打开文件描述符数量限制
- epoll 没有固定上限,同样理论上只受系统打开文件描述符数量限制
底层的数据结构不同:
- select 是基于位图
- poll 是基于数组
- epoll底层是红黑树加一个双向链表
遍历fd的不同:
- select 不会通知我们哪些fd上有事件发生,返回后需要遍历所有监听的 fd,通过 FD_ISSET 判断哪些 fd 就绪
- poll 同样不会通知我们哪些fd上有事件发生,返回后同样需要遍历整个 pollfd 数组
- epoll会告诉我们哪些fd上有事件发生,只返回就绪的fd,不需要遍历全部监听对象
2、epoll比poll更快吗
不一定,在并发量不高的场景下,
poll 遍历一个结构体数组未必满(O(n)),
在高并发场景下 epoll 性能明显更好,因为 epoll 只返回就绪的 fd,而 poll 需要遍历所有 fd。具体多高的连接算高并发,只能仁者见仁智者见智了
3、epoll 的底层为什么使用的是红黑树,而不是hash表或者B+树
因为 epoll 底层管理监听 fd 的核心需求需要满足:
高效插入
高效删除
高效查找
有序稳定
内核实现成熟
最坏情况可控
红黑树在内核中实现成熟,性能稳定,提供 O(log n) 的插入、删除和查找性能。
Hash 表虽然平均查找快(O(1)),但存在哈希冲突和扩容带来的最坏情况退化为(O(n))问题,
B+ 树更适合面向磁盘或大规模范围查询场景
4、epoll的ET和LT模式
主要区别是事件通知方式不同。
ET:边缘触发,只有状态发生变化时才通知(无数据->有数据),只触发 一次事件。ET 模式必须配合非阻塞,因为必须一次性把数据读完。
LT:水平触发,默认模式,只要 fd 上还有数据,就会一直通知。
5、为什么ET必须配合非阻塞
读数据时,可能一直阻塞在 read,后面的连接和事件就处理不了了,而 ET 又只通知一次,所以必须循环读,直到返回 EAGAIN。
6、epoll中的惊群问题是什么
如果多个线程/进程同时阻塞在同一个 epoll 实例上,一个事件到来时,可能把多个等待者一起唤醒,这就叫 惊群。
大家都去抢同一个fd,产生大量无效唤醒和上下文切换。
常见解决方法包括:让一个线程专门负责 epoll_wait、使用线程池分发任务、在监听 socket 上使用 EPOLLEXCLUSIVE,以及配合 EPOLLONESHOT 避免同一连接
被多个线程重复处理。
T必须配合非阻塞
读数据时,可能一直阻塞在 read,后面的连接和事件就处理不了了,而 ET 又只通知一次,所以必须循环读,直到返回 EAGAIN。
6、epoll中的惊群问题是什么
如果多个线程/进程同时阻塞在同一个 epoll 实例上,一个事件到来时,可能把多个等待者一起唤醒,这就叫 惊群。
大家都去抢同一个fd,产生大量无效唤醒和上下文切换。
常见解决方法包括:让一个线程专门负责 epoll_wait、使用线程池分发任务、在监听 socket 上使用 EPOLLEXCLUSIVE,以及配合 EPOLLONESHOT 避免同一连接
被多个线程重复处理。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)