跟踪分析进程切换的过程完成实验,总结进程切换的工作机制以及sp和ip在不同体系结构下汇编代码的切换方法,深入理解进程切换
本文以 Linux 内核中的上下文切换函数 content_switch 为中心,分析 Linux 5.4.34 版本内核中进程切换的基本操作与基本代码框架。
一、context_switch函数
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);
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev);
barrier();
return finish_task_switch(prev);
}
二、函数解析
1.函数参数解析
content_switch(rq、prev、next) 函数中有三个参数:rq、prev、next,其中 rq 指向本次进程切换发生的运行队列;prev 和 next 分别指向切换前后进程的进程描述符。
2.函数具体作用解析
-
prepare_task_switch()
该函数准备切换任务,调用一些体系结构特有的hook,必须和 finish_task_switch配合使用
-
arch_start_context_switch()
该函数给各个体系结构专有的开始上下文切换的工作提供了入口,不同体系结构的实现不同。
-
if (!next->mm) { // 1 enter_lazy_tlb(prev->active_mm, next); // 2 next->active_mm = prev->active_mm; // 3 if (prev->mm) // 4 mmgrab(prev->active_mm); else // 5 prev->active_mm = NULL; } else { // 6 membarrier_switch_mm(rq, prev->active_mm, next->mm); switch_mm_irqs_off(prev->active_mm, next->mm, next); // 7 if (!prev->mm) { // 8 rq->prev_mm = prev->active_mm; prev->active_mm = NULL; } }
上述代码切换进程的地址空间,可以看到四种内核态,用户态之间的组合切换调用的函数组合
-
switch_to(rq、prev、next)
该函数进行寄存器和堆栈的切换,而switch_to()会调用_switch_to_asm()函数,在_switch_to_asm()的中实现从prev内核堆栈到next内核堆栈的切换,而在最后不使用ret指令返回,而是通过jmp指令跳转到_switch_to()函数,在_switch_to()函数的结尾调用return返回,因为在_switch_to_asm()中进行了堆栈的切换,因此_switch_to()返回后,回到的是next进程的内核堆栈,而不是prev进程的内核堆栈。