07-24-2023, 03:21 AM
In [`linux/arch/x86/include/asm/switch_to.h`][1], there's the definition of the macro `switch_to`, the key lines which do the real thread switch miracle read like this (until Linux 4.7 when it changed):
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 */ \
The named operands have memory constraints like `[prev_sp] "=m" (prev->thread.sp)`. `__switch_canary` is defined to nothing unless `CONFIG_CC_STACKPROTECTOR` is defined (then it's a load and store using `%ebx`).
I understand how it works, like the kernel stack pointer backup/restore, and how the `push next->eip` and `jmp __switch_to` with a `ret` instruction at the end of the function, which is actually a "fake" call instruction matched with a real `ret` instruction, and effectively make the `next->eip` the return point of the next thread.
What I don't understand is, why the hack? Why not just `call __switch_to`, then after it `ret`, `jmp` to `next->eip`, which is more clean and reader-friendly.
[1]:
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 */ \
The named operands have memory constraints like `[prev_sp] "=m" (prev->thread.sp)`. `__switch_canary` is defined to nothing unless `CONFIG_CC_STACKPROTECTOR` is defined (then it's a load and store using `%ebx`).
I understand how it works, like the kernel stack pointer backup/restore, and how the `push next->eip` and `jmp __switch_to` with a `ret` instruction at the end of the function, which is actually a "fake" call instruction matched with a real `ret` instruction, and effectively make the `next->eip` the return point of the next thread.
What I don't understand is, why the hack? Why not just `call __switch_to`, then after it `ret`, `jmp` to `next->eip`, which is more clean and reader-friendly.
[1]:
[To see links please register here]