8

linux/arch/x86/include/asm/switch_to.h中,有宏的定义,switch_to真正的线程切换奇迹的关键行是这样的(直到 Linux 4.7 改变时):

asm volatile("pushfl\n\t"       /* save    flags */ \
              pushl %%ebp\n\t"      /* save    EBP   */ \
              "movl %%esp,%[prev_sp]\n\t"   /* save    ESP   */ \
              "movl %[next_sp],%%esp\n\t"   /* restore ESP   */ \
              "movl $1f,%[prev_ip]\n\t" /* save    EIP   */ \
              "pushl %[next_ip]\n\t"    /* restore EIP   */ \
              __switch_canary                   \
              "jmp __switch_to\n"   /* regparm call  */ \
              "1:\t"                        \
              "popl %%ebp\n\t"      /* restore EBP   */ \
              "popfl\n"         /* restore flags */ \

命名操作数具有内存限制,例如[prev_sp] "=m" (prev->thread.sp). __switch_canary除非CONFIG_CC_STACKPROTECTOR被定义,否则被定义为空(然后它是使用的加载和存储%ebx)。

我了解它是如何工作的,例如内核堆栈指针备份/恢复,以及函数末尾的指令如何与push next->eip指令相匹配,这实际上是与真实指令匹配的“假”调用指令,并有效地使返回下一个线程的点。jmp __switch_toretretnext->eip

我不明白的是,为什么要黑客攻击?为什么不只是call __switch_to,然后在它之后retjmpto next->eip,它更干净,更易于阅读。

4

1 回答 1

6

这样做有两个原因。

一是允许完全灵活的操作数/寄存器分配[next_ip]。如果您希望能够在jmp %[next_ip] 之后执行此操作call __switch_to,则必须%[next_ip]分配一个非易失性寄存器(即,根据 ABI 定义,在进行函数调用时将保留其值的寄存器)。

这对编译器的优化能力造成了限制,并且context_switch()(“调用者” -switch_to()使用的地方)的结果代码可能不如预期的那么好。但是有什么好处呢?

嗯 - 这就是第二个原因出现的地方,没有,真的,因为call __switch_to相当于:

pushl 1f
jmp __switch_to
1: jmp %[next_ip]

即它推送返回地址;你最终会得到一个序列push/ jmp( == call)/ ret/jmp而如果你不想返回这个地方(而这段代码不想),你可以通过“伪造”一个调用来节省代码分支,因为你只有做push/ jmp/ ret。代码在这里使自己尾递归

是的,这是一个小的优化,但是避免分支可以减少延迟,而延迟对于上下文切换至关重要。

于 2013-02-22T12:32:00.400 回答