
Epoll
• 1. 基本定义与地位
◦ 同步 IO 多路复用模型:虽然常用于异步编程,但本质上仍属于同步模型,。
◦ 事件通知机制:一种高效的内核事件处理机制,用于管理大规模的网络连接(如百万级并发)。
◦ 现状:目前在网络编程中应用最为广泛,是 Rust 异步编程底层的重要支撑。
• 2. 三大核心函数 (API)
◦ epoll_create:在内核中创建一个 epoll 实例(数据结构),并返回一个文件描述符 (FD)。
◦ epoll_ctl:管理 socket。用于向 epoll 实例中添加、修改或删除需要监听的 socket 事件。
◦ epoll_wait:获取就绪事件。等待并返回已经发生(就绪)的事件列表。
• 3. 内核底层数据结构
◦ 红黑树 (Red-Black Tree):在内核缓冲区维护,用于高效管理(添加、删除、查找)被监听的 socket,这是支持万级连接的关键。
◦ 就绪双向链表:当事件发生(如数据到达)时,内核通过中断处理程序将对应 socket 的引用放入此链表,epoll_wait 直接从中读取,无需遍历所有连接。
• 4. 触发机制 (Trigger Modes)
◦ 水平触发 (Level Triggered, LT):
▪ 默认模式,select 也仅支持此模式。
▪ 只要缓冲区内有数据,就会持续触发通知。
◦ 边缘触发 (Edge Triggered, ET):
▪ 仅在缓冲区状态发生变化(从空到有,或从满到不满)时触发。
▪ 优势:减少了频繁的读写通知,性能更高,Nginx 默认使用此方式。
• 5. 关键特性与问题解决
◦ 惊群效应 (Thundering Herd):
▪ 现象:一个事件发生时唤醒所有等待线程,但只有一个能处理。
▪ 解决:内核 4.5 版本以后引入了标识位,确保只有一个线程被唤醒。
io_using
1. 核心设计:双环形缓冲区
io_uring 的高性能源于其独特的架构设计,它在用户态和内核态之间建立了两个共享的环形缓冲区 (Circular Buffers):
• 提交队列 (Submission Queue, SQ):应用程序将 IO 请求(如读、写)放入此队列。
• 完成队列 (Completion Queue, CQ):内核处理完 IO 请求后,将结果放入此队列。
• 共享内存机制:通过 mmap 系统调用,用户态和内核态可以直接访问这两块内存,减少了频繁的系统调用开销,极大提升了效率。
2. 真正的异步体验
与 epoll 等同步 IO 多路复用模型不同,io_uring 实现了全阶段的异步:
• 涵盖 IO 的两个阶段:在同步模型中,虽然数据准备阶段可以是非阻塞的,但“将数据从内核拷贝到用户空间”的第二阶段永远是阻塞的。而在 io_uring 中,数据准备和数据拷贝这两个阶段全部由内核完成。
• 立即返回:应用程序发起系统调用后可以立即返回去处理其他任务,完全不参与数据拷贝的阻塞过程。
• 通知机制:只有当整个 IO 操作(包括拷贝)彻底完成后,内核才会通知用户进程。
3. 主要优势与特性
• 完全无阻塞 (No-blocking):整个过程处于无阻塞状态,是目前设计最优秀的异步 IO 模型。
• “零拷贝”感官:由于采用了共享内存的设计,它在实际运作中实现了类似“零拷贝”的高效性能。
• 解决 AIO 痛点:早期的 Linux AIO 接口在支持的文件类型和性能上存在局限,io_uring 作为继任者,提供了更通用且强大的异步能力。
4. 在 Rust 异步编程中的地位
• 底层支撑:虽然目前 Rust 异步编程中使用最广泛的底层模型仍然是 epoll,但 Rust 社区正在积极推进对 io_uring 的支持。
• 自下而上的理解:理解 io_uring 有助于开发者看清异步编程模型(如 Rust 的 async/await)是如何作用于底层 IO 硬件和内核之上的。
比喻理解: 如果说 Epoll 是一个会发短信通知你“快递到了,快来取”的管家,那么 io_uring 就是一个直接把快递拆开并摆放到你书架上的超级助理。你只需要在桌上留一张“买书清单”(提交队列),然后继续去写代码;等助理干完活,他会在你的备忘录上打个勾(完成队列)。你全程不需要去门口搬箱子,也不需要检查物流,只需要在想看书的时候直接从书架上拿即可。