本文代码基于linux内核4.19.195。
我们都知道,上下文切换在函数context_switch中完成,x86架构代码最终会调用函数 __switch_to_asm((prev), (next)));完成寄存器及内核栈的切换。
/*
* %rdi: prev task
* %rsi: next task
*/
ENTRY(__switch_to_asm)
UNWIND_HINT_FUNC
/*
* Save callee-saved registers
* This must match the order in inactive_task_frame
*/
pushq %rbp
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
pushfq //标志寄存器压栈指令
/* switch stack */ //栈切换
movq %rsp, TASK_threadsp(%rdi)
movq TASK_threadsp(%rsi), %rsp
#ifdef CONFIG_STACKPROTECTOR
movq TASK_stack_canary(%rsi), %rbx
movq %rbx, PER_CPU_VAR(irq_stack_union)+stack_canary_offset
#endif
#ifdef CONFIG_RETPOLINE
/*
* When switching from a shallower to a deeper call stack
* the RSB may either underflow or use entries populated
* with userspace addresses. On CPUs where those concerns
* exist, overwrite the RSB with entries which capture
* speculative execution to prevent attack.
*/
FILL_RETURN_BUFFER %r12, RSB_CLEAR_LOOPS, X86_FEATURE_RSB_CTXSW
#endif
/* restore callee-saved registers */
popfq
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
popq %rbp
jmp __switch_to
END(__switch_to_asm)
第一次看这个函数,感觉就是标准的上下文切换写法,最开始把prev进程的rbp、rbx、r12、r13、r14、r15寄存器完成保存,最后面再恢复next进程的这些寄存器。
诶,等等,那rax、rbx这些寄存器去哪了呢?不用恢复吗?
继续深入了看了几遍以及看了__switch_to函数,都没看到有保存和切换rax的代码,那么为啥内核不保存呢?
回到__switch_to_asm函数,猛然发现有一行注释写道:
save callee-saved registers
根本原因就在这里,因为__switch_to_asm是一个被C语言调用的函数,作为调用者,有义务保存相关寄存器,至于保存哪些寄存器,是由x86的函数调用约定规定的,因而,作为被调用方,只需要保存相关的没有被调用方保存的寄存器即可,剩下的寄存器在函数结束后由调用方自行恢复,这里如果还做保存就是浪费力气了。