LAB5 深入理解进程切换
本文以Linux 内核中的上下文切换函数 content_switch 为中心,分析并深入理解进程切换。
进程切换的框架主要位于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);
}
该函数实现了在一个 CPU 的运行队列上进行进程切换的操作。具体来说,该函数将当前正在运行的进程 prev 切换到下一个要运行的进程 next。
首先,函数调用 prepare_task_switch() 函数来准备进程切换。接着,调用 arch_start_context_switch() 函数来开始进程上下文的切换。
接下来是关于内存管理的操作,会根据进程的类型(内核空间进程或用户空间进程)进行不同的处理。如果切换到内核空间进程,则会进入“懒 TLB”(lazy tlb模式,并且直接使用前一个进程的地址空间;如果切换到用户空间进程,则需要切换地址空间,并调用 membarrier_switch_mm() 函数和 switch_mm_irqs_off() 函数进行一些额外的操作。
然后,函数调用 prepare_lock_switch() 函数准备锁的切换。最后,函数调用 switch_to() 函数来进行寄存器状态和栈的切换,并返回 finish_task_switch() 函数来完成进程切换。
context_switch中的一个重要函数是switch_to,switch_to调用了 __switch_to_asm
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) // 保存旧进程的栈顶
movq TASK_threadsp(%rsi), %rsp // 恢复新进程的栈顶
/* restore callee-saved registers */
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
popq %rbp
jmp __switch_to
END(__switch_to_asm)
这个函数是处理操作系统中进程切换的汇编代码,具体作用是将当前正在运行的进程切换为被调度的下一个进程。
ENTRY(__switch_to_asm)
定义一个名为__switch_to_asm的标号,作为进程切换函数的入口。
pushq %rbp
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
将寄存器%rbp、%rbx、%r12、%r13、%r14和%r15的值保存到当前进程的内核栈中,这些寄存器属于被调用者保护的寄存器,在调用函数时需要保存其值,以便在函数返回时进行恢复。
movq %rsp, TASK_threadsp(%rdi)
movq TASK_threadsp(%rsi), %rsp
将当前进程所使用的内核栈顶指针保存到旧进程的task_struct结构中的thread.sp字段中,并将新进程的task_struct结构中的thread.sp字段中保存的值作为新进程的内核栈顶指针。
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
popq %rbp
从当前进程的内核栈中弹出之前保存的%rbp、%rbx、%r12、%r13、%r14和%r15寄存器的值,并恢复其原本的值。
jmp __switch_to
END(__switch_to_asm)
跳转到C语言实现的__switch_to函数,完成进程切换操作。END指令用于标识函数结尾。