调度器在进程调度的时候,主要实现两个功能:
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;
};