从Linux源码角度看Epoll,透过现象看本质

💡 原文中文,约15700字,阅读约需38分钟。
📝

内容提要

epoll是一种高效的文件描述符监听机制,相比于select和poll系统调用,epoll在性能上有所提升。epoll通过一次性将所有文件描述符传入内核,然后等待事件发生,避免了重复拷贝的过程。在等待事件发生时,通过唤醒回调机制将产生事件的文件描述符放入一个链表中,然后返回这个链表上的文件描述符。epoll还实现了自己独特的文件系统事件轮询机制。

🔎

延伸解读

epoll 高效的核心:避免重复拷贝与回调唤醒

文章指出,select 和 poll 每次调用都需要将所有文件描述符复制到内核空间,且内核不保存这些描述符,导致效率低下。epoll 通过 epoll_ctl 一次性将文件描述符传入内核并保存,epoll_wait 时无需重复拷贝。同时,epoll 采用唤醒回调机制,在设备等待队列唤醒时调用回调函数,将就绪的文件描述符放入链表,而不是轮询所有描述符。这种设计减少了数据拷贝和遍历开销,是 epoll 性能优势的关键。

内核数据结构:eventpoll 与 epitem 的分工

epoll 在内核中主要维护两个数据结构:eventpoll 和 epitem。eventpoll 代表一个 epoll 实例,包含自旋锁、互斥锁、等待队列、就绪链表和红黑树根等。epitem 则对应每个被监听的文件描述符,包含红黑树节点、就绪链表节点、等待队列链表以及感兴趣的事件等。红黑树用于快速查找和插入文件描述符,就绪链表则存放已触发事件的文件描述符。这种分工使得 epoll 能高效管理大量文件描述符。

从 epoll_ctl 到回调:事件注册与触发的流程

当调用 epoll_ctl 添加文件描述符时,内核会分配并初始化一个 epitem,然后通过文件操作中的 poll 函数注册回调。具体来说,ep_insert 中调用 f_op->poll,进而触发 ep_ptable_queue_proc,将 ep_poll_callback 回调函数挂载到目标文件的等待队列上。当设备有事件发生时,硬件中断会唤醒等待队列,从而调用 ep_poll_callback,将对应的 epitem 加入就绪链表并唤醒等待的进程。这一流程实现了事件驱动的通知机制。

epoll_wait 的等待与超时处理

epoll_wait 最终调用 ep_poll 函数。如果就绪链表为空,当前进程会加入 eventpoll 的等待队列并进入睡眠,直到被 ep_poll_callback 唤醒或超时。ep_poll 中通过 schedule_timeout 实现超时等待,并处理信号中断。当有事件就绪时,ep_send_events 将就绪事件复制到用户空间。如果复制过程中没有获取到事件且超时未到,会重新尝试。这种机制确保了 epoll_wait 能高效地返回就绪事件,同时支持超时和信号处理。

❓

Q&A

epoll与select和poll相比有什么优势?

epoll在性能上优于select和poll,能够一次性将所有文件描述符传入内核,避免重复拷贝,提高效率。

epoll是如何处理文件描述符的事件的?

epoll通过唤醒回调机制,将产生事件的文件描述符放入链表中,并在epoll_wait时返回这些文件描述符。

epoll的初始化过程包括哪些步骤?

epoll初始化时会进行数据结构的初始化,包括锁机制和内存分配,确保安全的事件轮询。

如何使用epoll_create函数?

epoll_create函数用于创建每个eventpoll实例,分配内存并初始化相关数据结构。

epoll_ctl函数的作用是什么?

epoll_ctl函数用于添加、修改或删除文件描述符的监听,管理epoll实例中的文件描述符。

epoll_wait函数是如何工作的?

epoll_wait函数用于等待事件的发生,并返回就绪的文件描述符,支持超时机制。

🏷️

标签

➡️

继续阅读