上下文:描述了任务的执行状态。包括任务执行过程中堆栈中的内容和寄存器值,获取的资源信息,自身状态等
上下文切换:当一个任务切换到另一个任务时,它需要保存当前任务的所有状态,即保存当前任务的上下文,以便在再次执行该任务时,能恢复之前的状态,继续执行。
Linux 内核中进程上下文切换的步骤主要包括以下几个部分:
- 保存当前进程的寄存器信息和堆栈信息;
- 保存当前进程的上下文信息(task_struct 结构体);
- 切换进程地址空间(如果需要);
- 切换内核堆栈;
- 恢复下一个进程的上下文信息;
- 恢复下一个进程的堆栈和寄存器信息;
- 跳转到下一个进程的执行点。
源码位于 kernel/sched/core.c
/*
* context_switch - 上下文切换
参数:当前进程所在cpu的运行队列rq
当前正在运行任务 prev 和即将运行的任务 next
用于标记上下文切换的一些标志rf
*/
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
struct mm_struct *mm, *oldmm;
prepare_task_switch(rq, prev, next);
mm = next->mm; //新任务的地址空间
oldmm = prev->active_mm; //当前任务的
arch_start_context_switch(prev);
/*
内核线程是没有用户地址空间的概念的,所有的内核线程使用的都是内核地址空间,
所以它的mm指向NULL,但是需要发生调度的时候肯定要地址空间,需要向前一个进程借用,
所以其tsk->active_mm指向前进程的mm_struct,然后将前进程的mm_count+1避免销毁。
*/
if (!mm) {
/*
注意此时如果prev也是内核线程,
则oldmm为NULL, 即next->active_mm也为NULL
*/
next->active_mm = oldmm;
mmgrab(oldmm);
/* 切换到一个内核线程时,虚拟地址空间的用户部分不需要切换
(因为内核线程几乎不会访问用户进程地址空间)
* 这种加速上下文切换的技术称为惰性TLB
如果不是切换到内核线程,则需要切换用户虚拟地址空间的TLB*/
enter_lazy_tlb(oldmm, next);
} else
//不是内核线程, 则需要切换虚拟地址空间
switch_mm_irqs_off(oldmm, mm, next);
//如果prev是内核线程或正在退出的进程
if (!prev->mm) {
prev->active_mm = NULL;
rq->prev_mm = oldmm;
}
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.
切换堆栈和寄存器:
将旧进程的栈指针sp,pc,寄存器值保存到旧进程的cpu_context结构体中
恢复新进程中cpu_context中保存的内容
新进程在该调用后开始执行,
而switch_to之后的代码只有在当前进程下一次被选择运行时才会执行
*/
switch_to(prev, next, prev);
/* 优化屏障, barrier() __asm__ __volatile__("": : :"memory"
确保了switch_to和finish_task_switch的执行顺序
阻止编译器优化代码执行顺序
*/
barrier();
return finish_task_switch(prev);
}