linux 中进程切换涉及到一个调用链:
schedule() –> context_switch() –> switch_to –> __switch_to()
先是 context_switch 函数,content_switch 函数位于 Linux 内核源码目录的 kernel/sched/core.c 中,是 schedule 函数中实现进程切换的函数.
content_switch 函数有三个参数:rq、prev、next,其中 rq 指向本次进程切换发生的 running queue;prev 和 next 分别指向切换前后进程的进程描述符。
/*
* context_switch - switch to the new MM and the new thread's register state.
*/
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. prepare_task_switch
在进程切换之前,首先执行调用每个体系结构都必须定义的prepare_task_switch挂钩,,这使得内核执行特定于体系结构的代码,,为切换做事先准备。
static inline void
prepare_task_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next)
{
sched_info_switch(rq, prev, next);
perf_event_task_sched_out(prev, next);
fire_sched_out_preempt_notifiers(prev, next);
prepare_lock_switch(rq, next);
prepare_arch_switch(next);
}
2. arch_start_context_switch
函数给各个体系结构专有的开始上下文切换的工作提供了入口。
3. prepare_lock_switch
与死锁检测 lockdep 有关。
4. switch_to
最后用switch_to完成了进程的切换,该函数切换了寄存器状态和栈,新进程在该调用后开始执行,而switch_to之后的代码只有在当前进程下一次被选择运行时才会执行。
执行环境的切换是在switch_to()中完成的, switch_to完成最终的进程切换,它保存原进程的所有寄存器信息,恢复新进程的所有寄存器信息,并执行新的进程。
内核在switch_to中执行如下操作:
进程切换, 即esp的切换, 由于从esp可以找到进程的描述符
硬件上下文切换, 设置ip寄存器的值, 并jmp到__switch_to函数
堆栈的切换, 即ebp的切换, ebp是栈底指针, 它确定了当前用户空间属于哪个进程
5. finish_task_switch
这个函数和前面的prepare_task_switch相对应。finish_task_switch完成一些清理工作, 使得能够正确的释放锁。