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

时间:2019-05-31 20:12:03

标签: linux

我正在阅读“了解Linux内核”,我在上下文切换这一节。他们在本节中解释了switch_to()宏的代码以及__switch_to()函数的代码以及其内部功能。在那里写,那里没有保存通用寄存器的过程。 实际上,在该过程中唯一保存的寄存器是:ebp,eip,esp,eflags,fpu(取决于先前的进程是否在使用它们),调试寄存器,fs和gs。

在链接https://www.maizure.org/projects/evolution_x86_context_switch_linux/的文章“ Linux中x86上下文切换的演变”中,我也看到了switch_to的代码。 而且在那里,没有通用寄存器的保存,因此,这不仅简化了书的编写者使其变得更容易的方法。

我的问题是,此寄存器是保存在其他函数中(也许在schedule()函数中)还是没有必要将此寄存器保存在内核上下文中(我知道用户上下文的那些寄存器都保存在系统进入内核模式时进入内核堆栈。

非常感谢助手。

1 个答案:

答案 0 :(得分:3)

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

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

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

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

Linux 2.4使用"=b" (last)作为输出操作数,因此编译器必须在使用该asm的函数中保存/恢复EBX。 asm仍会保存/恢复ESI,EDI和EBP(以及ESP)。文章的文字对此进行了说明:

  

2.4内核上下文切换带来了一些小的更改:不再推送/弹出EBX,但现在将其包含在内联程序集的输出中。我们有一个新的输入参数。

我看不到他们在哪里告诉编译器有关EAX,ECX和EDX无法幸存的信息,所以很奇怪。通过制作函数noinline之类的东西,他们可能会解决这个错误?

i386上的Linux 2.6使用了更多的输出操作数,这些输出操作数使编译器可以处理保存/恢复。

但是Linux 2.6 for x86-64引入了使保存/恢复轻松地传递到编译器的技巧: #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中的显式push / pop指令位于其他所有内容之前/之后。