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

标签 linux-kernel x86 low-level context-switch

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

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

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

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

最佳答案

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

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

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

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

Linux 2.4 使用 "=b"(last) 作为输出操作数,因此编译器必须在使用此 asm 的函数中保存/恢复 EBX。 asm 仍然保存/恢复 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 或其他东西而逃脱的错误?

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

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

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

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

关于linux-kernel - 在 linux 2.6 中将通用寄存器保存在 switch_to() 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56401206/

相关文章:

sockets - 为什么 socket.c 中有 ioctl 调用?

macos - 控制 MagSafe 2 上的 LED(向检测引脚发送任意数据)

c - 如何优化用 C 读取的二进制文件(超过 1MB)?

.net - 我在哪里可以找到 CorFlags 值的每一位含义的引用?

linux-kernel - kmalloc 中的缓冲区也是 DMA 安全缓冲区吗?

linux - APQ-8016 骁龙 410c : non-standard UART baudrate in Linaro

c - 死锁 linux 管道

assembly - 为什么要使用 “movl $1, %eax”而不是 “movb $1, %eax”

gcc - 栈分配,为什么要多出空间?

c - 重新排列对齐对象的顺序以最小化空间使用