想想一个线程何以区别于其他线程。由于线程是负责“执行”,因此我们要通过线程当前的执行状态(也称线程上下文,线程状态,Context)来描述线程的当前执行情况(也称执行现场)。也就包括:
CPU 各寄存器的状态:
简单想想,我们会特别关心程序运行到了哪里:即
;还有栈顶的位置:即
。
当然,其他所有的寄存器都是一样重要的。
一个线程不会总是占据 CPU 资源,因此在执行过程中,它可能会被切换出去;之后的某个时刻,又从其他线程切换回来,为了线程能够像我们从未将它切换出去过一样继续正常执行,我们要保证切换前后线程的执行状态不变。
其他线程不会修改当前线程的栈,因此栈上的内容保持不变;但是 CPU 跑去执行其他代码去了,CPU 各寄存器的状态势必发生变化,所以我们要将 CPU 当前的状态(各寄存器的值)保存在当前线程的栈上,以备日后恢复。但是我们也并不需要保存所有的寄存器,事实上只需保存:
返回地址
页表寄存器
这与线程切换的实现方式有关,我们到时再进行说明。
首先是线程在栈上保存的内容:
前三个分别对应
,那最后为什么还有个中断帧呢?实际上,我们通过中断帧,来利用中断机制的一部分来进行线程初始化。我们马上就会看到究竟是怎么回事。
对于一个被切换出去的线程,为了能够有朝一日将其恢复回来,由于它的状态已经保存在它自己的栈上,我们唯一关心的就是其栈顶的地址。我们用结构体 Context
来描述被切换出去的线程的状态。
随后开一个新的 process
mod ,在里面定义线程结构体 Thread
。
里面用到了内核栈 KernelStack
:
在使用 KernelStack::new
新建一个内核栈时,我们使用第四章所讲的动态内存分配,从堆上分配一块虚拟内存作为内核栈。然而 KernelStack
本身只保存这块内存的起始地址。其原因在于当线程生命周期结束后,作为 一部分的 KernelStack
实例被回收时,由于我们实现了 Drop
Trait ,该实例会调用 drop
函数将创建时分配的那块虚拟内存回收,从而避免内存溢出。当然。如果是空的栈就不必回收了。
下一节,我们来看如何进行线程切换。