操作系统是利用PCB来维护所有任务的,包括进程和线程,但cpu提供的是TSS,linux系统可没用它,因为效率太低。但是还是要了解下TSS才清楚操作系统中某些操作的原因。
本节中所讲的特权级与它有着密不可分的联系,TSS作用不止涉及特权级,还包括任务寄存器环境,任务管理相关的内容,为了不干扰大家,这里只介绍和特权级相关的内容,待将来咱们用到更多内容时再和大伙儿细说。
TSS,即Task State Segment,意为任务状态段,它是处理器在硬件上原生支持多任务的一种实现方式,也就是说处理器原本是想让操作系统开发厂商利用此结构实现多任务,人家处理器厂商已经提供了多任务管理的解决方案,尽管后来操作系统并不买账^_^,这是后话,以后再议。TSS是一种数据结构,它用于存储任务的环境。咱们一睹为快,见图
TSS是每个任务都有的结构,它用于一个任务的标识,相当于任务的身份证,程序拥有此结构才能运行,这是处理器硬件上用于任务管理的系统结构,处理器能够识别其中每一个字段。该结构看上去也有点复杂,里面众多寄存器都囊括到这104字节中啦,其实这104字节只是TSS的最小尺寸,根据需要,还可以再接上个IO位图,这些内容将在后面章节用到时补充。这里目前只需要关注28字节之下的部分,这里包括了3个栈指针,这是怎么回事呢。
在没有操作系统的情况下,可以认为进程就是任务,任务就是一段在处理器上运行的程序,相当于某个计算机高手在脱离操作系统的情况下所写的代码,它能让计算机很好地运行。在有了操作系统之后,程序可分为用户程序和操作系统内核程序,故,之前完整的一个任务也因此被分为用户部分和内核部分,由于内核程序是位于0特权级,用户程序位于3特权级,所以,一个任务按特权级来划分的话,实质上是被分成了3特权级的用户程序和0特权级的内核程序,这两部分加在一起才是能让处理器完整运行的程序,也就是说完整的任务要历经这两种特权的变换。所以,我们平时在Linux下所写程序只是个半成品,咱们只负责完成用户态下的部分,内核态的部分由操作系统提供。
任务是由处理器执行的,任务在特权级变换时,本质上是处理器的当前特权级在变换,由一个特权级变成了另外一个特权级。这就开始涉及到栈的问题了,处理器固定,处理器在不同特权级下,应该用不同特权级的栈,原因是如果在同一个栈中容纳所有特权级的数据时,这种交叉引用会使栈变得非常混乱,并且,用一个栈容纳多个特权级下的数据,栈容量有限,这很容易溢出。举个例子,处理器位于0特权级时要用0特权级的栈,3特权级下也只能用3特权级的栈。
每个任务的每个特权级下只能有一个栈,不存在一个任务的某个特权级下存在多个同特权级栈的情况。也就是说,一共4个特权级,一个任务“最多”有4个栈。既然一个TSS代表一个任务,每个任务又有4个栈,那为什么TSS中只有3个栈:ss0和esp0、ss1和esp1、ss2和esp2?它们分别代表0级栈的段选择子和偏移量、1级栈的段选择子和偏移量、2级栈的段选择子和偏移量。大家看,我在前面说的一个任务最多拥有4个栈,并不是所有的任务都是这样。
要想搞清楚这个问题,得先弄明白TSS中记录的3个栈是用来干吗的。
刚才已经说过,特权级在变换时,需要用到不同特权级下的栈,当处理器进入不同的特权级时,它自动在TSS中找同特权级的栈,你懂的,TSS是处理器硬件原生的系统级数据结构,处理器当然知道TSS中哪些字段是目标栈的选择子及偏移量。
特权级转移分为两类,一类是由中断门、调用门等手段实现低特权级转向高特权级,另一类则相反,是由调用返回指令从高特权级返回到低特权级,这是唯一一种能让处理器降低特权级的情况。
对于第1种——特权级由低到高的情况,由于不知道目标特权级对应的栈地址在哪里,所以要提前把目标栈的地址记录在某个地方,当处理器向高特权级转移时再从中取出来加载到SS和ESP中以更新栈,这个保存的地方就是TSS。处理器会自动地从TSS中找到对应的高特权级栈地址,这一点对开发人员是透明的,咱们只需要在TSS中记录好高特权级的栈地址便可。
也就是说,除了调用返回外,处理器只能由低特权级向高特权级转移,TSS中所记录的栈是转移后的高特权级目标栈,所以它一定比当前使用的栈特权级要高,只用于向更高特权级转移时提供相应特权的栈地址。进一步说,TSS中不需要记录3特权级的栈,因为3特权级是最低的,没有更低的特权级会向它转移。
不是每个任务都有4个栈,一个任务可有拥有的栈的数量取决于当前特权级是否还有进一步提高的可能,即取决于它最低的特权级别。比如3特权级的程序,它是最低的特权级,还能提升三级,所以可额外拥有2、1、0特权级栈,用于将特权分别转移到2、1、0级时使用。2特权级的程序,它还可以提升两级,所以可额外拥有1、0特权级栈,用于将特权级分别转移到1、0级时使用。以此类推,1特权级的程序,它可以额外拥有0特权级栈,0特权级已经是至高无上了,只有这一个0级栈。以上所说的低特权级转向高特权级的过程称为“向内层转移”,想想4个特权级划分的同心圆就知道了,高特权级位于里面。
对于第2种——由高特权返回到低特权级的情况,处理器是不需要在TSS中去寻找低特权级目标栈的。其中一个原因我想您已经猜到了:TSS中只记录2、1、0特权级的栈,假如是从2特权级返回到3特权级,上哪找3特权级的栈?另一方面的原因是,低特权级栈的地址其实已经存在了,这是由处理器的向高特权级转移指令(如int、call等)实现的机制决定的,换句话说,处理器知道去哪里找低特权级的目标栈,等我把后面内容“啰嗦完”您就知道了。
由于特权级向低转移后,处理器特权级有了变化,同样也需要将当前栈更新为低特权级的栈,它如何找到对应的低特权级栈呢。正常情况下,特权级是由低向高转移在先,由高向低返回在后,即只有先向更高特权级转移,才能谈得上再从高特权级回到低特权级,否则没有“去”就谈不上“回”(宁可被骂啰嗦我也要说清楚)。当处理器由低向高特权级转移时,它自动地把当时低特权级的栈地址(SS和ESP)压入了转移后的高特权级所在的栈中(随着以后深入学习大家会明白这一点),所以,当用返回指令如retf或iret从高特权级向低特权级返回时,处理器可以从当前使用的高特权级的栈中获取低特权级的栈段选择子及偏移量。由高特权级返回低特权级的过程称为“向外层转移”。
当下次处理器再进入到高特权级时,它依然会在TSS中寻找对应的高特权级栈,而TSS中栈指针值都是固定的,每次进入高特权级都会用重复使它们。也就是说,即使曾经转移到高特权级下用过高特权级栈,处理器也不会自动把该高特权级栈指针更新到TSS中,因为在从高特权级返回时,处理器需要把栈更新为低特权级的栈选择子及esp指针,而原先在段寄存器SS和寄存器esp中高特权级下的栈段选择子及指针会被处理器自动丢弃。换句话说,如果想保留上一次高特权级的栈指针,咱们得自己手动更新TSS中相应栈的数据。
对啦,有没有同学有疑问,上面光说处理器从TSS中找更高特权级的栈地址,那处理器是怎样找到TSS的?
TSS是硬件支持的系统数据结构,它和GDT等一样,由软件填写其内容,由硬件使用。GDT也要加载到寄存器GDTR中才能被处理器找到,TSS也是一样,它是由TR(Task Register)寄存器加载的,每次处理器执行不同任务时,将TR寄存器加载不同任务的TSS就成了。至于怎么加载以及相关工作原理,目前咱们用不到,还是放在后面说比较合适。
您看,正是由于处理器提供了硬件方面的框架,所以很多工作都是“自动”完成的,虽然操作系统看上去很底层的样子,但其实也属于“应用型”开发。
好啦,TSS中有关特权级的内容就说到这,为了不干扰大家学习特权级,TSS的其它方面将会在后续章节中逐步说明。