11.9 XV6线程第一次调用switch函数

(注,首先是学生提问Linux内一个进程多个线程的实现方式,因为在XV6中,一个进程只有一个用户线程)

学生提问:操作系统都带了线程的实现,如果想要在多个CPU上运行一个进程内的多个线程,那需要通过操作系统来处理而不是用户空间代码,是吧?那这里的线程切换是怎么工作的?是每个线程都与进程一样了吗?操作系统还会遍历所有存在的线程吗?比如说我们有8个核,每个CPU核都会在多个进程的更多个线程之间切换。同时我们也不想只在一个CPU核上切换一个进程的多个线程,是吧?

Robert教授:Linux是支持一个进程包含多个线程,Linux的实现比较复杂,或许最简单的解释方式是:几乎可以认为Linux中的每个线程都是一个完整的进程。Linux中,我们平常说一个进程中的多个线程,本质上是共享同一块内存的多个独立进程。所以Linux中一个进程的多个线程仍然是通过一个内存地址空间执行代码。如果你在一个进程创建了2个线程,那基本上是2个进程共享一个地址空间。之后,调度就与XV6是一致的,也就是针对每个进程进行调度。

学生提问:用户可以指定将线程绑定在某个CPU上吗?操作系统如何确保一个进程的多个线程不会运行在同一个CPU核上?要不然就违背了多线程的初衷了。

Robert教授:这里其实与XV6非常相似,假设有4个CPU核,Linux会找到4件事情运行在这4个核上。如果并没有太多正在运行的程序的话,或许会将一个进程的4个线程运行在4个核上。或者如果有100个用户登录在Athena机器上,内核会随机为每个CPU核找到一些事情做。

如果你想做一些精细的测试,有一些方法可以将线程绑定在CPU核上,但正常情况下人们不会这么做。

学生提问:所以说一个进程中的多个线程会有相同的page table?

Robert教授:是的,如果你在Linux上,你为一个进程创建了2个线程,我不确定它们是不是共享同一个的page table,还是说它们是不同的page table,但是内容是相同的。

学生提问:有没有原因说这里的page table要是分开的?

Robert教授:我不知道Linux究竟用了哪种方法。

(注,以下是线程第一次调用switch的过程)

学生提问:当调用swtch函数的时候,实际上是从一个线程对于switch的调用切换到了另一个线程对于switch的调用。所以线程第一次调用swtch函数时,需要伪造一个“另一个线程”对于switch的调用,是吧?因为也不能通过swtch函数随机跳到其他代码去。

Robert教授:是的。我们来看一下第一次调用switch时,“另一个”调用swtch函数的线程的context对象。proc.c文件中的allocproc函数会被启动时的第一个进程和fork调用,allocproc会设置好新进程的context,如下所示:

实际上大部分寄存器的内容都无所谓。但是ra很重要,因为这是进程的第一个switch调用会返回的位置。同时因为进程需要有自己的栈,所以ra和sp都被设置了。这里设置的forkret函数就是进程的第一次调用swtch函数会切换到的“另一个”线程位置。

学生提问:所以当swtch函数返回时,CPU会执行forkret中的指令,就像forkret刚刚调用了swtch函数并且返回了一样?

Robert教授:是的,从switch返回就直接跳到了forkret的最开始位置。

学生提问:因吹斯听,我们会在其他场合调用forkret吗?还是说它只会用在这?

Robert教授:是的,它只会在启动进程的时候以这种奇怪的方式运行。下面是forkret函数的代码,

从代码中看,它的工作其实就是释放调度器之前获取的锁。函数最后的usertrapret函数其实也是一个假的函数,它会使得程序表现的看起来像是从trap中返回,但是对应的trapframe其实也是假的,这样才能跳到用户的第一个指令中。

学生提问:与之前的context对象类似的是,对于trapframe也不用初始化任何寄存器,因为我们要去的是程序的最开始,所以不需要做任何假设,对吧?

Robert教授:我认为程序计数器还是要被初始化为0的。

因为fork拷贝的进程会同时拷贝父进程的程序计数器,所以我们唯一不是通过fork创建进程的场景就是创建第一个进程的时候。这时需要设置程序计数器为0。

学生提问:在fortret函数中,if(first)是什么意思?

Robert教授:文件系统需要被初始化,具体来说,需要从磁盘读取一些数据来确保文件系统的运行,比如说文件系统究竟有多大,各种各样的东西在文件系统的哪个位置,同时还需要有crash recovery log。完成任何文件系统的操作都需要等待磁盘操作结束,但是XV6只能在进程的context下执行文件系统操作,比如等待I/O。所以初始化文件系统需要等到我们有了一个进程才能进行。而这一步是在第一次调用forkret时完成的,所以在forkret中才有了if(first)判断。

Last updated