11.3 XV6线程切换(一)

接下来我将通过两张图来介绍XV6中的线程切换是如何实现的,其中一张图是简单的,另一张图包含了更多的细节,这一小节先看简单的图。

我们或许会运行多个用户空间进程,例如C compiler(CC),LS,Shell,它们或许会,也或许不会想要同时运行。在用户空间,每个进程有自己的内存,对于我们这节课来说,我们更关心的是每个进程都包含了一个用户程序栈(user stack),并且当进程运行的时候,它在RISC-V处理器中会有程序计数器和寄存器。当用户程序在运行时,实际上是用户进程中的一个用户线程在运行。如果程序执行了一个系统调用或者因为响应中断走到了内核中,那么相应的用户空间状态会被保存在程序的trapframe中(注,详见lec06),同时属于这个用户程序的内核线程被激活。所以首先,用户的程序计数器,寄存器等等被保存到了trapframe中,之后CPU被切换到内核栈上运行,实际上会走到trampoline和usertrap代码中(注,详见lec06)。之后内核会运行一段时间处理系统调用或者执行中断处理程序。在处理完成之后,如果需要返回到用户空间,trapframe中保存的用户进程状态会被恢复。

除了系统调用,用户进程也有可能是因为CPU需要响应类似于定时器中断走到了内核空间。上一节提到的pre-emptive scheduling,会通过定时器中断将CPU运行切换到另一个用户进程。在定时器中断程序中,如果XV6内核决定从一个用户进程切换到另一个用户进程,那么首先在内核中第一个进程的内核线程会被切换到第二个进程的内核线程。之后再在第二个进程的内核线程中返回到用户空间的第二个进程,这里返回也是通过恢复trapframe中保存的用户进程状态完成。

当XV6从CC程序的内核线程切换到LS程序的内核线程时:

  1. XV6会首先会将CC程序的内核线程的内核寄存器保存在一个context对象中。

  2. 类似的,因为要切换到LS程序的内核线程,那么LS程序现在的状态必然是RUNABLE,表明LS程序之前运行了一半。这同时也意味着LS程序的用户空间状态已经保存在了对应的trapframe中,更重要的是,LS程序的内核线程对应的内核寄存器也已经保存在对应的context对象中。所以接下来,XV6会恢复LS程序的内核线程的context对象,也就是恢复内核线程的寄存器。

  3. 之后LS会继续在它的内核线程栈上,完成它的中断处理程序(注,假设之前LS程序也是通过定时器中断触发的pre-emptive scheduling进入的内核)。

  4. 然后通过恢复LS程序的trapframe中的用户进程状态,返回到用户空间的LS程序中。

  5. 最后恢复执行LS。

这里核心点在于,在XV6中,任何时候都需要经历:

  1. 从一个用户进程切换到另一个用户进程,都需要从第一个用户进程接入到内核中,保存用户进程的状态并运行第一个用户进程的内核线程。

  2. 再从第一个用户进程的内核线程切换到第二个用户进程的内核线程。

  3. 之后,第二个用户进程的内核线程暂停自己,并恢复第二个用户进程的用户寄存器。

  4. 最后返回到第二个用户进程继续执行。

这么曲折的一个线路。

学生提问:线程调度会发生在这个过程中,是吗?

Robert教授:是的,我接下来会介绍线程调度器。

Last updated