io_uring是Linux 5.1中引入的一套新的syscall接口,用于支持异步IO。近来这套机制颇受关注,很多人认为它代表了与内核实现高性能交互的一种模式。本文将对io_uring的原理和实现进行分析,了解其相对于原有IO机制的优势,并尝试预测其应用场景和发展趋势。
异步IO机制
在介绍io_uring之前,需要先了解一下其实现的异步IO机制,以及io_uring之前的异步IO是如何实现的。
在linux下,目前绝大部分程序中的IO操作都是同步的,最后通过read/write系列的系统调用实现对文件的读写。同步IO的性能受限于文件类型和底层的设备性能,还有可能造成线程阻塞,在要求实时性的场景下显然不能满足需求。为此,出现了多种异步IO的实现机制,来解决实时IO的问题。异步IO是一种IO接口类型,调用这种类型的接口进行IO操作,可以在IO操作实际完成之前就返回,而不会阻塞等待。常见的异步IO实现方式有3种:
- POSIX AIO。这种方案的实现可在linux手册中看到:https://www.man7.org/linux/man-pages/man7/aio.7.html。这套方案在glibc中提供,通过使用pthread库创建用户态多线程的方式实现异步IO的接口,最后调用的IO syscall仍然是内核的同步接口。在手册中写道,“这套机制局限性很大,最显著的一点就是使用多线程实现异步IO的效率和可扩展性太差”。因此,手册中还提到了下一种异步IO方案。
- LINUX AIO。这套方案是linux内核提供的接口,名字也叫AIO。这套方案是io_uring出现前Linux内核支持异步IO的唯一接口,由于glibc中已经提供了同样名为AIO的机制,因此没有为其提供封装,需要通过专门的libaio库进行调用。io_uring的开发者,也是Linux内核IO部分的主要maintainer,Jens Axboe在Efficient IO with io_uring这篇介绍io_uring的文章中分析了Linux AIO的缺陷:AIO最大的局限性是只支持direct IO,这不仅导致程序无法使用cache,更让程序无法使用普通的malloc/new等方式分配的内存用于IO,而必须使用mmap方式直接分配4K对齐的页。在绝大部分情况下,这显然是无法实现的,因为即使开发者在自己的代码中管理内存分配,却无法控制引用的各类库和包的内存管理方式,因此在大部分情况下AIO没有实用价值。此外AIO在某些场景下仍然会退化为阻塞调用,其接口传递的参数过大因此效率也不高。因此linux社区一直认为“这套AIO机制不够成熟,还不能将glibc中的POSIX AIO接口改用这套机制实现”。
- 事实上,当前主流的异步IO模型,大多是程序直接根据程序逻辑实现的。例如异步日志,就是在主要的工作线程中将需要写的日志内容写入一个专门的buffer队列中,再由另一个专门的日志写入线程将队列内容写入文件。也有一些库能够实现这样的功能,例如log4j2。本质上这种实现与POSIX AIO的原理是一致的,但通过结合程序的实现功能需求和配置,就避免了通用的AIO机制带来的过多线程创建管理开销。
IO_URING
现有的内核AIO机制经过多年的开发,仍然存在大量的问题。内核维护者认为已有机制问题太多,使用场景太少,早期设计时没有考虑到异步IO的实际需求,在其基础上开发新特性已经没有意义。因此决定针对AIO机制开发中发现的一系列问题,从头开发一套新的异步IO机制,称作io_uring。根据笔者的理解,uring这个朴实的名字就是user和ring的意思,这两者也是io_uring机制的核心。io_uring的高效性就是建立在使用用户态(user-space)可访问的无锁环形队列(ring)的基础之上的。开发者Jens Axboe在Efficient IO with io_uring中详细介绍了io_uring的设计思想和使用方式,在Linux异步IO新时代:io_uring中对其内容做了概括,【译】高性能异步 IO — io_uring(Effecient IO with io_uring)是其中文翻译(建议参考原文阅读)。
设计目标
作者在文章中明确列出了io_uring的设计目标:
- 易用。从笔者的角度来看,与现有的IO接口相比,io_uring相关syscall接口其实并不算易用,甚至理解起来也不算容易。作者自己也说,这些设计目标之间是有冲突的,特性丰富、高效还可伸缩的接口必然是很难用的。为了解决这个问题,作者为io_uring开发了一个配套的库liburing。既然支持全部需求的接口对于一般开发者来说使用难度过高,那就对其中最常用的部分再封装一层,提供一个更简单易用的接口。使用liburing无法使用io_uring全部的功能,特别是一些为高性能目标设计的功能,但能够使用一套风格与io_uring类似,但简单的多的接口来使用io_uring的基本功能,这对于大部分开发者来说也已经足够了。对于需要高级特性的开发者来说,也可以在使用liburing的基础上调用io_uring syscall接口来获取自己需要的特性,因为这类开发者一般也不会同时需要所有高级特性,而只是使用其中很小的一部分。这个设计方式值得我们学习,如何解决功能强大和接口易用之间的矛盾,“加一个中间层”永远是一个有效的思路。
- 可扩展。这里的可扩展指的是i

本文介绍了Linux 5.1引入的异步IO机制io_uring。先阐述了异步IO机制及之前实现方式的局限,接着分析io_uring的设计目标、原理与结构,包括共享内存、无锁环形队列等,还介绍其调用接口和liburing库。最后探讨了内核实现及风险规避,展示了其高性能交互的新可能。
542

被折叠的 条评论
为什么被折叠?



