在 linux 2.6 中将通用寄存器保存在 switch_to() 中

saving general purpose registers in switch_to() in linux 2.6

我在linkhttps://www.maizure.org/projects/evolution_x86_context_switch_linux/

文章"Evolution of the x86 context switch in Linux"中看到了switch_to的代码

大多数版本的 switch_to 仅 save/restore ESP/RSP and/or EBP/RBP,而不是内联 asm 中的其他调用保留寄存器。但是 Linux 2.2.0 版本 确实 将它们保存在这个函数中,因为它使用软件上下文切换而不是依赖硬件 TSS 东西。后来 Linux 版本仍然进行软件上下文切换,但没有这些推送/弹出指令。

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

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

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

硬件任务切换很慢,所以后来的内核避免了。 Linux 2.2 save/restore call-preserved 手动注册,push/pop before/after 交换堆栈. EAX、EDX 和 ECX 被声明为虚拟输出 ("=a" (eax), "=d" (edx), "=c" (ecx)),因此编译器知道这些寄存器的旧值不再可用。

这是一个明智的选择,因为 switch_to 可能在 non-inline 函数内部使用。调用者将进行一个函数调用,最终 returns(在 运行 另一个任务一段时间后)恢复 call-preserved 寄存器,并且 call-clobbered 注册被破坏,就像一个普通的函数调用。 (因此使用 switch_to 宏的函数的编译器 code-gen 不需要在内联 asm 之外发出 save/restore 代码)。如果您考虑在 asm 中编写整个上下文切换函数(而不是 inline asm),您会免费得到这种对易失性寄存器的破坏,因为调用者希望如此。

那么后来的内核如何避免saving/restoring内联asm中的那些寄存器?

Linux 2.4 使用 "=b" (last) 作为输出操作数,因此编译器必须在使用此 asm 的函数中 save/restore EBX。 asm 仍然是 saves/restores ESI、EDI 和 EBP(以及 ESP)。文章正文是这样注释的:

The 2.4 kernel context switch brings a few minor changes: EBX is no longer pushed/popped, but it is now included in the output of the inline assembly. We have a new input argument.

我没看到他们在哪里告诉编译器 EAX、ECX 和 EDX 不存在,所以这很奇怪。这可能是他们通过制作函数 noinline 或其他东西而逃脱的错误?

Linux i386 上的 2.6 使用更多的输出操作数让编译器处理 save/restore.

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

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

这告诉编译器内联 asm 会破坏所有这些寄存器,因此编译器将向 save/restore 这些寄存器发出指令 switch_to 最终内联到 switch_to 的任何函数的 start/end .

告诉编译器在上下文切换后所有寄存器都被销毁解决了与使用内联 asm 手动 saving/restoring 它们相同的问题。编译器仍然会生成一个遵循调用约定的函数。

context-switch 交换到新任务的堆栈,因此 compiler-generated save/restore 代码始终是 运行 适当的堆栈指针。请注意,内联 asm int Linux 2.2 和 2.4 中的显式 push/pop 指令在其他所有内容之前/之后。