调度器在进程调度的时候,主要实现两个功能:


1.选择下一个要运行的进程

2.context_switch来进行上下文切换


进程切换总结为两步:

(1)​切换进程的进程地址空间​。将 next进程的页表切换到硬件页表中,由switch_mm实现。

(2)​切换next进程的内核栈和硬件上下文​。由switch_to函数实现,硬件上下文提供了内核执行next进程的所有硬件信息。

switch_mm

switch_mm是将新进程的页表基地址设置到页表基地址寄存器,刷新TLB及设置硬件页表。

如何提高TLB的性能?对于TLB来说,可以分为全局和进程独有两种类型。为了支持进程独有类型的TLB,ARM提出了一种硬件解决方案ASID(Address Space ID),这样TLB就可以识别哪些TLB属于某个进程,每个TLB都包含一个ASID。ASID机制从硬件上保证了进程切换时TLB不会出现冲突。

switch_to

#define switch_to(prev, next, last)         \
do { \
((last) = __switch_to((prev), (next))); \
} while (0)

#endif

第一个参数表示将要被调度出去的进程prev,第二个参数表示将要被调度进来的进程next,第三个参数表示需要对prev进程进行清理,也就是传入prev进程。

/*
* Thread switching.
*/
__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
struct task_struct *next)
{
struct task_struct *last;

fpsimd_thread_switch(next);
tls_thread_switch(next);
hw_breakpoint_thread_switch(next);
contextidr_thread_switch(next);
entry_task_switch(next);
ssbs_thread_switch(next);
erratum_1418040_thread_switch(next);
ptrauth_thread_switch_user(next);

// 完成这个CPU上任何悬而未决的TLB或缓存维护,
// 以防线程迁移到不同的CPU上。这个全屏障也是membarrier系统调用所要求的。

dsb(ish);

/*
* MTE thread switching must happen after the DSB above to ensure that
* any asynchronous tag check faults have been logged in the TFSR*_EL1
* registers.
*/
mte_thread_switch(next);
/* avoid expensive SCTLR_EL1 accesses if no change */
if (prev->thread.sctlr_user != next->thread.sctlr_user)
update_sctlr_el1(next->thread.sctlr_user);

/* the actual thread switch */
last = cpu_switch_to(prev, next);

return last;
}

cpu_context

cpu_context保存了硬件上下文信息,进程切换时,我们需要将prev进程的x19-x28寄存器以及fp,sp和pc寄存器内容保存到cpu_context结构中,然后把next进程上一次保存的数据恢复到实际硬件的寄存器中,这样就完成了进程的上下文切换。

struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21;
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp;
unsigned long sp;
unsigned long pc;
};