系统调用开销

17

我刚开始学习系统调用。 我想知道发生系统调用时会导致什么开销。

例如,如果我们考虑 getpid() 函数,当调用 getpid() 时,我的猜测是,如果控制权当前在子进程中,则必须进行上下文切换才能进入父进程以获取 pid。这是否会对开销造成贡献?

此外,当调用 getpid() 时,将在用户空间边界之间传输一些元数据并进入并退出内核。因此,频繁切换用户空间和内核之间是否也会造成一些开销?


顺便提一下,在启用内核中的Spectre缓解措施的现代x86上,系统调用开销比以前显着增加,至少对于简单/廉价的系统调用而言,其中刷新分支预测器占总时间的很大一部分。SYSCALL/SYSRET指令在x86 CPU上的执行情况如何? - 系统调用后的执行需要一段时间才能恢复到正常状态,因为内核代码污染了缓存,并且对于乱序执行来说,需要看得足够远才能找到并行性。 - Peter Cordes
3个回答

25

我在一台x86-64 Linux机器上进行了更精确的基准测试(使用-O3编译):

ns    relative(rounded) function
4.89  1      regular_function  //just a value return
6.05  1      getpid   //glibc caches this one (forks invalidate the cached value)
17.7  4      sysconf(_SC_PAGESIZE)
22.6  5      getauxval(AT_EUID)
25.4  5      sysconf(_SC_NPROCESSORS_ONLN)
27.1  6      getauxval(AT_UID)
54.1  11     gettimeofday
235   48     geteuid
261   53     getuid
264   54     getppid
314   64     sysconf(_SC_OPEN_MAX)
622   127    pread@0 // IO funcs benchmarked with 1 bytes quantities
638   130    read    // through a 1 Gigabyte file
1690  346    write
1710  350    pwrite@0

最便宜的"syscalls"是通过辅助向量的那些(~20-30ns)。中间的调用(~250-310ns)应该最准确地反映平均开销,因为内核中不需要做太多的工作。

相比之下,小尺寸请求(<64字节=>没有系统调用)的malloc+free对成本约为70-80ns(请参见我在C中静态内存分配与动态内存分配的成本比较回答)。

https://softwareengineering.stackexchange.com/questions/311165/why-isnt-there-generic-batching-syscall-in-linux-bsd/350173提供了一些有关如何将syscall开销最小化的有趣想法。


7
自从glibc 2.25发布后,getpid()已不再被缓存,这个答案提交后不久就发布了。getpid(2)的手册页面提供了有关此主题的一些附加上下文,并指出在版本2.3.4到2.24之间存在缓存。 - jmikola
正则函数是系统调用还是普通函数? - choxsword
1
@scottxiao 常规 = 普通。geteuid 是真正的系统调用开始的地方。一个真正的系统调用不可能比这个快多少。 - Petr Skocik
在表格中包含一个通用函数调用会获得很多加分,这让我们可以说“仅获取信息的系统调用比本地函数调用慢5-50倍”。非常有用。 - Daniel Griscom

7
例如,如果我们考虑getpid(),当系统调用getpid()时,如果控制当前在子进程中,则我猜想需要进行上下文切换以进入父进程以获取pid。
这里不应该需要对子进程进行上下文切换 - 内核应该拥有所有必要的数据。在大多数情况下,内核只会在调度程序中或从系统调用返回时切换到用户空间进程。
此外,当调用getpid()时,将在用户空间和内核之间传输一些元数据并进入和退出内核。因此,用户空间和内核之间的不断切换是否也会导致一些开销?
是的,如果经常调用getpid(),则开销肯定会累积。有一些方法可用于避免简单的“getter”系统调用(如getpid()和gettimeofday())产生的这种开销;其中一种曾经在Linux下使用的方法是将系统调用的已知结果存储在特殊的内存页面中。 (此机制称为vsyscall。)

4

请见谅我有些概括(而不是每个句子都详细说明)。

调用系统服务(例如返回进程信息)会有一个用户模式壳。这个壳会触发异常,然后通过系统调度表路由到调用内核模式系统服务。

切换到内核模式需要类似于进程上下文切换的操作。例如,需要从用户栈切换到内核栈(和其他系统相关的更改)。

调用进程提供用户模式返回缓冲区。出于安全原因,系统服务将在写入响应数据之前检查其是否为有效的用户模式缓冲区。

像只返回有关当前进程信息的getpid这样的库函数可能不需要切换到内核模式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接