系统调用与函数调用之间的性能差异



我经常听到驱动程序开发人员说尽可能避免内核模式切换是件好事。我无法理解确切的原因。首先我的理解是——

  1. 系统调用是软件中断。在 x86 上,它们由使用指令系统输入器触发。这实际上看起来像一个分支指令,它从特定于机器的寄存器中获取目标。
  2. 系统调用实际上不必更改地址空间或进程上下文。
  3. 但是,它们确实将寄存器保存在进程堆栈上,并将堆栈指针更改为内核堆栈。

在这些操作中,系统调用的工作方式几乎与普通函数调用类似。尽管系统门特的行为可能像一个错误预测的分支,这可能导致处理器管道中的 ROB 刷新。即使这样也不是很糟糕,它就像任何其他错误预测的分支一样。

我听到一些人在Stack Overflow上回答:

  1. 你永远不知道系统调用需要多长时间 - [我]是的,但任何函数都是如此。所需时间取决于功能
  2. 它通常是调度现场。 - [me] 进程可以重新调度,即使它一直在用户模式下运行。 例如,while(1); 不保证无上下文切换。

实际的系统呼叫成本从何而来?

您没有指明您要询问的操作系统。无论如何,让我尝试一个答案。

syscallsysenter的 CPU 指令不应与系统调用的概念及其在相应操作系统中的表示形式混淆。

《英特尔® 64 和 IA-32 架构开发人员手册》第 2A 卷(有关int,请参阅第 3-392 页)和第 2B 卷(有关sysenter请参阅第 4-463 页)的操作部分,可以最好地解释每个指令所产生的开销差异。也不要忘记瞥一眼iretdsysexit

随意计算操作的伪代码会产生:

  • 408 int线
  • 55 行sysenter

注意:尽管现有的答案是正确的,因为sysentersyscall不是中断或以任何方式与中断相关,但Linux和Windows世界中的旧内核使用中断来实现其系统调用机制。在Linux上,这曾经是int 0x80的,而在Windows int 0x2E上。因此,在这些内核版本上,IDT 必须准备好为相应的中断提供中断处理程序。在较新的系统上,确实如此,sysentersyscall指令已完全取代旧方法。sysenter它是MSR(机器特定的寄存器)0x176,它被设置为sysenter处理程序的地址(请参阅下面链接的阅读材料)。

<小时 />

在视窗上...

Windows上的系统调用,就像在Linux上一样,会导致切换到内核模式。NT 的计划程序不提供有关授予线程的时间的任何保证。此外,它占用了线程的时间,甚至可能最终导致线程饥饿。一般来说,可以说用户模式代码可以被内核模式代码抢占(很少有非常具体的例外,你肯定会在"高级驱动程序编写类"中获得)。如果我们只看一个例子,这是完全有道理的。可以换出用户模式代码 - 或者,就此而言,可以交换它尝试访问的数据。现在 CPU 对如何访问交换/分页文件中的页面一无所知,因此需要一个中间步骤。这也是内核模式代码必须能够抢占用户模式代码的原因。这也是Windows上最多产的错误检查代码之一的原因,主要由第三方驱动程序引起: IRQL_NOT_LESS_OR_EQUAL 。这意味着驱动程序在无法抢占接触该内存的代码时访问了分页内存。

<小时 />

延伸阅读

  1. Windows中的SYSENTER和SYSEXIT由Geoff Chappell撰写(根据我的经验,总是值得一读!)
  2. Linux 2.6 中基于 Sysenter 的系统调用机制
  3. Windows NT
  4. 平台特定讨论:Windows NT系统调用如何真正工作?
  5. Windows NT 平台特定讨论:使用 SYSENTER 指令进行系统调用优化
  6. Windows Internals, 5th ed., Russinovich et al. - Page 125 to 132.
  7. KiFastSystemCall的ReactOS实现

SYSENTER/SYSCALL不是软件中断;这些指令的全部意义在于避免因发出IRQ和调用中断处理程序而导致的开销。

在堆栈上节省寄存器需要时间,这是系统调用成本的来源之一。

另一个地方来自内核模式开关本身。它涉及更改段寄存器 - CS,DS,ES,FS,GS,它们都必须更改(在x86-64上成本较低,因为分段大多未使用,但您仍然需要基本上跳到内核代码)并且还更改执行的CPU环。

总结:函数调用(在不使用分段的现代系统上)是近调用,而系统调用涉及远调用和环铃开关。

最新更新