[转载] io_uring 不是一个事件系统
原文出处: https://despairlabs.com/blog/posts/2021-06-16-io-uring-is-not-an-event-system/
作者: Rob Norris
许可协议: 本文为原文的中文翻译,所有权利归原文作者所有。
io_uring 不是一个事件系统
Norris 写道,他听说 io_uring 已经好几年了。这是 Linux 上相对较新的技术,能以极小的开销实现高性能 I/O。
每当有人提到它时,“通常都是把它当作 select、poll 和 epoll 的新替代品来介绍的。“这让他以为它只是那些描述符就绪机制的下一个迭代版本——一种告诉程序文件或 socket 上发生了某事,以便程序做出响应的工具。他认为这只是开销更小但本质相同的增量改进,他觉得这"不错,但很无聊。”
然后他看了 Brendan Gregg 在 LISA21 上的演讲"Computing Performance: On The Horizon”,注意到一张幻灯片,让他觉得 io_uring 可能比他意识到的要更强大:

因为认为它只是一种新的就绪机制,Norris 设计了 yoctochat:一个用不同就绪机制多次实现的最小聊天服务器原型。他构建了 select、poll 和 epoll 版本。由于"本质相同,程序的结构也基本一样。"
完成这些之后,他对这类程序有了清晰的认识,开始着手 yc_uring.c。经过一些试错,他逐渐理解了其中的门道。在做家务时反思他的程序结构时,他恍然大悟:
io_uring 根本不是一个事件系统。io_uring 实际上是一个通用的异步系统调用设施。
那一刻他理解了为什么人们如此兴奋。
经典的 UNIX I/O 系统调用如 read() 是同步且阻塞的——你调用它,你的程序就会休眠,直到请求的操作完成。对于 read() 来说,这意味着"数据已到达"。
明显的困难在于想同时从多个来源 read()。阻塞在一个来源上可能意味着无限期地错过另一个来源的活动。
存在一些笨拙的变通方法——设置闹钟来中断操作,或使用非阻塞模式——但各有缺陷。真正有效的是描述符就绪机制:“当这些描述符中的任何一个有数据可读时唤醒我,然后告诉我哪些是就绪的。“一旦收到通知,你遍历报告的条目,对每个条目调用 read(),知道它们不会阻塞,因为数据已经在等待。
select() 是最初的 UNIX 用于此目的的工具,在小数量描述符时工作良好,但扩展性不佳。poll() 解决了它的一些问题,又引入了另一些问题。大多数类 UNIX 系统都开发了自己的改进——最著名的是 Linux 的 epoll 和 FreeBSD 的 kqueue——这些年来不断扩展,成为首选的工具。但它们在概念上都与旧方法相似:让系统在某事物准备就绪时唤醒进程,以便进程能对每个就绪项采取行动。
io_uring 采取了不同的路线。它重新审视了最初的问题,提出:与其让内核告诉我们何时某事物准备就绪以便我们采取行动,不如告诉内核我们想采取什么行动,当条件成熟时它就会执行。
这几乎总是我们真正想要的。在传统模型中,用户程序调用内核请求就绪通知,然后立即再次调用内核来执行操作。用户程序"在中间什么也不做”,所以如果这两个调用可以合并并一次性提交给内核,用户程序根本不需要参与。
操作很简单:一个调用被拆分为两部分——你发出请求,稍后它完成并取回结果。尽管请求和响应的编码方式不同(调用名称和参数放入你提交的内存缓冲区,而不是实际的函数调用),但效果是一样的——内核被要求代表程序做某事。
该机制使用两个队列:一个用于请求的提交队列,和一个已完成请求及其结果出现的完成队列。
至于"环形缓冲区”(ring buffer)和"uring"这个术语?Norris 认为它们"实际上只是实现细节",这很可能解释了他大部分的困惑:“每篇该死的文章都对着环形缓冲区兴奋不已”,让他以为这是核心所在,而实际上感觉更像是调用一个远程 API。
由于它本质上是进行系统调用的另一种方式,它几乎适用于所有事情,包括文件 I/O——传统上很难异步处理的领域。这意味着它有潜力显著简化许多程序。
Norris 在理解了这一点后印象深刻,并期待在他使用的更多软件中看到它。他希望"它的文档能更好一些",不知道有多少人因为没有探索 io_uring,也是因为他们以为它是别的什么东西。
原文分类:Linux, I/O, io_uring, Kernel
原文发布于 2021 年 6 月 16 日,作者 Rob Norris。