switch_to
last保存的是什么?
last = cpu_switch_to(prev, next)
- cpu_switch_to函数是一段汇编,根据arm64函数的调用规则来看,x0保存的是函数返回值,也就是prev进程;
- 比如A进程切换到B进程,那么last 就是进程 A;
为什么需要保存last?
有这么一个场景:B进程是一个内核线程;A进程切换到B进程,B进程切换到了C进程;
- 在A进程中,判断出了B进程是一个内核线程(
next->mm 为 NULL),所以B进程要借用A进程的mm_struct,对其进行了引用计数++; - B进程开始运行,这个过程中,要使用mm_struct,所以这个过程不能对mm_struct的引用计数--;那么只能在B进程结束,切换到C进程的时候对其引用计数--;
- 比如B 进程 调用 cpu_switch_to 切换到了C进程,last参数保存的就是B进程;
- 在C进程中的finish_task_switch 函数对last参数进行了处理;
C进程的函数返回执行点是哪里?为什么C进程能准确调用finish_task_switch?
- 比如在之前,C进程通过
context_switch->switch_to->cpu_switch_to切换到了X进程,过了一段时间,B进程调用到了C进程; - cpu_switch_to函数中,str lr, [x8]将lr保存进入了cpu_context中,lr是函数的返回地址,指向的就是cpu_switch_to函数的下条指令,也就是 reterun last;
- 那么切换到C进程执行时,执行的第一条指令就是return last;
- 进而就执行到了finish_task_switch对last进程完成了回收;
能否直接使用局部变量保存last?
可以看出上面的实现过程对last的保存很绕,通过x0寄存器对last进行了保存;
我能否在context_switch 函数中定义一个局部变量临时保存prev呢?也就是last。
不能,原因是:如果定义局部变量,那么prev是保存在了栈中,在cpu_switch_to函数中会对栈进行切换,比如B进程切换到了C进程,实际上在B进程的栈中保存了prev(B进程),切换到了C进程这个变量的值就变了;那么C进程就看不见B进程了;
如果是用局部变量:
C进程切换出去之前把自己保存进入了prev,B进程切换到C进程时,B进程把自己保存进了prev;那么B进程切换到C进程时,C进程看到的prev是C进程而不是B进程;
理解本质
栈只是一个寄存器而已,其特殊的地方在于它指向了一个地址(操作系统保证这块地址有效),进城切换时会把栈进行切换(给栈赋这个进程对应的栈值)。
x0能保存的原因是不会对x0进行切换,为什么不切换x0?这是因为arm64的调用规定中x0保存返回值。
为什么中断上下文不能睡眠

这是一个进程上下文切换的流程,task_struct A code比如主动调用__schedule换出,具体的换出点在cpu_switch_to切换硬件上下文中;
那么task_struct A被唤醒以后,会根据lr一路回退运行,又回到task_strcut A code __schedule()中的下一句代码;
中断关闭导致系统不能调度?所以不能睡眠
在A被唤醒以后,finish_task_switch会打开中断所以不存在这个问题;
具体原因
- 假如某个中断调度出去了,什么时候被调度回来呢?由于EOI信号没有发送,所以这个期间这个中断就无法再次触发了;
- 中断上下文是中断关闭的,假如这个中断进程被唤醒了,那它返回的时候会调用finish_task_switch会打开中断,如果这个期间有中断发生,就形成了中断嵌套,中断使用有一个特殊的中断栈,那么很可能就爆栈;
- 所以内核直接在schedule中直接提前判断,关闭了中断调度的处理;
疑问
schedule 的一个调度时机是:
el0_irq->ret_to_user->work_pending->do_notify_resume->schedule()
->kernel_exit->eret
可以看见schedule的调用也是在中断上下文中(还没完全退出中断),是否有问题呢?
已经调用irq_exit了,在软件层面上没有问题,但是还是处于实际的硬件环境。
应该没有:
不能切换的原因有两个:eoi信号,爆栈;
在ret_to_user之前,已经处理好了eoi信号,而且也完成了栈切换;
只是硬件层面没有打开中断而已,只是在finish_task_switch提前打开了中断,应该没问题;

浙公网安备 33010602011771号