文章摘要
文章讲述了作者带领学生开发反向代理服务器TinyGate的经历,从最初简单版本到基于epoll的重写,最终转向io_uring的完整过程,并对比了这两种Linux异步I/O机制的性能差异。
文章总结
好的,这是根据您的要求,对原文进行中文重述和精简后的版本:
标题:Linux 中的 epoll 与 io_uring 对比
核心内容:
本文作者通过开发反向代理服务器 TinyGate 的经历,引出了对 Linux 异步 I/O 机制的探讨。最初版本的 TinyGate 性能不佳,促使作者研究如何优化。他们首先采用了 epoll 重写了第二版,性能虽有大幅提升,但仍不及 nginx 和 haproxy。最终,他们转向了 io_uring,并为此再次重写了整个项目。
epoll 的局限:
epoll 是一种“就绪通知”模型,它只告诉应用程序 I/O 操作何时可以进行。应用程序在收到通知后,仍需自行调用 read() 或 write() 系统调用来执行实际 I/O。这意味着每个 I/O 事件至少需要两次系统调用(epoll_wait 和 read/write),再加上一次性的 epoll_ctl 注册。每次系统调用都会引发用户态与内核态的上下文切换,在处理大量连接时会产生巨大的开销。
iouring 的优势:
iouring 是一种“完成通知”模型,它直接告诉应用程序 I/O 操作何时已经完成。它通过在内核和应用程序之间共享内存(环形缓冲区)来提交请求和获取结果。默认情况下,仍需调用 io_uring_enter() 来通知内核处理提交队列,但一次调用可以批量提交多个操作并批量获取完成结果,而不是像 epoll 那样每个操作都需要一对系统调用。如果启用 IORING_SETUP_SQPOLL 模式,内核会创建一个专用线程来轮询提交队列,在稳定状态下几乎可以实现零系统调用(代价是消耗 CPU 资源)。
架构对比:
* epoll:通知 I/O 就绪,每个 I/O 操作都需要跨越内核边界。
* io_uring:通知 I/O 完成,只需支付一次性的设置成本和按批次调用的 io_uring_enter() 成本,而非按操作计费。在处理大量 I/O 时,能显著减少系统调用次数。
代码示例对比:
* epoll 示例:需要 epoll_create、epoll_ctl(注册)、epoll_wait(等待事件)和 read(读取数据)等步骤。
* io_uring 示例:需要 io_uring_queue_init(初始化)、io_uring_get_sqe(获取提交队列条目)、io_uring_prep_read(准备读取操作)、io_uring_submit(提交)和 io_uring_wait_cqe(等待完成)等步骤。代码更简洁,无需单独的注册和就绪检查步骤。
io_uring 的额外特性:
* 零拷贝:通过 io_uring_register_buffers() 预注册缓冲区,可避免内核重复映射内存。对于网络发送,IORING_OP_SEND_ZC(内核 6.0+)可完全跳过数据拷贝。
* SQPOLL 的 CPU 消耗:即使队列为空,IORING_SETUP_SQPOLL 模式下的内核线程也会持续轮询,消耗 CPU。虽然有闲置超时机制,但并非零成本。
* 异步错误处理:错误会作为完成队列条目(cqe)的 res 字段异步返回,需要异步处理。
总结: iouring 是现代 Linux 环境下异步 I/O 的新标准。对于在支持 iouring 的系统上从零开始的新项目,作者强烈推荐使用 io_uring,而不是 epoll。作者认为,在合理的情况下,应尽快放弃对旧系统的支持。
评论总结
根据评论内容,总结如下:
主要观点一:iouring性能优势与安全风险并存 - 支持者认为iouring比epoll快约20%("I think I had like 20% faster req/s with iouring") - 但存在安全顾虑:内核与用户态直接内存共享导致漏洞频发("multiple exploits that hit iouring in recent times") - 因此高性能项目(如Go)未将其设为默认选项("don't really bake io_uring in as a sane default")
主要观点二:基准测试的局限性 - 评论者指出仅关注基准测试无法反映复杂系统的全貌("takes a very benchmark focus...only says part of the story") - 其他平台(如Windows)早有类似接口,但Linux的I/O系统并不因此更差("does not make Linux's I/O system worse") - 正确实现的服务器无论使用多路复用还是异步API,性能通常都很快("fast in either multiplexing or async API if implemented correctly")
主要观点三:性能优化建议 - 建议使用CPU绑定(cpu pinning)和监听套接字绑定(SOINCOMINGCPU)提升性能("cpu pin your threads and cpu pin your listen sockets") - 推荐使用零拷贝和内存对齐工具:concurrencykit、mimalloc("zero-copy and mem aligned reverse proxy") - 可结合eBPF/XDP实现DDoS防护和L4功能("add a DDoS protection and more advanced L4 stuff")
主要观点四:对项目架构的批评 - 评论者指出项目未使用CPU绑定技术("doesn't look like you're doing anything with cpu pinning") - 建议通过流量引导(traffic steering)和源端口选择优化后端通信("manage source port selection to your backend") - 核心目标是避免跨CPU通信("handle packets without any cross cpu communication")