兰陵月 发表于 2018-1-14 10:07:21

X86汇编语言-从实模式到保护模式—笔记(45)-第15章 任务切换

本帖最后由 兰陵月 于 2018-1-14 10:10 编辑

第15章任务切换
从80286开始的处理器是面向多任务系统而设计的。在一个多任务的环境中,可以同时存在多个任务,每个任务都有各自的局部描述符表(LDT)和任务状态段(TSS)。在局部描述符表中存放着专属于任务局部空间的段的描述符。可以在多个任务之间切换,使它们轮流执行,从一个任务切换到另一个任务时,具体的切换过程是由处理器的固件负责进行的。
所谓多任务系统,是指能够同时执行两个以上任务的系统。即使前一个任务没有执行完,下一个任务也可以开始执行。但是,什么时候切换到另一个任务,以及切换到哪一个任务执行,主要是操作系统的责任,处理器只负责具体的切换过程,包括保护前一个任务的现场。
有两种基本的任务切换方式,一种是协同式的,从一个任务切换到另一个任务,需要当前任务主动地请求暂时放弃执行权,或者在通过调用门请求操作系统服务时,由操作系统“趁机”将控制转移到另一个任务。这种方式依赖于每个任务的“自律”性,当一个任务失控时,其他任务可能得不到执行的机会。
另一种是抢占式的,在这种方式下,可以安装一个定时器中断,并在中断服务程序中实施任务切换。硬件中断信号总会定时出现,不管处理器当时在做什么,中断都会适时发生,而任务切换也就能够顺利进行。在这种情况下,每个任务都能获得平等的执行机会。而且,即使一个任务失控,也不会导致其他任务没有机会执行。

15.2任务切换前的设置
系统中的任务状态如下图15-1所示,所有任务共享一个全局空间,这是内核或者操作系统提供的,包含了系统服务程序和数据;同时,每个任务还有自己的局部空间,每个任务的功能都不一样,所以,局部空间包含的是一个任务区别于其他任务的私有代码和数据。

任务切换是以任务为单位的,是指离开一个任务,转到另一个任务中取执行。离开当前任务,转到另一个任务开始执行时,要保存旧任务的各种状态,并恢复新任务的运行环境。
为了方便,我们先创建0特权级别的操作系统(内核)任务。
当前正在执行的内核代码段的作用是创建其他任务,管理它们,所以称做任务管理器,或者叫程序管理器。
任务状态段(TSS)是一个任务存在的标志,没有它,就无法执行任务切换,因为任务切换时需要保存旧任务的各种状态数据。
先为拟创建的任务状态段TSS申请所需的内存104字节。再为其填写进基本的数据。再创建它的描述符(TSS只能安装在GDT中)。再将当前任务的TSS选择子传送到寄存器TR中。
执行LTR指令后,处理器用选择子访问GDT,找到相对应的TSS描述符,将其B位置“1”,表示该任务正在执行中(或者处于挂起状态),同时,还将该描述符传送到TR寄存器的描述符高速缓存器中。
15.3任务切换的方法
第一种任务切换的方法是借助于中断,这也是现代抢占式多任务的基础。
实模式下,内存最低地址端的1KB是中断向量表,保存着256个中断处理过程的段地址和偏移地址。当中断发生时,处理器把中断号乘以4,作为表内索引号访问中断向量表,从相应位置取出中断处理过程的段地址和偏移地址,并转移到那里执行。
在保护模式下,中断向量表不再使用,取而代之的是中断描述符表。它和GDT、LDT是一样的,用于保存描述符。唯一不同的地方是,它保存的是门描述符,包括中断门、陷阱门和任务门。当中断发生时,处理器用中断号乘以8(因为每个描述符占8字节),作为索引访问中断描述符表,取出门描述符。门描述符中有中断处理过程的代码段选择子和段内偏移量,这和调用门时一样的。接着,转移到相应的位置去执行。
一般的中断处理可以使用中断门和陷阱门。中断门和陷阱门允许在任务内实施中断处理,转到全局空间去执行一些系统级的管理工作,本质上,也是任务内的控制转移行为。但是,在中断发生时,如果该中断号对应的门是任务门,那么,就会进行任务切换。即,要中断当前任务的执行,保护当前任务的现场,并转换到另一个任务去执行。
下图15-2所示,是任务门描述符的格式。

