io_uring 作为一种新型高性能异步编程框架,代表着 Linux 内核未来的方向,当前仍处于快速发展中。本文先简单整理了各版本内核 io_uring 所支持特性,再介绍 io_uring 社区发展思路及当前社区在推进的几个比较有价值的特性,最后介绍阿里云内核存储团队围绕 io_uring 所开展的社区工作。
各版本内核 io_uring 支持特性列表
以下信息主要参考每个内核版本发布的 Release Notes,相关特性可参考 io_uring 三个系统调用的 man page 以获得更详细的信息。
-
v5.1
首次进入内核主线;
不仅支持异步 direct IO,同时也支持异步 buffered IO;
支持 polling;
多个丰富特性支持。
-
v5.2
支持 eventfd 通知机制;
支持 sync_file_range(2);
支持 drain (IOSQE_IO_DRAIN)。
-
v5.3
支持 recvmsg();
支持 sendmsg();
-
v5.4
支持 IORING_OP_TIMEOUT;
支持同时分配 SQ/CQ 以优化 mmap 系统调用;
支持在单个 single io_uring_enter() 中 SQ poll 唤醒 + 事件获取;
支持 linked drain。
-
v5.5
支持 connect(2);
支持 accept(4) (IORING_OP_ACCEPT);
支持 sparse file sets (IORING_REGISTER_FILES_UPDATE);
支持 overflowed CQ ring;
支持 IORING_OP_ASYNC_CANCEL;
支持取消现有超时的请求(IORING_OP_TIMEOUT_REMOVE);
支持 linked timeouts (IORING_OP_LINK_TIMEOUT);
引入 io_uring 的专有 workqueue io-wq;
支持 NOMMU archs mapping。
-
v5.6
支持 fallocate(2),openat(2),and close(2);
支持普通版本的 read(2)/write(2);
支持 statx(2);
支持 epoll 操作(epoll_ctl(2));
支持 openat2(2);
支持 madvise(2) and fadvise(2);
支持 send(2) and recv(2);
支持查询当前内核版本是否支持给定的 opcode(IORING_REGISTER_PROBE)。
-
v5.7
重构 pollable 异步 IO 的处理机制,不再额外线程来处理(IORING_FEAT_FAST_POLL);
支持 splice(2)。
-
v5.8
支持 tee(2);
支持启动/禁用注册 eventfd 的通知机制;
通过 IORING_SQ_CQ_OVERFLOW 导出 cq overflow 状态到用户态。
-
v5.9
异步读优化,不再依赖额外线程来处理。
支持 EPOLLEXCLUSIVE。
-
v5.10
支持对特定操作的限制(sqe opcode and flags, register opcode),主要用于非信任的场景使用 io_uring 的 ring buffer,如虚拟化的 guest;
支持 sqpoll offload 模式下 blkcg 的记账;
sqpoll 模式支持应用等待内核消费 SQ ring,而不是 busy polling。
Alibaba Cloud Linux 2 io_uring 特性基本上与社区主线 v5.8 版本保持同步,同时包含了我们推送中的特性,并在稳定性上进行了加固。
io_uring 社区开发现状
当前 io_uring 社区开发主要聚焦在以下几个方面。
-
io_uring 框架自身的迭代优化
前面提到,io_uring 作为一种新型高性能异步编程框架,当前仍处于快速发展中。因此随着特性越来越丰富,以及各种稳定性问题的修复等等,代码也变得越来越臃肿。因此 io_uring 框架也需要同步”进化“,不断进行代码重构优化。
通常异步编程一般是将工作交由线程池来做,但对于 io_uring 来说,这只是最坏的 slow path,例如异步读优化,就是想尝试尽量在当前上下文中处理。另外,在 sqpoll 模式下,io_uring 接管用户提交的系统调用,一个系统调用的执行与特定的进程上下文相关,因此 io_uring 需要维护系统调用进程的内存上下文,文件系统上下文等大量信息。同时 io_uring 作为一种框架,框架本身的开销应该尽可能的小,才能与用户态高性能框架 SPDK 对标,因此需要持续优化。
- 特性增强
-
io_uring buffer registration 特性增强
io_uring 的 buffer registration 特性可以用来减少读写操作时频繁的 get_user_pages()/unpin_user_pages() 调用,get_user_pages() 主要用来实现用户态虚拟地址到内核页的转换,会消耗一定的 cpu 资源。来自 Oracle 的同学这组patchset 让 buffer registration 特性支持更新和共享操作,更加方便用户态编程,目前已发到第三版。我们遇到一些业务也明确提出过需要 buffer registration 支持更新操作。
https://lore.kernel.org/io-uring/[email protected]/T/#t -
fs/userfaultfd: support iouring and polling
来自 VMware 的同学这组 patchset 使得 userfaultfd 支持 io_uring,且支持 polling 模式。在RDMA,持久内存等高速场景,系统调用用户态内核态上下文切换的开销在 userfualtfd 整体开销的占比会非常突出,支持 io_uring 后,可以显著减少用户态内核态上下文切换,带来明显的性能提升。
https://lwn.net/Articles/838504/ -
add io_uring with IOPOLL support in scsi layer
io_uring 出现后,Linux 内核才真正完整地支持 iopoll,iopoll 相比于中断模式能带来明显的性能提升。此前只有nvme driver 对 iopoll 提供比较好的支持,现在 scsi 层也开始准备支持 iopoll。
https://patchwork.kernel.org/project/linux-block/patch/[email protected]/#23816967 -
no-copy bvec
在用 io_uring 进行 IO 栈性能分析时,来自 Facebook 的 Pavel Begunkov 发现在 direct IO 场景下是不需要拷贝bvec 结构的,他提交的这组 patchset 可以进一步提高内核 direct IO 的性能。
https://lore.kernel.org/lkml/[email protected]/ -
io_uring and Optane2
block 社区已经开始重视提高 IO 栈性能,一种思路是利用高性能设备,借助 io_uring 压测 IO 栈,从而发现软件性能瓶颈。比如 Intel提供最新的 Gen2 Optane SSD 给 block / io_uring 的维护者 Jens Axboe 来做内核 IO 栈性能分析。
https://lore.kernel.org/io-uring/[email protected]/T/#u -
fs: Support for LOOKUP_NONBLOCK / RESOLVE_NONBLOCK
Jens Axboe 优化 vfs 的文件查询逻辑,从而可以使 io_uring 的 IORING_OP_OPENAT 操作效率更高。
https://lore.kernel.org/linux-fsdevel/[email protected]/T/#m2057d72cb04a5e9e099375f94f26ca186e2d8835
-
-
IO 栈协同优化
借助 io_uirng 这一高性能异步编程框架,能比较容易地发现其他内核子系统的软件性能瓶颈,从而对其进行优化,以提高内核 IO 栈的整体性能。我们之前也基于这个思路发现了 block 层的几处优化,同时提出了 device mapper polling 打通的 RFC。
我们的 Upstream 工作
阿里云基础软件内核存储团队从 2020 年 2 月开始参与 io_uring 社区开发,目前累计贡献 40+ io_uring 内核补丁,10+ liburing 补丁,多项社区工作得到维护者 Jens Axboe的认可。主要涉及: -
io_uring file registration 特性优化
io_uring file registration 特性用来减少文件操作时 fget / fput 原子操作开销。初始 file registration 特性实现在某些场景会导致其并不能减少 fget / fput 原子操作开销,我们采用一种比较巧妙的设计重构优化 io_uring 的 files registration 特性。前面介绍的 Oracle 同学在推进的 io_uring buffer registration 特性增强也用到了我们这种设计思路。
-
多 io_uring 实例共享 sqthread 特性
根据我们在业务场景的实践经验,在 io_uring 社区推进多 io_uring 实例共享同一个 sqthread 特性,在保证性能的前提下优化多实例 sqpoll 模式下的 cpu 开销。经过与社区的多轮讨论,社区最终也意识到该特性的业务价值,并在 5.10 内核版本中提供支持。我们后续又对该特性进行进一步重构优化,进一步改善性能。
-
io_uring_enter() 系统调用支持 timeout
初始的 io_uring 实现通过单独发送一个超时请求系统调用的方式来监测请求的超时情况,此方案在多线程环境使用时性能非常低。我们的方案只需要通过一个系统调用即可,相比于原有实现有着更好的性能,且用户态的编程友好。
-
io_uring 支持 EPOLLEXCLUSIVE 标记
采用该标记,可以有效的防止基于io_uring的网络应用出现惊群效应。
-
稳定性加固
我们主要将 io_uring 用于基于高速块设备的 IO 领域,利用 io_uring 的 sqpoll 和 iopoll 特性来加速内核 IO 性能。在实践过程中我们修复多个严重 BUG,如死锁,IO hang等。
-
内核其他子系统的协同
io_uring 本身只是一个异步编程框架,具体工作还是依赖 IO 栈上其他子系统。我们也积极对内核其他子系统进行优化,以更好的适配 io_uring。如 ext4 支持 iopoll,block split bio 场景 iopoll 协同等。
此外,我们目前还有一些重要工作处于社区推进中:
-
dm 支持 iopoll
与社区有过较多的讨论,涉及面比较广,已发了一版 RFC。
https://lore.kernel.org/linux-block/[email protected]/T/#t -
io_uring percpu sqthread 支持
针对多实例共享 sqthread 的进一步优化。
https://lore.kernel.org/io-uring/[email protected]/T/
OpenAnolis 社区
io_uring 目前仍然处于高速发展中,快速开发节奏较容易引入一些回归。目前上游社区功能回归测试基本依赖 liburing 自带的测试用例集覆盖,但性能回归测试仍然是空白。因此我们为 io_uring 开发了性能测试框架,以及时发现 io_uring 的性能回归。目前该项目已开源到
OpenAnolis 社区高性能存储技术 SIG。
此外,我们也积极探索了 io_uring 在网络应用上的优化,同时也开源到 OpenAnolis 社区,如 echo server benchmark,Redis,Nginx 等。欢迎对 io_uring 技术感兴趣的同学一起加入到 OpenAnolis 社区,围绕 io_uring 打造基于标准 Linux 内核的高性能 IO 栈。