关注了就能看到更多这么棒的文章哦~
Descriptorless files for io_uring
By Jonathan Corbet
July 19, 2021
DeepL assisted translation
https://lwn.net/Articles/863071/
文件描述符(file descriptor )这个底层信息是 Linux 系统中的基本对象之一。文件描述符,是一个简单的整数,可以指代一个打开的文件,或者代表一个网络连接、一个正在运行的进程、一个加载了的 BPF 程序或一个命名空间。多年来,使用文件描述符来指代一个临时对象的做法已经广泛应用,甚至人们都不觉得使用其他方案的 API 是合理的了。但有趣的是,io_uring 子系统看起来似乎正在逐步使用自己的数字空间(number space),而不是直接使用文件描述符了。
Io_uring 是为了解决异步 I/O 问题而开发出来的,异步 I/O 在 Linux 中一直未能按用户希望的那样完美支持起来。用户空间可以往一个直接与内核共享的内存区域中的队列内来加入一些操作,然后发起这些操作,许多情况下都不需要系统调用,于是能减少相关开销。同样地,有另一个共享内存区域中放置了这些操作完成后的结果。起初的时候,io_uring 只专注于一些简单操作(例如读写操作),但它后来很快就支持了许多其他系统调用。它正在逐渐发展成为 Linux 系统所一直缺乏的一套通用的异步操作 API。
Fixed files
读写操作必须要指定其需要操作的目标文件描述符,以及放置数据的 buffer。不过,在操作进行之前,内核中必须进行大量的配置工作。其中包括获取这个打开了的文件的引用技术(以防止在操作进行时文件消失)并且把这部分 buffer 相关内存落实(lock down)下来。很多情况下这部分操作的开销会占到整个操作的开销的很大一部分,因为程序往往会对相同的文件描述符和相同的 buffer 进行多次操作,而这部分开销就会为同一个资源耗费很多次,不断增加。
从一开始,io_uring 就提供了一种减少这种开销的方法,那就是 io_uring_register() 系统调用:
int io_uring_register(unsigned int fd, unsigned int opcode,
void *arg, unsigned int nr_args);
如果 opcode 是 IORING_REGISTER_BUFFERS,那么 io_uring 子系统将为 arg 所指向的 nr_args 个 buffer 进行准备工作,并保留结果。之后这些 buffer 就可以多次使用了,不必每次都需要重新耗费时间来进行配置。如果 opcode 是 IORING_REGISTER_FILES,那么 arg 将被解释为一个 nr_args 个文件描述符数组。该数组中的每个文件都会增加引用计数并保持打开状态,这样,它同样可以在后续多个操作中有效地使用起来。在 io_uring 的行话中将这些文件描述符称为 "fixed" 状态(已固定的状态)。
fixed 文件有几个有趣的特性。其一是应用程序就可以对这个 fixed 文件相关的文件描述符调用 close(),但 io_uring 中的引用技术将保留,并且文件仍然可以使用。另一个特性是,在后续的 io_uring 操作中, fixed 文件不会被其文件描述符的数字编号所引用。取而代之的是使用当初调用 io_uring_register()时该文件描述符在 args 阵列中的偏移量位置。因此,如果文件描述符 42 被放在 args[13]中,它随后将在 io_uring 中被称为 fixed file 13。
效果上来说,io_uring 子系统其实是重新建立了一套描述符用来引用之前打开过的文件,但它跟常规的文件描述符数字并不一致。不过,在当前内核中,仍然需要先为一个文件获得一个常规的文件描述符,并完成 register 流程,这样之后该文件才会出现在 io_uring 的 fixed-file space 中。然而,如果某个应用程序从来不会在 io_uring 之外对文件进行操作的话,那么创建常规的文件描述符这个操作实际上就没有任何实际作用了。
事实上是可以在 io_uring 中创建、使用和关闭一个文件描述符的。如上所述,这个子系统并不局限于简单的 I/O 操作,它也可以通过 io_uring 操作来打开文件或者接受网络连接。不过目前来说,用户空间必须在文件描述符的创建和使用之间进行一些干预,才能将其改为 fixed file。这部分工作的开销并不大,但是如果这个应用程序要处理大量的文件描述符的话,这个开销也会累计起来。
No more file descriptors
为了解决这个问题,Pavel Begunkov 发布了一组 patch,增加了一个 open 操作,可以直接进入 fixed file 状态。这个用来创建文件描述符的 io_uring 操作(类似于 openat2()和 accept() 这些系统调用)获得了这样的能力:根据用户提供的偏移量来直接将其结果存储为 fixed file table。当选择了这个选项时,根本就没有创建出常规的文件描述符,要引用文件的时候,只能使用 io_uring 中的这种特殊描述符。
这个功能最可能的用例就是网络服务端了。一个繁忙的服务端程序可以在短时间内创建(使用 accept() 系统调用)并使用大量的文件描述符。虽然 io_uring 操作是异步的,通常都是可以按任何顺序执行的,但也有可能有一些连锁操作,也就是在前一个操作成功完成之前不会开始另一个操作。利用这个功能,网络服务端程序就可以将一系列操作都加到队列中去来接受后续传入的连接(并将其存储在 fixed-file table 中),然后给对端按照标准的问候语来回复回去,然后开始从对端来读取第一个数据。只有当数据到达并准备好等待处理时,用户空间才需要参与进来。
这显然是一种有趣的功能,它显示了 io_uring 是如何迅速发展成为 Linux 系统的另一种编程接口的。与传统的文件描述符的分离只是朝着这个方向又迈出了一步。随着未来加入 BPF 支持(仍在开发中),这种分离趋势将变得更加明显。有些应用程序的用户空间部分的工作会变得更少。io_uring API 的使用,对于大多数应用程序来说可能是没有必要的,但对于某些应用程序来说则可以带来很大的变化。很期待看到它今后的发展方向。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~