任务门描述符中的主要成分是任务的TSS选择子。任务门用于在中断发生时执行任务切换,而执行任务切换时必须找到新任务的任务状态段(TSS)。所以,任务门应当指向任务的TSS。为了指向任务的TSS,只需要在任务门描述符中给出任务的TSS选择子就可以了。
任务门描述符中的P位指示该门是否有效,当P位为“0”时,不允许通过此门实施任务切换;DPL是任务门描述符的特权级,但是对因中断而发起的任务切换不起作用,处理器不按特权级施加任何保护。但是,这并不意味着DPL字段没有用处,当以非中断的方式通过任务门实施任务切换时,它就有用了。
运行过程:当中断发生时,处理器用中断号乘以8作为索引访问中断描述符表。当它发现这是一个任务门(描述符)时,就知道应当发起任务切换。于是,它取出任务门描述符;再从任务门描述符中取出新任务的TSS选择子;接着,再用TSS选择子访问GDT,取出新任务的TSS描述符。在转到新任务执行前,处理器要先把当前任务的状态保存起来。当前任务的TSS是由任务寄存器TR的当前内容指向的,所以,处理器把每个寄存器的“快照”保存到由TR指向的TSS中。然后,处理器访问新任务的TSS,从中恢复各个寄存器的内容,包括通用寄存器、标志寄存器EFLAGS、段寄存器、指令指针寄存器EIP、栈指针寄存器ESP,以及局部描述符表寄存器(LDTR)等。最终,任务寄存器TR指向新任务的TSS,而处理器旋即开始执行新的任务。一旦新任务开始执行,处理器固件会自动将其TSS描述符的B位置“1”,表示该任务的状态为忙。
当中断发生时,可以执行常规的中断处理过程,也可以进行任务切换。尽管性质不同,但它们都要使用iret指令返回。前者是返回到同一任务内的不同代码段;后者是返回到被中断的那个任务。那么处理器是如何区分这两种截然不同的返回类型呢?
如图15-3所示,32位处理器的EFLAGS有NT位(位14),意思是嵌套任务标志(Nested Task Flag)。每个任务的TSS中都有一个任务链接域(指向前一个任务的指针),可以填写为前一个任务的TSS描述符选择子。如果当前任务EFLAGS寄存器的NT位是“1”,则表示当前正在执行的任务嵌套于其他任务内,并且能够通过TSS任务链接域的指针返回到前一个任务。

因中断而引发任务切换时,取决于当前任务(旧任务)是否嵌套于其他任务内,其EFLAGS寄存器的NT位可能是“0”,也可能是“1”。不过这无关紧要,因为处理器不会改变它,而是和其他寄存器一道,写入TSS中保护起来。另外,当前任务(旧任务)肯定处于“忙”的状态,其TSS描述符的B位一定是“1”,在任务切换后同样保持不变。
对新任务的处理是,要把老任务的TSS选择子填写到新任务TSS中的任务链接域,同时,将新任务EFLAGS寄存器的NT位置“1”。无论任何时候处理器碰到iret指令,它都要检查NT位,如果此位是0,表明是一般的中断过程,按一般的中断返回处理,即,中断返回是任务内的(中断处理过程虽然属于操作系统,但属于任务的全局空间);如果此位是1,则表明当前任务之所以能够正在执行,是因为中断了别的任务。因此,应当返回原先被中断的任务继续执行。此时,由处理器固件把当前任务EFLAGS寄存器的NT位改成“0”,并把TSS描述符的B位改成“0”(非忙)。在保存了当前任务的状态之后,接着,用新任务(被中断的任务)的TSS恢复现场。
除了因中断引发的任务切换之外,还可以用远过程调用指令CALL,或者远跳转指令JMP直接发起任务切换。在这两种情况下,CALL和JMP指令的操作数是任务的TSS描述符选择子或任务门。以下是两个例子:
call 0x0010:0x00000000
jmp 0x0010:0x00000000
当处理器执行这两条指令时,首先用指令中给出的描述符选择子访问GDT,分析它的描述符类型。如果是一般的代码段描述符,就按普通的段间转移规则执行;如果是调用门,按调用门的规则执行;如果是TSS描述符,或者任务门,则执行任务切换。此时,指令中给出的32位偏移量被忽略,原因是执行任务切换时,所有处理器的状态都可以从TSS中获得。注意,任务门描述符可以安装在中断描述符表中个,也可以安装在全局描述符表(GDT)或者局部描述符表(LDT)中。
如果是用于发起任务切换,call指令和jmp指令也有不同之处。使用call指令发起的任务切换类似于因中断发起的任务切换。这就是说,由call指令发起的任务切换是嵌套的,当前任务(旧任务)TSS描述符的B位保持原来的“1”不变,EFLAGS寄存器的NT位也不发生变化;新任务TSS描述符的B位置“1”,EFLAGS寄存器的NT位也置“1”,表示此任务嵌套于其他任务重。同时,TSS任务链接域的内容改为旧任务的TSS描述符的选择子。
如图15-4所示,假设任务1是整个系统中的第一个任务。当任务1开始执行时,其TSS描述符的B位是“1”,EFLAGS寄存器的NT位是“0”,不嵌套于其他任务。当从任务1转换到任务2后,任务1仍然为“忙”,EFLAGS寄存器的NT位不变(在其TSS中);任务2也变为“忙”,EFLAGS寄存器的NT位变为“1”,表示嵌套于任务1中。同时,任务1的TSS描述符选择子也被复制到任务2的TSS中(任务链接域)。

