Linux内核x86架构context_switch中switch_to相关技术疑问
Linux x86架构上下文切换技术问题解答
问题1:为何switch_to()宏采用如下方式定义与调用?
#define switch_to(prev, next, last) \ do { \ ((last) = __switch_to_asm((prev), (next))); \ } while (0)
这个宏的设计核心原因有两点:
- do-while(0)的语法安全性:C宏是文本替换逻辑,用do-while(0)包裹能确保宏在任何语法场景下都正常工作。比如直接写赋值语句时,若在
if(...) switch_to(...);这类无括号的条件语句后使用,会导致后续代码被错误纳入条件判断;而do-while(0)会让宏展开后成为独立完整的语句块,避免这类语法陷阱。 - 双向切换的任务指针传递:上下文切换是双向操作——当前任务被切走后,未来一定会被再次切回。
__switch_to_asm汇编函数负责完成栈和寄存器的切换,当当前任务被恢复执行时,它会返回当初把当前任务切走的那个任务指针。通过last参数接收这个返回值,调用者就能知道“这次恢复是从哪个任务切换回来的”。代码中switch_to(prev, next, prev)的写法,就是把返回值直接覆盖到原prev变量,方便后续收尾逻辑(比如finish_task_switch)使用。
问题2:LOCATION (1)处的prev值与LOCATION (2)处是否相同?switch_to()的栈切换操作是否会改变它?
结论是两处的prev值完全不同,栈切换是实现这种变化的基础,具体逻辑如下:
- LOCATION(1)处的
prev是即将被切走的当前任务指针,此时CPU准备从prev切换到next。 - 调用
switch_to(prev, next, prev)时,宏会把__switch_to_asm的返回值赋值给第三个参数(即原prev变量),但这个赋值不是立即完成的:__switch_to_asm先保存prev的上下文,切换到next的栈并恢复next的上下文,此时CPU开始执行next任务,prev的执行被暂停。 - 直到未来某个时刻,
next任务被切走,CPU回到prev任务继续执行,此时__switch_to_asm才会返回当初把prev切走的那个任务指针(也就是当时的next),这个返回值会覆盖原prev变量。 - 所以代码执行到LOCATION(2)时,
prev已经不是当初被切走的任务指针,而是切换回当前任务的前一个任务指针。 - 补充:虽然rsi和rdx寄存器在
__switch_to_asm中未被保存,但栈切换后,原prev变量所在的栈不再是当前CPU的执行栈,直到任务恢复时才会回到这个栈,此时__switch_to_asm的返回值会覆盖prev——栈切换本身不直接修改prev,但它是实现这种“延迟赋值”的前提,没有栈切换就不会有后续的返回值覆盖操作。
附对应内核代码(v5.8.6):
static __always_inline struct rq * context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next, struct rq_flags *rf) { prepare_task_switch(rq, prev, next); /* * For paravirt, this is coupled with an exit in switch_to to * combine the page table reload and the switch backend into * one hypercall. */ arch_start_context_switch(prev); /* * kernel -> kernel lazy + transfer active * user -> kernel lazy + mmgrab() active * * kernel -> user switch + mmdrop() active * user -> user switch */ if (!next->mm) { // to kernel enter_lazy_tlb(prev->active_mm, next); next->active_mm = prev->active_mm; if (prev->mm) // from user mmgrab(prev->active_mm); else prev->active_mm = NULL; } else { // to user membarrier_switch_mm(rq, prev->active_mm, next->mm); /* * sys_membarrier() requires an smp_mb() between setting * rq->curr / membarrier_switch_mm() and returning to userspace. * * The below provides this either through switch_mm(), or in * case 'prev->active_mm == next->mm' through * finish_task_switch()'s mmdrop(). */ switch_mm_irqs_off(prev->active_mm, next->mm, next); if (!prev->mm) { // from kernel /* will mmdrop() in finish_task_switch(). */ rq->prev_mm = prev->active_mm; prev->active_mm = NULL; } } rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP); prepare_lock_switch(rq, next, rf); // LOCATION (1) /* Here we just switch the register state and the stack. */ switch_to(prev, next, prev); // LOCATION (2) barrier(); return finish_task_switch(prev); }
内容的提问来源于stack exchange,提问作者user3882729
相关产品推荐
相关产品推荐

