![为什么没有实现复合系统调用之类的东西?](https://linux22.com/image/216763/%E4%B8%BA%E4%BB%80%E4%B9%88%E6%B2%A1%E6%9C%89%E5%AE%9E%E7%8E%B0%E5%A4%8D%E5%90%88%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E4%B9%8B%E7%B1%BB%E7%9A%84%E4%B8%9C%E8%A5%BF%EF%BC%9F.png)
由于内核和用户空间之间的隔离,Syscall(系统调用)会导致一些性能损失。因此,减少系统调用听起来是个好主意。
所以我的想法是,我们可以将系统调用打包成一个。因此,我们的想法是将系统调用和参数放置在内存中的简单数据结构中。然后我们可以引入一个新的系统调用,我们给出这个数据结构。然后,内核可以并行触发所有功能,并在一个(或所有)系统调用完成时恢复线程。
我认为这种方法将成为并发编程(异步 I/O)的良好基础,并且通过允许任何系统调用上的并发并减少总体上下文切换来改进现有的 select/poll/epoll 解决方案。
为什么没有这样做?
答案1
这已经存在了。在Linux上它的实现是io_uring,自内核 5.1 版本(2019 年 5 月)起可用:操作被放置在队列(或更确切地说,环)上并在没有系统调用的情况下进行处理,其结果将发送到另一个队列。
答案2
一般概念已经完成并且确实存在。正如 Stephen Kitt 的回答所指出的,最接近的示例是 Linux 上的 io_uring,但它远不是此类接口的唯一示例。 Windows、Solaris、AmigaOS 和少数其他操作系统都具有类似的面向 IO 的完成队列机制,其工作方式与 io_uring 类似(Linux 实际上有点晚了)。
此外,实际上在类 UNIX 系统上有很多系统调用,虽然它们不像您建议的那样工作,但确实避免了很多通过将通常在用户空间中完成的某些任务推入内核来实现潜在的上下文切换。系统sendfile()
调用可能是此类系统调用的最佳示例,它需要执行一项非常常见的任务(将大量数据从一个文件描述符复制到另一个文件描述符)并将其完全推送到内核模式,完全避免循环和大量上下文切换在用户空间中执行此操作需要(和额外的缓冲区)。
不过,这里要理解的一个关键事情是,为了使这一点有意义,像这样批量设置与相关操作集相关的所有内容的成本必须低于仅以“正常”方式进行的成本。仅当您正在处理时,使用 io_uring 才有意义地段IO,例如为 VM 模拟块存储设备时(QEMU 支持为此使用它,即使在快速主机硬件上,性能差异也是如此)疯狂的),或者每秒读取数千个文件(我工作的公司最近开始在内部讨论可能使用 io_uring 来处理此类工作负载)。类似地,sendfile()
只有当您需要多次读/写迭代来通过用户空间复制数据时才有意义(尽管这通常是无法承担用户空间中的缓冲区空间的一种功能,而不是运行读/写迭代更快)迭代)。
此外,系统调用实际上必须在批处理上下文中有意义。如果处理保留了调用的顺序,IO 通常在这里确实有意义,但很多事情就是没有意义。例如,尝试使用这种类型的接口exec()
(组合的 fork 和 exec或许,但不是普通的执行者)。同样,某些类型的系统调用只有在单独处理时才有用。操作进程的信号掩码就是一个很好的例子,除了初始设置之外,您几乎总是这样做是为了保护代码中的关键部分,并且您通常需要为此目的进行及时、可预测的处理。
答案3
这些功能已经存在了相当长的时间。
1997 年的 Solaris 2.6添加了一个内核异步 IO 系统调用来执行此操作 -kaio()
。
访问它的一种方法是通过`lio_listio() 函数:
lio_listio
- 列出定向 I/O
概要
cc [ flag... ] file... -lrt [ library... ] #include <aio.h> int lio_listio(int mode, struct aiocb *restrict const list[], int nent, struct sigevent *restrict sig);
描述
该
lio_listio()
函数允许调用进程、LWP 或线程在单个函数调用中发起 I/O 请求列表。
Illumoslibc
源代码已开源并源自原始 Solaris 实现,lio_listio()
可以在以下位置找到:https://github.com/illumos/illumos-gate/blob/470204d3561e07978b63600336e8d47cc75387fa/usr/src/lib/libc/port/aio/posix_aio.c#L121
此类功能并不常见的原因之一是,除非整个软件和硬件系统都旨在利用它,否则它们实际上并不能提高性能太多。
必须配置存储以提供正确对齐的块,必须构建文件系统,以便它们与存储系统提供的块正确对齐,并且需要将整个软件堆栈写入不是把 IO 搞砸了——这一切都必须正确对齐 IO。
对于旋转磁盘,对同一磁盘的一批 IO 操作很容易相互干扰,并且实际上会减慢所有操作,因为磁头花费更多时间进行查找。
根据我的经验,只需其中一层做错事,批处理系统调用的性能优势就会消失在开销中。因为即使与最糟糕的系统调用开销相比,IO 也很慢。
创建和维护组合硬件/软件系统以利用批量 IO 系统调用提供的性能改进的成本是巨大的。
我见过的最好的数字是,将许多 IO 调用批量合并到一个系统调用中可以将性能提高大约 25-30%。
如果您要全天候连续处理数百 GB 的数据,那就很重要。
构建和维护这样的整个系统只是为了将观看猫视频的延迟从 8 毫秒降低到 6 毫秒?没那么多。