最后是从任务2转换到任务3执行。和从前一样,任务2保持“忙”的状态,EFLAGS寄存器的NT不变(在其TSS中);任务3称为当前任务,其TSS描述符的B位变成“1”(忙),EFLAGS寄存器的NT位也变成“1”,同时,其TSS的任务链接域指向任务2.
用CALL执行发起的任务切换,可以通过iret指令返回到前一个任务。此时,旧任务TSS描述符的B位,以及EFLAGS寄存器的NT位都恢复到“0”。
和call指令不同,使用jmp指令发起的任务切换,不会形成任务之间的嵌套关系。执行任务切换时,当前任务(旧任务)TSS描述符的B位清零,变为非忙状态,EFLAGS寄存器的NT位不变;新任务TSS描述符的B位置“1”,进入忙的状态,EFLAGS寄存器的NT位保持从TSS加载时的状态不变。
任务是不可重入的。任务不可重入的本质是,执行任务切换时,新任务的状态不能为忙。比如下面两个典型的情况:
第一种情形,执行任务切换时,新任务不能是当前任务自己。试想一下,如果允许这种情况发生,处理器该如何执行现场的保护和恢复操作?
第二种情形,如图15-4所示,不允许使用CALL指令从任务3切换到任务2和任务1上。如果不禁止这种情况的话,任务之间的嵌套关系将会因为TSS任务链接域的破坏而错乱。
处理器是通过TSS描述符的B位来检测重入的。因中断、call和jmp指令发起任务切换时,处理器固件会检测新任务TSS描述符的B位,如果为“1”,则不允许执行这样的切换。
15.4用call/jmp/iret指令发起任务切换的实例
保护模式下的中断和异常处理要在第17章才能详细阐述;和中断有关的任务切换也将在第17章介绍。在本章,我们重点关注的是用call、jmp和iret指令的任务切换。
第938~945行是用来加载用户程序的。先分配一个任务控制块(TCB),然后将它挂到TCB链上。接着,压入用户程序的起始逻辑扇区号及其TCB基地址,作为参数调用过程load_relocate_program。该过程的工作和上一章相比没有太大变化,仅仅是对TSS的填写比较完整。注意,这是任务切换的要求,从一个任务切换到另一个任务时,处理器要从新任务的TSS中恢复(加载)各个寄存器的内容。尽管这是任务的第一次执行,但处理器并不知道,这是它的例行工作,你得把任务执行时,各个寄存器的内容放到TSS中供处理器加载。
当执行任务切换时,处理器用得到的选择子访问GDT,一旦它发现那是一个TSS描述符,就知道应该执行任务切换的操作。首先,因为当前正在执行的任务是由任务寄存器TR指示的,所以,它要把每个寄存器的“快照”保存到由TR指向的TSS中。
然后,处理器用指令中给出的TSS选择子访问GDT,取得新任务的TSS描述符,并从该TSS中恢复各个寄存器的内容,包括通用寄存器、标志寄存器EFLAGS、段寄存器、指令指针寄存器EIP、栈指针寄存器ESP,以及局部描述符表寄存器(LDTR)等。最终,任务寄存器TR指向新任务的TSS,而处理器旋即开始执行新的任务。
页: [1]
查看完整版本: X86汇编语言-从实模式到保护模式—笔记(45)-第15章 任务切换