Linux内核的进程切换(上)


硬件上下文概念

尽管每个进程都可以拥有属于自己的地址空间,但所有进程必须共享CPU寄存器。因此在,在恢复一个进程执行之前,内核必须确保每个寄存器装入了挂起进程时的值。而这些必须装入的寄存器中的数据就称为硬件上下文。

任务状态段

80X86体系结构:
包括一个特殊的段类型,叫任务状态段(TSS),作用是存放硬件上下文。段是x86的概念,在保护模式下,段选择符参与寻址,段选择符在段寄存器中,而tss段则在tr寄存器中。当进程切换的时候就将

intel的建议:
为每一个进程准备一个独立的tss段,进程切换的时候切换tr寄存器使之指向该进程对应的tss段,然后在任务切换时(比如涉及特权级切换的中断)使用该段保留所有的寄存器。

Linux设计:
Linux内核并没有遵从Itel的建议为每个进程设置一个TSS,而是为每个CPU设置了一个TSS,tr寄存器保存该段。

进程切换 概念篇

按照Intel的设计思路,进程切换的硬件上下文要保存在TSS中,这是可以的,因为每个进程都有TSS,然而Linux中每个CPU有一个TSS,所有进程共享一个TSS,那么切换进程时的硬件上下文保存在哪里呢?答案是进程描述符中。在linux中,进程描述符有一个thread_struct类型的字段thread,只要进程被切换出去,内核就把硬件上下文保存在这个结构里面,这个结构里面包含了大部分的CPU寄存器,但是不不包括eax、ebx等等这些通用寄存器,它们的值放在哪里呢?自然是内核栈中啦!进程切换只发生在内核态,执行切换之前,用户进程使用的所有寄存器保存在内核堆栈中,这也包括ss和esp这对寄存器。

小总结:
执行进程切换时,硬件上下文保存在两个地方:

task_struct的thread字段:这里保存了大部分的寄存器,如esp,eip。 内核态堆栈:状态寄存器、通用寄存器,如eax,ebx…

那么现在有个问题,既然Linux既然使用thread_struct保存硬件上下文,为什么要为每个CPU设计一个TSS呢?

理由有二:

当80x86的CPU从用户态切换到内核态时候需要找到内核态堆栈的地址,地址去哪里找呢?就是TSS中。进程切换时,只更新tss段中的esp0/ss0字段,使其指向新进程的内核栈。 当用户态进程师徒通过一个in或者out访问一个端口的时候,CPU需要访问存放在TSS中的I/O许可权位图已检查进程是否有访问端口的权利。

由于进程切换不更换TSS本身,也就是根本不更换TR的内容。这是因为,改变TSS中SS0和ESP0所化的开销比通过装入TR以更换一个TSS要小得多。因此,在Linux内核中,TSS并不是属于某个进程的资源,而是全局性的公共资源。在多处理机的情况下,尽管内核中确实有多个TSS,但是每个CPU仍旧只有一个TSS,并且使用init_tass数组表示多个TSS,如果有N个CPU,那么数组的长度是N。

上述理由说明了在Linux中TSS的作用。到目前为止还有2个问题没有阐述清楚,即进程切换的时候,Linux是如何更新tss的内容的?thread_struct结构体到底长什么样子?下面就来解答这些问题。

执行进程切换

从本质上说,每个进程切换由两步组成:

切换页全局目录以安装一个新的地址空间。 切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包括CPU寄存器。
其中第一步是进程地址空间的内容,这里不再描述,有兴趣的可以看深入理解Linux内核的第九章,我们重点关注的是第二步,这步是由switch_to宏执行,切换的具体过程放在下篇介绍吧。

相关内容