我正在尝试了解 Linux 内核(特别是 x86)中上下文切换的实现,为此我有几个问题。
为什么
switch_to()
宏的定义和调用如下所示?
下面注释中位置 (1) 中
prev
的值是否与位置 (2) 中的值相同,或者作为 switch_to()
一部分的堆栈切换是否会改变它?如果 prev
和 next
存储在寄存器 rsi
和 rdx
中,而它们似乎没有保存在 __switch_to_asm()
中,我会假设 prev
在位置 (1) 和 (2) 中是相同的),但考虑到宏的定义,也许情况并非如此?
#define switch_to(prev, next, last) \
do { \
((last) = __switch_to_asm((prev), (next))); \
} while (0)
从 Linux 内核 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);
}
为什么
宏的定义和调用如下所示(下面引用)?switch_to()
我只能假设此功能是作为宏实现的,因为它依赖于架构,并且可能需要的不仅仅是简单的函数调用(就像您在 x86 中看到的那样)。这是特定于 arch 的代码的常见模式。您可以在here查看宏的不同实现。
下面注释中位置 (1) 中
的值与位置 (2) 中的值相同,还是作为prev
一部分的堆栈切换会更改它?switch_to()
堆栈开关并不重要。
prev
的值既不会从堆栈中取出,也不会保存到堆栈中。 prev
的新值将只是 __switch_to_asm()
的返回值,即 __switch_to()
的返回值(因为前者对后者执行尾部调用)。由于 __switch_to()
按原样返回传递的 prev
(参见代码),因此最终结果是 prev
值保持不变。
这是有道理的,你不希望通过
prev
切换上下文后switch_to()
的值发生变化。如果您使用 prev=A next=B
进行安排,并且之前 B
被 prev=B next=C
切换掉,那么您希望 B
使用 prev=A
恢复,而不是 prev=B
!