以linux 4.16.3为例
A->B->C
A进程切换到B进程,B进程切换到C进程。
在A进程的栈看来, prev为A, next为B
在B进程的栈看来, prev为B, next为C
static __always_inline struct rq *context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next, struct rq_flags *rf)
{
struct mm_struct *mm, *oldmm;
mm = next->mm;
oldmm = prev->active_mm;
if (!mm) {
next->active_mm = oldmm;
mmgrab(oldmm);
enter_lazy_tlb(oldmm, next);
} else
switch_mm_irqs_off(oldmm, mm, next);
=>void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk)
{
this_cpu_write(cpu_tlbstate.is_lazy, false);
load_new_mm_cr3(next->pgd, new_asid, true); // 重新加载页表
if (next != &init_mm)
this_cpu_write(cpu_tlbstate.last_ctx_id, next->context.ctx_id);
this_cpu_write(cpu_tlbstate.loaded_mm, next);
this_cpu_write(cpu_tlbstate.loaded_mm_asid, new_asid);
load_mm_cr4(next);
switch_ldt(real_prev, next);
}
switch_to(prev, next, prev);
=>#define switch_to(prev, next, last)\
do {\
prepare_switch_to(next);\
\
((last) = __switch_to_asm((prev), (next)));\
} while (0)
=>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
/* switch stack */
movq%rsp, TASK_threadsp(%rdi)
movqTASK_threadsp(%rsi), %rsp// 切换rsp指针, 以后push和pop就在B进程的内核栈进行
#ifdef CONFIG_CC_STACKPROTECTOR
movqTASK_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 */
popq%r15
popq%r14
popq%r13
popq%r12
popq%rbx
popq%rbp// 切换rbp指针, 后续的局部变量都是B的
jmp__switch_to// jmp不压栈, 所以_switch_to 不会返回到jmp的下一条指令
=>__visible __notrace_funcgraph struct task_struct *
__switch_to(struct task_struct *prev_p, struct task_struct *next_p) // 两个入参分别是rdi rsi, 通过反汇编可以看出
{
struct thread_struct *prev = &prev_p->thread;// 由于是寄存器传递参数, 所以prev_p还是指向A, 但是prev和next已经是在B的内核栈空间创建
struct thread_struct *next = &next_p->thread;// next_p还是指向B
struct tss_struct *tss = &per_cpu(cpu_tss_rw, cpu);
save_fsgs(prev_p);
load_TLS(next, cpu);
savesegment(es, prev->es);
savesegment(ds, prev->ds);
load_seg_legacy(prev->fsindex, prev->fsbase,
next->fsindex, next->fsbase, FS);
load_seg_legacy(prev->gsindex, prev->gsbase,
next->gsindex, next->gsbase, GS);
switch_fpu_finish(next_fpu, cpu);
/*
* Switch the PDA and FPU contexts.
*/
this_cpu_write(current_task, next_p);
this_cpu_write(cpu_current_top_of_stack, task_top_of_stack(next_p));
/* Reload sp0. */
update_sp0(next_p);
return prev_p;// A只有rdi指向, 所以很容易丢失, 通过一个返回值返回用于别的用途, 返回到了 switch_to(prev, next, prev);的下一条语句, 即barrier();
}
END(__switch_to_asm)
barrier();
return finish_task_switch(prev);
}
总之, 关键记住两点: rsp决定push和pop; rbp决定局部变量. 当rsp和rbp切换之后, A进程就变成了B进程。
关于Linux进程切换switch_to宏的一个细节(认识内联汇编)
https://blog.csdn.net/qq_42763389/article/details/89028749
switch_to()宏的理解
http://blog.chinaunix.net/uid-130624-id-2907709.html
【内核】进程切换 switch_to 与 __switch_to (好文)
https://cnblogs.com/visayafan/archive/2011/12/10/2283660.html