首页 > 解决方案 > 在 linux 2.6 中的 switch_to() 中保存通用寄存器

问题描述

我在链接https://www.maizure.org/projects/evolution_x86_context_switch_linux/switch_to中看到了文章《Linux 中 x86 上下文切换的演进》中的代码

大多数版本switch_to只保存/恢复 ESP/RSP 和/或 EBP/RBP,而不是内联汇编中的其他调用保留寄存器。但是 Linux 2.2.0 版本确实将它们保存在这个函数中,因为它使用软件上下文切换而不是依赖硬件 TSS 的东西。后来的 Linux 版本仍然做软件上下文切换,但是没有这些 push/pop 指令。

寄存器是否保存在其他函数中(可能在schedule()函数中)?还是不需要将这些寄存器保存在内核上下文中?

(我知道当系统进入内核模式时,用户上下文的那些寄存器保存在内核堆栈中)。

标签: linux-kernelx86low-levelcontext-switch

解决方案


2.2.0 之前的 Linux 版本使用硬件任务切换,TSS 为您保存/恢复寄存器。这就是"ljmp %0\n\t"正在做的事情。(ljmp是远 jmp 的 AT&T 语法,大概是任务门)。我不太熟悉硬件 TSS 的东西,因为它不是很相关;它仍然在现代内核中用于让 RSP 指向内核堆栈以进行中断处理程序,但不适用于任务之间的上下文切换。

硬件任务切换很慢,所以后来的内核避免了它。Linux 2.2使用push/ popbefore/after 交换堆栈手动保存/恢复调用保留寄存器。EAX、EDX 和 ECX 被声明为虚拟输出 ( "=a" (eax), "=d" (edx), "=c" (ecx)),因此编译器知道这些寄存器的旧值不再可用。

这是一个明智的选择,因为switch_to可能在非内联函数中使用。调用者将进行一个函数调用,该函数调用最终返回(在运行另一个任务一段时间后),调用保留的寄存器恢复,调用破坏的寄存器被破坏,就像常规函数调用一样。(因此,使用宏的函数的编译器代码生成switch_to不需要在内联 asm 之外发出保存/恢复代码)。如果您考虑在 asm(而不是内联asm)中编写整个上下文切换函数,您将免费获得这种易失性寄存器的破坏,因为调用者期望这样做。

那么后来的内核如何避免在 inline asm 中保存/恢复这些寄存器呢?

Linux 2.4"=b" (last)用作输出操作数,因此编译器必须在使用此 asm 的函数中保存/恢复 EBX。asm 仍然保存/恢复 ESI、EDI 和 EBP(以及 ESP)。文章的正文指出了这一点:

2.4 内核上下文切换带来了一些小的变化:EBX 不再被推送/弹出,但它现在包含在内联汇编的输出中。我们有一个新的输入参数。

我没有看到他们在哪里告诉编译器 EAX、ECX 和 EDX 无法生存,所以这很奇怪。noinline这可能是他们通过制作功能或其他东西来摆脱的错误?

i386 上的 Linux 2.6 使用更多的输出操作数让编译器处理保存/恢复。

但是用于 x86-64 的 Linux 2.6 引入了将保存/恢复轻松交给编译器的技巧: #define __EXTRA_CLOBBER ,"rcx","rbx","rdx","r8","r9","r10", "r11","r12","r13","r14","r15"

注意clobbers声明:: "memory", "cc" __EXTRA_CLOBBER

switch_to这告诉编译器内联汇编销毁了所有这些寄存器,因此编译器将发出指令以在最终内联到的任何函数的开始/结束处保存/恢复这些寄存器。

告诉编译器在上下文切换后销毁所有寄存器可以解决与使用内联 asm 手动保存/恢复它们相同的问题。编译器仍然会创建一个遵循调用约定的函数。

上下文切换交换到新任务的堆栈,因此编译器生成的保存/恢复代码始终使用适当的堆栈指针运行。请注意,内联 asm int Linux 2.2 和 2.4 中的显式推送/弹出指令位于其他所有内容之前/之后。


推荐阅读