在linux/arch/x86/include/asm/switch_to.h
,有宏觀switch_to
的定義,做閱讀這樣的真正的線程切換奇蹟的關鍵線路(直到版Linux 4.7時,它改變):爲什麼switch_to使用push + jmp + ret來改變EIP,而不是直接改變jmp?
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)
內存限制。除非定義了CONFIG_CC_STACKPROTECTOR
(然後它是使用%ebx
的加載和存儲),否則定義爲__switch_canary
。
我理解它是如何工作的,如內核堆棧指針備份/恢復,以及如何push next->eip
和jmp __switch_to
在函數的結束,這實際上是一個真正的ret
相匹配的「假」 CALL指令ret
指令指令,並有效地使下一個線程的返回點成爲next->eip
。
我不明白的是,爲什麼黑客?爲什麼不只是call __switch_to
,之後它ret
,jmp
到next->eip
,這是更乾淨和易於閱讀。
但它不會有效殺死回報預測堆棧嗎? – harold 2013-02-22 12:37:20
是的 - 但是註冊目標的'jmp'也是這樣做的,因爲你_never_想真的返回'switch_to()',真的(直到下一個上下文切換)。兩者之間沒有區別。 – 2013-02-22 13:20:11
@harold:好點; (''in'kernel/sched/core.c'](http://elixir.free-electrons.com)返回的上下文切換之後,返回地址預測器棧的當前內容是有價值的/linux/v4.6/source/kernel/sched/core.c#L2752)),並且可能有幾個級別將調用棧備份到調度程序中(直到它們的後退分歧)。但是,只有'%[next_ip]'總是/通常在switch_to內時纔是真的;它確實以這種方式設置了'prev_ip',但也許這不是最常見的值(內核搶佔可能會將其留在別的地方?) – 2017-12-09 21:31:24