在一般操作系统中,切换的动作为0.01~0.03秒就会执行一次,本身切换会占用0.0001秒左右。基本上会占据1%的算力。

切换时,将当前寄存器等所有上下文保存起来,读取下一个上下文,就完成了一次切换。关键就是在于TSS。

TSS(任务状态段)是由程序员来提供,CPU进行维护。程序员提供是指需要我们定义一个结构体,里面存放任务要用的寄存器数据。CPU维护是指切换任务时,CPU会自动把旧任务的数据存放的结构体变量中,然后将新任务的TSS数据加载到相应的寄存器中,也就是说,在硬件上就提供了这种进程转换的能力。

TSS和之前所说的段一样,本质上也是一片存储数据的内存区域,CPU用这块内存区域保存任务的最新状态。所以也需要一个描述符结构来表示它,这个描述符就是TSS描述符,它的结构如下

MyOS 之 多任务

这个描述符主要关注于它type字段中的B位,B位为1时表示任务繁忙。

任务繁忙有两方面的意义,一是此任务正在CPU上运行,二是此任务嵌套调用了新的任务,CPU此时正在执行这个新的任务,此任务暂时挂起,等到新任务运行完了之后返回此任务继续执行

这个B位是由CPU自动维护的,任务被调到上CPU的时候,CPU自动将此B位置1,被换下CPU的时候,自动将其置0

这个描述符主要关注于它type字段中的B位,B位为1时表示任务繁忙。

任务繁忙有两方面的意义,一是此任务正在CPU上运行,二是此任务嵌套调用了新的任务,CPU此时正在执行这个新的任务,此任务暂时挂起,等到新任务运行完了之后返回此任务继续执行

这个B位是由CPU自动维护的,任务被调到上CPU的时候,CPU自动将此B位置1,被换下CPU的时候,自动将其置0

前面说的TSS描述符是用来描述TSS的,TSS的结构如下图

MyOS 之 多任务

TSS结构中的数据就是我们保存任务时需要存储的数据,我们提供的保存TSS的数据结构也要照着这个设计。

一个CPU上的所有任务共享一个TSS,通过TR寄存器保存这个TSS,在使用ltr指令加载TSS之后,该TR寄存器永远指向同一个TSS,之后在进行任务切换的时候也不会重新加载TSS,只需要把TSS中的SS0和esp0更新为新任务的内核栈的段地址及栈指针。

在当初硬件厂商设计TSS的时候,本意是想让一个任务保存一份TSS,这样在切换任务的时候,重新从内存中加载TSS,让TR寄存器指向该TSS,从而实现任务切换。但是这种方式切换任务,每次都要从内存中加载数据,对于CPU来说,速度太慢了,而且切换的步骤也十分繁琐。

Linux的任务切换方式只需要修改TSS中的SS0和esp0,进行任务切换的速度当然是大幅度提升了。

反正,内存切换就是要用jmp了。jmp分为两种,只改写ip就是near跳转,同时改写ip和cs就是远跳转。

如果一条jmp指令所指定的目标地址段不是可执行的代码,而是TSS的话,CPU就不会执行通常的改写EIP和CS的操作,而是将这条指令理解为任务切换,这是最关键的。CPU每次执行带有段地址的指令时,都会去确认一下GDT的设置,判断接下来要执行的JMP指令到底是far-jmp,还是任务切换,在汇编代码上,这两者没有任何区别,因此需要硬件判断。TSS段内容会被放到GDT中!TR寄存器的目的就是让CPU知道现在正在执行的是哪一个任务。当任务切换的时候,TR寄存器的值也会自动变化。我们每次给TR寄存器赋值的时候,必须把GDT的编号乘以8,这是硬性规定。
 

 

 

 

 

 

 

 

 

 

 

 

相关文章: