上下文切换
上下文切换是实际资源的所有权转让发生的地方。这里更多的考虑是硬件相关的实际操作,例如 tlb 的切换、内存的释放等。
主要流程:
- 切换前的准备工作
- 将下一个任务的 on_cpu 状态设置为 1;
- 代码覆盖率切换;
- 统计任务在队列上的等待时间;
- perf 性能计时器的切换;
- 发送 sched out 抢占通知;
- 32 位系统中高端内存的映射切出;
- 构架相关的准备工作;
- 切换开始时,CPU 构架需要做的工作;
- 当下一个任务是内核任务时(内核任务没有用户态部分的内存)
- 告诉内核不需要进行 tlb 切换;
- 借用上个任务的用户态内存;
- 如果上个任务为用户任务,则增加内存的引用计数器,防止上个任务退出时将内存释放掉;
- 如果上个任务也是内核任务,则直接将内存转交给下个任务;
- 当下一个任务是用户任务时:
- 同步内存屏障状态;
- 切换 tlb、mmu 和加载页表;
- 如果上个任务是内核任务:
- 在 rq->prev_mm 中记录该任务——上个任务会清除 active_mm 记录,而该块内存所属的任务有可能已经退出,所以在这里需要由 rq 来进行记录并在后续释放;
- 清除上个任务的 active_mm ;
- 清除 rq 的时钟的统计跳过标记;
- 进行实际的上下文切换:
- 变更内核栈(用户部分已经在 switch_mm 部分变更);
- 切换寄存器的内容;
- 在执行最后部分之前插入内存屏障,保证之前的指令都已经完成;
- 调用
finish_task_switch()
函数对上个任务进行最后的处理,该函数与之前的prepare_task_switch()
函数成对使用:- 修正抢占计数器;
- 统计上个任务的 idle/system 时间;
- 到这里前一个任务已经被切出 CPU 这里需要更新上个任务的 on_cpu 状态;
- 重新评估 nohz 状态,检查是否需要重新启用定时器;
- 做一次负载均衡;
- 调用构架相关的函数在上下文切换完成后进行一些处理工作;
- 代码覆盖率切换;
- 处理高端内存的映射切入;
- 发送 sched in 通知,表明任务切换已经完成;
- 如果上个任务死亡:
- 调用上个任务调度类的 task_dead() 函数做后续处理,例如 cfs 调度类会删除该任务对应的负载贡献值;
- 处理上个任务的引用计数器;
- 延迟释放 bpf & perf 使用的内存空间;
任务的上下文切换主要做四件事情:
- 切换进程的空间,也即虚拟内存;
- 切换寄存器和 CPU 资源;
- 标记任务状体( on_cpu );
- 以及死亡任务的后续处理;