diff --git a/io_uring/文章/下一代异步 IO io_uring 技术解密.md b/io_uring/文章/下一代异步 IO io_uring 技术解密.md new file mode 100644 index 0000000..30eff7a --- /dev/null +++ b/io_uring/文章/下一代异步 IO io_uring 技术解密.md @@ -0,0 +1,186 @@ +## 概述 + +Alibaba Cloud Linux 2 是阿里云操作系统团队基于开源 Linux 4.19 LTS 版本打造的一款针对云应用场景的下一代 Linux OS 发行版。在首次推出一年后,阿里云操作系统团队对外正式发布了Alibaba Cloud Linux 2 LTS 版本。LTS 版本的发布是一个重要的里程碑,标志着阿里云操作系统团队将为 Alibaba Cloud Linux 2 提供长期技术支持、稳定的更新和更好的服务,为 Alibaba Cloud Linux 2 的客户提供更多保障。 + +上一篇我们在 Alibaba Cloud Linux 2 上对比测试了 io_uring 与 libaio 以及 SPDK,可以看到 io_uring 带来的性能提升非常明显。这篇文章我们详细分析下 io_uring 的原理,以及我们在 io_uring 社区所做的工作。 + +## io_uring 原理介绍 + +为了从根本上解决当前 Linux aio 存在的问题和约束,io_uring 从零开始全新设计的了异步 IO 框架。其设计的主要目标如下: + +- 简单易用,方便应用集成。 +- 可扩展,不仅仅为 block IO 使用,同样可以用于网络 IO。 +- 特性丰富,满足所有应用,如 buffer io。 +- 高效,尤其是针对大部分场景的 512 字节或 4K IO。 +- 可伸缩,满足峰值场景的性能需要。 + +io_uring 为了避免在提交和完成事件中的内存拷贝,设计了一对共享的 ring buffer 用于应用和内核之间的通信。其中,针对提交队列(SQ),应用是 IO 提交的生产者(producer),内核是消费者(consumer);反过来,针对完成队列(CQ),内核是完成事件的生产者,应用是消费者。 + +![img](https://kernel.taobao.org/2020/08/Introduction_to_IO_uring/1.png) + +共享环的设计主要带来以下 3 个好处: + +- 提交、完成请求时节省应用和内核之间的内存拷贝; +- 使用 SQPOLL 高级特性时,应用程序无需调用系统调用; +- 无锁操作,用 memory ordering 实现同步。 + +### io_uring 系统调用 + +io_uring 一共提供了 3 个系统调用:io_uring_setup(),io_uring_enter(),以及io_uring_register(),位于 fs/io_uring.c。 + +``` +/** * io_uring_setup - setup a context for performing asynchronous I/O */ +int io_uring_setup(u32 entries, struct io_uring_params *p); +/** * io_uring_enter - initiate and/or complete asynchronous I/O */ +int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete, + unsigned int flags, sigset_t *sig) + +/** * io_uring_register - register files or user buffers for asynchronous I/O */ +int io_uring_register(int fd, unsigned int opcode, void *arg, + unsigned int nr_args) +``` + +Alibaba Cloud Linux 2 LTS 版本支持的异步操作如下,更多的特性支持持续完善中。 + +- IORING_OP_NOP 仅产生一个完成事件,除此之外没有任何操作。 +- IORING_OP_READV / IORING_OP_WRITEV 提交 readv() / writev() 操作,大多数场景最核心的操作。 +- IORING_OP_READ_FIXED / IORING_OP_WRITE_FIXED 使用已注册的 buffer 来提交 IO 操作,由于这些 buffer 已经完成映射,可以降低系统调用的开销。 +- IORING_OP_FSYNC 下发 fsync() 调用。 +- IORING_OP_POLL_ADD / IORING_OP_POLL_REMOVE 使用 IORING_OP_POLL_ADD 可对一组文件描述符 (file descriptors) 执行 poll() 操作;可以使用 IORING_OP_POLL_REMOVE 显式地取消 poll()。这种方式可以用来异步地监控一组文件描述符。 +- IORING_OP_SYNC_FILE_RANGE 执行 sync_file_range() 调用,是对 fsync() 的一个增强。 +- IORING_OP_SENDMSG / IORING_OP_RECVMSG 在 sendmsg() 和 recvmsg() 基础上,提供异步收发网络包功能。 +- IORING_OP_TIMEOUT 用户态程序等待 IO 完成事件时,可以通过 IORING_OP_TIMEOUT 设置一个超时时间,类似 io_getevents(2) 的 timeout 机制。 + +### io_uring 用户态库 liburing + +为了简化使用,原作者 Jens 开发了一套 liburing 库,用户无需了解诸多 io_uring 细节便可以使用起来,如无需关心 memory barrier,以及 ring buffer 的管理等。相关接口在头文件 /usr/include/liburing.h 中定义。 + +Alibaba Cloud Linux 2 LTS 提供了 liburing 和 liburing-devel 包供用户安装。 + +``` +sodo yum install liburing liburing-devel +``` + +基于 liburing 的一个 helloworld 示例如下: + +``` +#include #include #include #include #include #define ENTRIES 4 int main(int argc, char *argv[]) +{ + struct io_uring ring; + struct io_uring_sqe *sqe; + struct io_uring_cqe *cqe; + struct iovec iov = { + .iov_base = "Hello World", + .iov_len = strlen("Hello World"), + }; + int fd, ret; + if (argc != 2) { + printf("%s: \n", argv[0]); + return 1; + } + /* setup io_uring and do mmap */ + ret = io_uring_queue_init(ENTRIES, &ring, 0); + if (ret < 0) { + printf("io_uring_queue_init: %s\n", strerror(-ret)); + return 1; + } + fd = open("testfile", O_WRONLY | O_CREAT); + if (fd < 0) { + printf("open failed\n"); + ret = 1; + goto exit; + } + /* get an sqe and fill in a WRITEV operation */ + sqe = io_uring_get_sqe(&ring); + if (!sqe) { + printf("io_uring_get_sqe failed\n"); + ret = 1; + goto out; + } + io_uring_prep_writev(sqe, fd, &iov, 1, 0); + /* tell the kernel we have an sqe ready for consumption */ + ret = io_uring_submit(&ring); + if (ret < 0) { + printf("io_uring_submit: %s\n", strerror(-ret)); + goto out; + } + /* wait for the sqe to complete */ + ret = io_uring_wait_cqe(&ring, &cqe); + if (ret < 0) { + printf("io_uring_wait_cqe: %s\n", strerror(-ret)); + goto out; + } + /* read and process cqe event */ + io_uring_cqe_seen(&ring, cqe); +out: + close(fd); +exit: + /* tear down */ + io_uring_queue_exit(&ring); + return ret; +} +``` + +更多的示例可参考: + +https://github.com/axboe/liburing/tree/master/examples/ https://github.com/axboe/liburing/tree/master/test + +## io_uring 高级特性 + +### Polled IO + +IORING_SETUP_IOPOLL,与非 polling 模式等待硬件中断唤醒不同,内核将采用 polling 模式不断轮询硬件以确认 IO 请求是否已经完成,这在追求低延时和高 IOPS 的应用场景非常有用。 + +### Kernel Side Polling + +IORING_SETUP_SQPOLL,当前应用更新 SQ ring 并填充一个新的 sqe,内核线程 sqthread 会自动完成提交,这样应用无需每次调用 io_uring_enter() 系统调用来提交 IO。应用可通过 IORING_SETUP_SQ_AFF 和 sq_thread_cpu 绑定特定的 CPU。 同时,为了节省无 IO 场景的 CPU 开销,该内核线程会在一段时间空闲后自动睡眠。应用在下发新的 IO 时,通过 IORING_ENTER_SQ_WAKEUP 唤醒该内核线程,该操作在 liburing 中都已封装完成。 + +### Fixed Files + +IORING_REGISTER_FILES / IORING_REGISTER_FILES_UPDATE / IORING_UNREGISTER_FILES,通过 io_uring_register() 系统调用提前注册一组 file,缓解每次 IO 操作因 fget() / fput() 带来的开销。 + +### Fixed Buffers + +IORING_REGISTER_BUFFERS / IORING_UNREGISTER_BUFFERS,通过 io_uring_register() 系统调用注册一组固定的 IO buffers,当应用重用这些 IO buffers 时,只需要 map / unmap 一次即可,而不是每次 IO 都要去做,减少get_user_pages() / put_page() 带来的开销。 + +### Linked SQE + +IOSQE_IO_LINK,建立 sqe 序列之间的关联,这在诸如 copy 之类的操作中非常有用。使用 linked sqe 后,copy 操作的写请求链接在读请求之后,应用程序无需等待读请求数据返回后再下发写请求,而是共享了同一个 buffer,避免了上下文切换的开销。 + +## 社区工作 + +阿里云操作系统团队在 backport io_uring 特性到 Alibaba Cloud Linux 2 的过程中,进一步优化性能,并加固 io_uring 的稳定性,相关工作以补丁的形式回馈到社区。 + +### 性能优化 + +- engines/io_uring: delete fio_option_is_set() calls when submitting sqes fio io_uring 提交 IO 性能提升 30%。 +- __io_uring_get_cqe: eliminate unnecessary io_uring_enter() syscalls 在某些场景下,减少 50% 的 io_uring_enter() 系统调用开销。 +- ext4: start to support iopoll method +- io_uring: io_uring_enter(2) don’t poll while SETUP_IOPOLL|SETUP_SQPOLL enabled 能带来 13% 的性能提升,同时减少 20% 的 CPU 开销。 + +### 代码优化和特性重构 + +- io_uring: cleanup io_alloc_async_ctx() +- io_uring: refactor file register/unregister/update handling 重构 file register/unregister/update 特性,能更好地处理大量文件场景。 +- io_uring: do not always copy iovec in io_req_map_rw() +- io_uring: avoid whole io_wq_work copy for requests completed inline +- io_uring: avoid unnecessary io_wq_work copy for fast poll feature +- e697deed834d io_uring: check file O_NONBLOCK state for accept + +### BugFix + +- io_uring: fix __io_iopoll_check deadlock in io_sq_thread +- io_uring: fix poll_list race for SETUP_IOPOLL|SETUP_SQPOLL +- io_uring: restore req->work when canceling poll request +- io_uring: only restore req->work for req that needs do completion +- io_uring: use cond_resched() in io_ring_ctx_wait_and_kill() +- io_uring: fix mismatched finish_wait() calls in io_uring_cancel_files() +- io_uring: handle -EFAULT properly in io_uring_setup() +- io_uring: reset -EBUSY error when io sq thread is waken up +- io_uring: don’t submit sqes when ctx->refs is dying +- io_uring: fix io_kiocb.flags modification race in IOPOLL mode +- io_uring: don’t fail links for EAGAIN error in IOPOLL mode +- io_uring: add memory barrier to synchronize io_kiocb’s result and iopoll_completed +- io_uring: fix possible race condition against REQ_F_NEED_CLEANUP + +> 原文链接:https://kernel.taobao.org/2020/08/Introduction_to_IO_uring/