在模式切换中涉及的开销是什么?

18

很多时候我读到/听到的观点是,由于应用程序需要进行模式切换(即从用户模式切换到内核模式),并在执行系统调用后通过再次进行模式切换开始在用户模式下执行,因此进行大量系统调用等操作效率低下。

我的问题是模式切换的开销是多少? CPU缓存是否会失效或TLB条目会被清除,或者发生了什么导致了开销?

请注意,我问的是模式切换所涉及的开销,而不是上下文切换。我知道模式切换和上下文切换是两个不同的概念,并且我完全了解与上下文切换相关的开销,但我不理解模式切换会导致哪些开销。

如果可能,请提供关于特定*nix平台(如Linux、FreeBSD、Solaris等)的一些信息。

问候

lali

2个回答

13

简单模式切换不应该有CPU缓存或TLB清除。

一个快速的测试告诉我,在我的Linux笔记本电脑上,一个用户空间进程完成一个简单的系统调用(除了切换到内核模式和返回之外没有任何工作)大约需要0.11微秒。我正在使用getuid(),它只从内存结构体中复制一个整数。strace确认该系统调用被重复执行了MAX次。

#include <unistd.h>
#define MAX 100000000
int main() {
  int ii;
  for (ii=0; ii<MAX; ii++) getuid();
  return 0;
}

在我的笔记本电脑上,使用 time ./testover 进行测量大约需要 11 秒钟,而将 11 秒钟除以 1 亿得到的结果是 0.11 微秒。

从技术上讲,这是两次模式切换,因此你可以认为单个模式切换需要 0.055 微秒,但单向切换并不是很有用,所以我认为来回切换的数字更具相关性。


更快的系统调用实际上是调用一个不存在的系统调用,它会返回 ENOSYS。请参见:https://dev59.com/yKnka4cB1Zd3GeqPLD4m - Tony Baguette

2

在x86 CPU上进行模式切换有许多方法(我在这里假设)。对于一个用户调用的函数,常规方式是执行任务跳转或调用(称为任务门和调用门)。这两种方式都涉及到任务切换(相当于上下文切换)。加上一些调用前的处理、标准验证后的处理以及返回操作。这样就可以保证最基本的安全模式切换。

至于Eric的时间问题,虽然我不是Linux专家,但在我接触过的大多数操作系统中,简单的系统调用会将数据缓存(如果可以安全地这样做)在用户空间中,以避免这种开销。而且我认为getuid()是一个很好的候选项。因此,Eric的时间可能更多地反映了用户空间预切换处理的开销,而不是其他什么问题。


1
没有发生数据缓存,这就是为什么我使用strace来验证系统调用是否正在进行。按照定义,系统调用是对内核空间的调用。 - Eric Seppanen
1
此外,你所断言的模式切换等同于上下文切换是不正确的。上下文切换涉及交换整个CPU状态和页表;这是一项重要的工作,对于系统调用并非必需。系统调用是一个简单的软件中断(x86汇编“int $0x80”)。 - Eric Seppanen
2
Juice: 在x86 Linux上,int 0x80系统调用使用陷阱门(Trap Gate),它不会改变任务(tr寄存器)。它只是指定一个段选择器和偏移量以跳转到相应的代码。段选择器本身包含新的特权级别(DPL)- 因为我们正在更改特权级别,所以我们还会得到一个新的堆栈段和堆栈指针 - 这些都从TSS中加载。 - caf
1
我认为你误解了——Linux内核已经有一段时间没有使用硬件任务了。每个CPU只有一个TSS,此外在i386上还有一个双重故障处理程序TSS(只有在双重故障时才使用任务门)。如果这是不安全的,我相信参与Linux内核开发的英特尔员工中的某一个现在就会说出来了。请参见“init_task.c”中第35行的注释:http://lxr.linux.no/#linux+v2.6.32/arch/x86/kernel/init_task.c - caf
4
我错了。为了辩护,我已经有一段时间没玩这个东西了。你可以使用安全的、更高特权级别的调用方式,而无需进行任务切换(使用Call、Interrupt或Trap门)。对此我深表歉意。 - Juice
显示剩余4条评论

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