关注了就能看到更多这么棒的文章哦~
The rapid growth of io_uring
By Jonathan Corbet
January 24, 2020
一年前的时候,io_uring子系统在mainline kernel里面还根本不存在,是在2019年5月的时候第一次出现在5.1版本中。从根本上来讲,io_uring是一个用来做异步I/O(asynchronous I/O)的机制,不过慢慢地开始有了更多的应用场景,也增加了更强的能力。这里我们介绍一下io_uring的当前状况、发展方向,以及这个过程中出现的一两个有趣的问题。
传统的Unix I/O天生就是同步操作。从application的角度来看,一次I/O操作在read() / write()系统调用返回的时候就完成了,哪怕其实底层可能还在做一些处理,application并不需要关心这一点了。也没有什么方法来发起一个异步操作并等待(wait)后续的completion,这种操作其实在Unix出现之前,已经有其他一些操作系统支持多年了。
在Linux里面,自从有了asynchronous I/O (AIO) subsystem,算是补上了这个缺陷,不过这个方案大家一直没有完全满意。AIO需要在底层有一些支持,因此在一些核心的应用场景里(direct file I/O和networking)无法得到很好的应用。在过去几年里不断有讨论如何更好的解决asynchronous-I/O的问题。有很多种方案被提出来,例如fibrils, threadlets, syslets, acall, work-queue-based AIO等,不过都没能合入mainline。
最近一次尝试就是io_uring,它终于被合入了mainline。Io_uring跟此前的方案不同,它是基于user space和kernel之间共享的ring buffer来实现的,这样一来发起操作和收集结果的时候,很多情况下并不需要调用系统调用进入kernel了。这个接口看起来有点复杂,不过对大多数需要进行大量I/O操作的应用程序来说,是愿意为了提升性能而接受这个复杂性的。可以查看此pdf ( https://kernel.dk/io_uring.pdf ) 来了解io_uring的API描述。如果使用liburing库的话,可以简化使用的API。
What io_uring can do
提交到io_uring submission ring里的每个entry都包含了一个opcode(操作代码)用来告知kernel需要做什么。当io_uring加入5.1 kernel的时候,可以使用的opcodes如下:
IORING_OP_NOP
这个OP什么都不会做,主要用在有些情况下的placeholder。
IORING_OP_READV
IORING_OP_WRITEV
提交一个readv()或者write()操作,这是io_uring在大多数情况下最核心的目标。
IORING_OP_READ_FIXED
IORING_OP_WRITE_FIXED
这些opcode也会提交I/O操作,不过他们使用了kernel里面早就map好的"registered" buffer,这样就能减少总共的overhead。
IORING_OP_FSYNC
发布一次fsync()调用,也就是一个异步的synchronization操作。
IORING_OP_POLL_ADD
IORING_OP_POLL_REMOVE
IORING_OP_POLL_ADD会发起一次针对一组文件描述符的poll()操作。这是一次性的操作,完成之后就必须再重新提交发起一次,也可以用IORING_OP_POLL_REMOVE来明确指定取消它。采用这种方式进行的polling可以异步地观察一组文件描述符。io_uring子系统也支持多个操作之间有互相依赖关系,这样可以用一个poll操作来暂时延后另一个操作,直到相关的文件描述符准备好之后再发出下一个操作。
io_uring功能引起了很多人对它的强烈兴趣。Io_uring的作者Jens Axboe本可以到此为止,歇息一段时间。不过他其实继续做了很多事情,在5.1版本之后,增加了如下的操作:
IORING_OP_SYNC_FILE_RANGE (5.2)
执行一次sync_file_range()操作 — 这是对现有的fsync()支持的一个改进,不过执行结果不如fsync()那么可靠,只能达到部分效果。
IORING_OP_SENDMSG (5.3)
IORING_OP_RECVMSG (5.3)
这些操作支持在网络上通过sendmsg()和recvmsg()来异步地进行packet发送和接收。
IORING_OP_TIMEOUT (5.4)
IORING_OP_TIMEOUT_REMOVE (5.5)
这个操作会在指定时长之后结束,可以指定是若干秒或者若干个完成的io_uring操作。这是一个强制唤醒正在等待的application的方式,哪怕它在唤醒后还会继续sleep等待其他的操作完成。
IORING_OP_ACCEPT (5.5)
IORING_OP_CONNECT (5.5)
accept某个socket上的connection,或者发起一个connection去连接远端对象。
IORING_OP_ASYNC_CANCEL (5.5)
试着取消目前已经发起过的一个操作。是否能够成功,则取决于这个操作的类型,以及它已经走了多元了。
IORING_OP_LINK_TIMEOUT (5.5)
创建一个timeout,跟ring buffer中某个特定的operation关联起来。如果在timeout时间到的时候,这个operation还在进行,那么kernel就试着去取消这个操作。如果此operation已经结束了,那么这个timeout操作本身会被取消掉。
以上就是5.5 kernel release里面io_uring的接口情况。
Coming soon
io_uring的开发还远未停止。可以通过查看一下linux-next里面等待合入5.6的功能就可以有一些了解:
IORING_OP_FALLOCATE
用fallocate()针对某个文件分配一些block。
IORING_OP_OPENAT
IORING_OP_OPENAT2
IORING_OP_CLOSE
打开关闭文件。
IORING_OP_FILES_UPDATE
可以把常用的文件注册到io_uring里面,从而加快访问速度。这个命令可以异步地把文件加到list里(或者从list里面拿掉)。
IORING_OP_STATX
利用statx()来查询某个文件的信息。
IORING_OP_READ
IORING_OP_WRITE
这些操作跟IORING_OP_READV 和 IORING_OP_WRITEV类似,不过他们用了更简单的接口,只能处理单个buffer。
IORING_OP_FADVISE
IORING_OP_MADVISE
异步地执行posix_fadvise() 和 madvise() 系统调用。
IORING_OP_SEND
IORING_OP_RECV
发送和接收网络数据。
IORING_OP_EPOLL_CTL
使用epoll_ctl()系统调用来操作一组epoll文件描述符。
不知道kernel 5.6里面会变成什么样。目前有在试图加ioctl()的支持,不过被否决了,因为担心可靠性和安全性。不过Axboe找到了一个方法可以今后逐个针对特定的ioctl()操作增加支持。举例来说,media subsystem中如果支持若干个对性能有帮助的ioctl()操作,肯定可以从中受益。
此前还有一个patchset增加了splice()的支持。
An asynchronous world
所有人都说io_uring在快速孵化出许多年前人们就期望出现的thread-based asynchronous机制。大家都非常希望避免event loop(事件队列)被阻塞的情况出现。很有可能这套API会继续发展,直到有许多情况下的task都可以自由执行而几乎不会被阻塞(blocking)。不过这个过程中还是有一些有趣的问题需要处理好。
首先,io_uring里面的命令部分只有8个bit,也就是最多只支持256个opcode。在5.6里已经有30个opcode被用到了,还有许多空余。不过在Linux里面,系统调用的个数已经超过256个了。如果io_uring最终发展得能支持大多数系统调用,那么就不够用了。
Stefan Metzmacher提出了另一个问题。Io_uring已经支持了多个command之间的互相依赖关系,比如可以直到某个前面的操作完成之后再开始另一个操作的初始化动作。不过更麻烦的是在多个operation之间传递信息。Metzmacher有个应用场景需要异步地调用openat(),然后针对结果里的文件描述符来发起I/O操作,不需要等open操作真正完成。
看起来有一个方案能解决这个问题:那就是利用BPF来把两个操作串起来。BPF有能力在kernel里面一系列异步操作中恰当的位置那里执行一些代码,这样一来,肯定可以实现更多的可能性了。Axboe说“这里有很大潜力”。确实如此,你可以想象一下把整个程序都用一个很小的C语言写的驱动放到ring里面,然后大多数时候都会自己执行不用管它了。
不过这里还有一个潜在问题,io_uring是一个无特权的接口,在执行各种操作的时候,会进行所有必要的权限检查。不过BPF目前看来越来越没有指望能让无特权的普通用户调用了,开发者明确指出今后不会支持BPF的unprivileged use。这样一来,BPF很难配合io_uring使用了。在Facebook倒可能有些潜在的计划来解决这个问题,不过目前还没提交到公众mailing list来讨论。总之,在2020 Linux Storage, Filesystem, and Memory-Management Summit上会需要讨论一些BPF相关话题了。
总之,尽管io_uring看起来势头很好,只有很少的一些成长的烦恼。在后续几个release里面,我们很期待能看到有哪些功能又加入这个subsystem里面去。从最近的历史记录来看,io_uring的成长仍然没有慢下来的迹象。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~