lab5:深入理解进程切换
Linux系统中的进程调度过程可以总结为以下几个方面:
1.调度选择从schedule()函数开始,在该函数中进行进程的选择和切换。
2.通过解读switch_to宏中CPU的值的变化,可以分析进程切换的具体执行过程。
3.在进程切换时,堆栈会发生切换位置,切换前后current_thread_info变量也会发生变化。
4.进程切换还涉及到地址空间的切换,但这不会影响后续切换代码的执行。
5.Current宏代表的进程发生变化的源码位置,标志着进程切换的发生。
6.任务状态段中关于内核堆栈的信息发生变化的源码位置,也是进程切换的一部分。
关键函数的调用关系:
schedule() —> context_switch() —> switch_to —> __switch_to()
在Linux内核中,schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换。部分函数具体代码如下,一个调度新进程,一个是进行上下文切换,还有相关堆栈信息的保存。
static void __sched notrace __schedule(bool preempt)
{
struct task_struct *prev, *next;
......
next = pick_next_task(rq, prev, &rf);
......
if (likely(prev != next)) {
......
rq = context_switch(rq, prev, next, &rf);
} else {
......
}
balance_callback(rq);
}
next= pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
context_switch(rq,prev, next);//进程上下文切换
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
......
if (!next->mm) {
enter_lazy_tlb(prev->active_mm, next);
next->active_mm = prev->active_mm;
if (prev->mm)
mmgrab(prev->active_mm);
else
prev->active_mm = NULL;
} else {
membarrier_switch_mm(rq, prev->active_mm, next->mm);
switch_mm_irqs_off(prev->active_mm, next->mm, next);
if (!prev->mm) {
rq->prev_mm = prev->active_mm;
prev->active_mm = NULL;
}
}
......
switch_to(prev, next, prev);
......
}
switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程.内核在switch_to中执行如下操作:
1.进程切换, 即esp的切换, 由于从esp可以找到进程的描述符
2.硬件上下文切换, 设置ip寄存器的值, 并jmp到__switch_to函数
3.堆栈的切换, 即ebp的切换, ebp是栈底指针, 它确定了当前用户空间属于哪个进程
swtich_to 紧接着调用 __switch_to_asm,x86下汇编代码如下:
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)
总结
Linux系统的进程切换的一般执行过程是这样的,从进程X转向进程Y的过程是这样的。
1.正在运行的用户态进程X 。
2.发生中断——save cs:eip/esp/eflags(current)to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point tokernel stack)。
3.SAVE_ALL//保存现场。
4.中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换。
5.标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)。
6.restore_all //恢复现场。
7.iret- pop cs:eip/ss:esp/eflags from kernel stack //恢复。
8.继续运行用户态进程Y