在ARM64架构上运行的Linux 5.7内核中,进程上下文切换是通过一系列的函数调用完成的,这些函数负责保存当前任务的状态,并恢复新任务的状态。以下是进程上下文切换的详细流程,以及涉及的关键函数和代码片段。
进程上下文切换的步骤
-
保存当前任务的上下文:保存当前任务的寄存器状态,包括通用寄存器、程序计数器等。
-
调用
prepare_task_switch
:这个函数负责一些准备工作,比如更新当前任务的结束时间戳。 -
检查新任务的内存空间:判断新任务是否需要切换内存空间(Memory Management,mm)。
-
检查新任务是否是内核线程:内核线程不需要切换内存空间,因为它们不使用用户空间。
-
调用
__switch_to
:这是实际进行上下文切换的函数,它会保存当前任务的状态,并恢复新任务的状态。 -
恢复新任务的上下文:加载新任务的寄存器状态,包括通用寄存器、程序计数器等。
代码分析
以下是一些关键函数的代码片段,这些代码片段用于说明上下文切换的过程:
/*
* prepare_task_switch 是进行任务切换前的准备工作,比如更新当前任务的结束时间戳。
*/
static inline void prepare_task_switch(struct task_struct *prev)
{
// 更新当前任务的结束时间戳
prev->se.end_time = sched_clock();
}
/*
* task_switch_mm 是检查新任务是否需要切换内存空间的函数。
*/
static inline bool task_switch_mm(struct mm_struct *mm, struct task_struct *next)
{
return next->mm != mm;
}
/*
* __switch_to 是实际进行上下文切换的函数。
*/
static __always_inline struct task_struct *
__switch_to(struct task_struct *prev, struct task_struct *next)
{
if (task_switch_mm(prev->mm, next)) {
mmdrop(prev->active_mm);
next->active_mm = next->mm;
atomic_inc(&next->mm->mm_count);
}
// 切换内存空间
switch_mm_irqs_off(prev, next, prev->mm);
// 检查新任务是否是内核线程
if (next->flags & PF_KTHREAD) {
// 内核线程不需要切换到用户空间
next->thread.sp = (unsigned long)kernel_stack_next;
} else {
// 用户线程需要设置用户空间的栈指针
next->thread.sp = next->thread.usp;
}
// 保存当前任务的寄存器状态
prev->thread.sp = (unsigned long)__builtin_frame_address(0);
// 恢复新任务的寄存器状态
return next;
}
详细流程
-
当
schedule_tail()
被调用时,它会首先调用prepare_task_switch()
,保存当前任务的结束时间戳。 -
然后,它会检查新任务是否需要切换内存空间,这通过
task_switch_mm()
函数完成。 -
如果需要切换内存空间,它会释放当前任务的内存空间引用,并增加新任务内存空间的引用计数。
-
接下来,调用
__switch_to()
函数,这个函数会执行实际的上下文切换。 -
在
__switch_to()
中,首先检查新任务是否是内核线程。如果是,它将使用内核栈;如果不是,它将使用用户栈。 -
然后,保存当前任务的栈指针,并将新任务的栈指针设置为当前栈指针。
-
最后,
__switch_to()
函数返回新任务的指针,这样调度器就可以继续执行新任务。