ed9*_*9er 8 x86 assembly linux-kernel
在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 */ \
Run Code Online (Sandbox Code Playgroud)
命名操作数具有内存约束[prev_sp] "=m" (prev->thread.sp). __switch_canary除非CONFIG_CC_STACKPROTECTOR被定义(然后它是一个加载和存储使用%ebx),被定义为什么都没有.
我理解它是如何工作的,如内核堆栈指针备份/恢复,以及如何push next->eip和jmp __switch_to一个ret在功能,这实际上是一个真正的相匹配的"假"调用指令的结束指令ret的指令,并有效地使next->eip返回下一个线程的要点.
我不明白的是,为什么黑客?为什么不干脆call __switch_to,那么之后ret,jmp到next->eip,这是更清洁,方便读者.
这样做有两个原因.
一种是允许操作数/寄存器分配的完全灵活性[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]
Run Code Online (Sandbox Code Playgroud)
即推送返回地址; 你最终会得到一个序列push/ jmp(== call)/ ret/ jmp如果你不想回到这个地方(并且这段代码没有),你可以通过"伪造"一个电话来节省代码分支,因为你只有做push/ jmp/ ret.代码在这里使尾部递归.
是的,这是一个小优化,但避免分支减少延迟和延迟对于上下文切换至关重要.
| 归档时间: |
|
| 查看次数: |
1303 次 |
| 最近记录: |