io_uring
是 Linux 提供的一个异步 I/O 接口。io_uring
在 2019 年加入 Linux 内核,经过了两年的发展,现在已经变得非常强大。本文基于 Linux 5.12.10 介绍 io_uring
接口。
io_uring
的实现主要在 fs/io_uring.c 中。
io_uring 的 用户态 API
io_uring
的实现仅仅使用了三个 syscall:io_uring_setup
, io_uring_enter
和 io_uring_register
。它们分别用于设置 io_uring
上下文,提交并获取完成任务,以及注册内核用户共享的缓冲区。使用前两个 syscall 已经足够使用 io_uring
接口了。
用户和内核通过提交队列和完成队列进行任务的提交和收割。后文中会出现大量的简写,在这里先做一些介绍。
缩略语 | 英语 | 中文 | 解析 |
---|---|---|---|
SQ | Submission Queue | 提交队列 | 一整块连续的内存空间存储的环形队列。 用于存放将执行操作的数据。 |
CQ | Completion Queue | 完成队列 | 一整块连续的内存空间存储的环形队列。 用于存放完成操作返回的结果。 |
SQE | Submission Queue Entry | 提交队列项 | 提交队列中的一项。 |
CQE | Completion Queue Entry | 完成队列项 | 完成队列中的一项。 |
Ring | Ring | 环 | 比如 SQ Ring,就是“提交队列信息”的意思。 包含队列数据、队列大小、丢失项等等信息。 |
初始化 io_uring
long io_uring_setup(u32 entries, struct io_uring_params __user *params)
用户通过调用 io_uring_setup
1 初始化一个新的 io_uring
上下文。该函数返回一个 file descriptor,并将 io_uring
支持的功能、以及各个数据结构在 fd
中的偏移量存入 params
。用户根据偏移量将 fd
映射到内存 (mmap) 后即可获得一块内核用户共享的内存区域。这块内存区域中,有 io_uring
的上下文信息:提交队列信息 (SQ_RING
) 和完成队列信息 (CQ_RING
);还有一块专门用来存放提交队列元素的区域 (SQEs)。SQ_RING
中只存储 SQE 在 SQEs 区域中的序号,CQ_RING
存储完整的任务完成数据。2
在 Linux 5.12 中,SQE 大小为 64B,CQE 大小为 16B。因此,相同数量的 SQE 和 CQE 所需要的空间不一样。初始化 io_uring
时,用户如果不在 params
中设置 CQ 长度,内核会分配 entries
个 SQE,以及 entries * 2
个 CQE。
io_uring_setup
设计的巧妙之处在于,内核通过一块和用户共享的内存区域进行消息的传递。在创建上下文后,任务提交、任务收割等操作都通过这块共享的内存区域进行,在 IO_SQPOLL
模式下(后文将详细介绍),可以完全绕过 Linux 的 syscall 机制完成需要内核介入的操作(比如读写文件)&#