进程切换时,首先通过shedule函数完成进程调度,取出当前进程(prev),并决定那一个进程作为下一个进程(next),然后通过context_switch进行进程上下文切换,将next进程调入cpu执行.
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);
}
content_switch 函数有三个参数:rq、prev、next,其中 rq 指向本次进程切换发生的 running queue;prev 和 next 分别指向切换前后进程的进程描述符。
在进程切换过程中,content_switch 函数中首先调用了prepare_task_switch、arch_start_context_switch函数,其中prepare_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()进行寄存器和堆栈的切换。在swtich_to函数中又进一步调用了 __switch_to_asm, __switch_to_asm函数负责进行了从prev内核堆栈到next内核堆栈的切换。
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)
最后通过jmp指令跳转到_switch_to()函数,在_switch_to()函数的结尾调用return返回,_switch_to()返回后,将回到next进程的内核堆栈。
使用_shedule完成进程调度,确定调入进程,然后使用context_switch完成进程上下文切换,其中通过在swtich_to函数中调用 __switch_to_asm函数完成prev到next进程的内存堆栈切换,最后跳转到_switch_to()函数,待_switch_to()函数返回后,即切换到nex进程的内核堆栈。
总结:
在进程切换时,首先通过 schedule 函数完成进程调度,选择当前进程(prev)并决定下一个进程(next)。然后通过 context_switch 函数进行进程上下文切换,将 next 进程调入 CPU 执行。context_switch 函数有三个参数:rq、prev 和 next。其中 rq 指向本次进程切换发生的运行队列;prev 和 next 分别指向切换前后进程的进程描述符。在进程切换过程中,首先调用了 prepare_task_switch 和 arch_start_context_switch 函数,然后进行地址空间切换,之后调用 switch_to() 进行寄存器和堆栈的切换。最后通过跳转到 _switch_to() 函数,在 _switch_to() 函数的结尾调用返回,返回后将回到 next 进程的内核堆栈。