4.1. 任务管理概述
什么是任务?
任务是处理器可以分派、执行和挂起的工作单元。任务由两个部分组成:任务执行空间和任务状态段(TSS)
80x86
提供了哪些硬件支持?
由上面的任务组成部分可知,任务由两个部分组成,如上图所示。如果操作系统或者执行程序使用处理器的特权级别保护机制,任务执行空间还会给特别优先级别的任务提供单独的堆栈。TSS来指定任务执行空间的段,并为任务状态信息提供存储位置。在多任务系统中,TSS还提供了一种连接任务的机制。
任务由对应的TSS来标识,当一个任务被加载到处理器执行的时候,TSS的段选择器、基址、限制和段描述符属性被加载到任务寄存器 TR
中。
如果为任务实现分页,就将对应的页目录的基地址加载到控制寄存器CR3
中
描述符表中与任务相关的描述符有哪些?
-
段选择器中的段选择子定义任务的当前执行空间。
-
EFLAGS
、EIP
、控制寄存器CR3
以及LDTR
寄存器的状态。 -
TSS中的I/O映射基地址和I/O映射、特权级别0、1和2堆栈的堆栈指针和先前执行的任务的链接。
所有这些项目除了任务寄存器的状态都包含在TSS中,且LDTR只有部分内容在TSS中(只有段选择子)
任务切换与过程调用的区别是什么?
任务切换很象过程调用,但任务切换会保存更多的处理器状态信息。任务切换会把控制权完全转移到 一个新的执行环境,即新任务的执行环境。这种转移操作要求保存处理器中几乎所有寄存器的当前内容, 包括标志寄存器 EFLAGS 和所有段寄存器。与过程不一样的是,任务不可重入。任务切换不会把任何信息压入堆栈中,处理器的状态信息都被保存在内存中称为任务状态段(Task state segment)的数据结构中。
4.1.1. 任务的结构
一个任务由几部分构成?
一个任务由两个部分组成,任务执行空间和任务状态段TSS。
任务执行空间包括什么?
代码段,堆栈段和一个或者多个的数据段
为什么会有多个特权级栈空间?
因为有多个特权级,程序在访问计算机的堆栈的时候,在更高特权级别的程序才能对较低的特权级别的堆栈段进行访问,也就是说需要将堆栈段根据不同任务的特权级别来分成对应的特权级别的栈空间,这样才能实现不同特权级别访问的过滤。
4.1.2. 任务状态
当前正在执行的任务状态包括哪些内容?
当一个任务加载到处理器进行执行的时候,那么该任务的段选择符号、基地址、段限长以及TSS段描述符属性就会被加载到任务寄存器TR中(Task Register)。
任务状态包含以下的内容:
- 所有通用寄存器和段寄存器信息
- 标志寄存器
EFLAGS
、程序指针EIP
,控制寄存器CR3
、任务寄存器和LDTR
寄存器 - 段寄存器指定的任务当前执行空间
- I/O映射位图基地址和I/O位图信息(在TSS中)
- 特权级别0/1/2的堆栈指针(在TSS中)
- 链接至前一个任务的链指针(在TSS中)
每一个被包含内容的含义和为什么要包含这些内容?
所有通用寄存器和段寄存器中包含的是当前数据段的位置和执行当前程序的寄存器的上下文内容;标志寄存器EFLAGS
里面存储的是当前程序的各种标志位以及当前计算机的运行模式选择,程序指针EIP
和可能出现的SS
段寄存器代表的是执行指令的地址(应该是下一条指令的地址)。任务寄存器记录了当前执行的任务的地址,在返回的时候有利于重新返回任务;IDTR
代表的是IDT的基地址位置,和段选择子一起能够寻址到当前的描述符。
I/O映射位图基地址和I/O位图信息,保存了I/O所在的地址信息,使得程序能够正常的调用I/O进行工作
特权级别0/1/2的堆栈指针能够使得程序可以访问到当前特权级所可以访问的堆栈信息并进行程序的执行对其进行利用或者修改。
4.1.3. 任务的执行
任务的执行方式有几种?
软件或者处理器可以使用以下的方法之一来调度执行一个任务:
- 使用CALL指令明确的调用一个任务
- 使用JMP指令明确的跳转到一个任务
- 隐含的调用一个中断句柄处理任务;
- 隐含的调用一个异常句柄处理任务
熟悉掌握每一种执行方式的过程
当使用CALL或者JMP指令调度一个任务的时候,指令中的选择符既可以直接选择任务的TSS,也可以选择存放有TSS选择符的任务门。当调度一个任务来处理一个中断或者异常的时候,那么IDT中该终端或者异常的表项必须是一个任务门,并且其中含有中断或者异常处理任务的TSS描述符。
当调度一个任务执行的时候,当前正在运行任务和调度任务之间会自动的发生任务切换操作。
Linux 0.00
用的是哪种方式?
Linux内核使用的是JMP指令明确的跳转到一个任务。
任务可以递归调用吗?为什么?
任务是不可以进行递归调用的,也就是任务不能调用或者跳转到自己。因为任务的调度方法是如果当前执行任务调用了被调度的新任务,那么调用者的TSS段选择符号会被被存在被调用者的TSS中,从而提供了一个返回调用者的链接;自己调用自己是不存在调度者和被调度者的,所以是不合法的。
4.2. 任务的数据结构
任务状态段 Task-State Segment (TSS)
程序的状态段的结构如上所示。
TSS各个字段可以分成两个大类:动态字段和静态字段
动态字段
- 通用寄存器字段。用于保存EAX、ECX、EDX、EBX、ESP、EBP、ESI和EDI寄存器的内容
- 段选择符字段。用于保存ES、CS、SS、DS、FS、和GS段寄存器的内容。
- 标志寄存器
EFALGS
字段。在切换之前保存EFLAGS。 - 指令
EIP
字段。在切换之前保存EIP寄存器内容。 - 先前任务连接字段。含有前一个任务TSS段选择符号(在调用、中断或者异常激发的时候更新)。该字段(通常也称为后连接字段(Back link field))允许任务使用IRET指令切换到前一个任务。
静态字段
LDT
段选择符字段。含有任务的 LDT 段的选择符。CR3
控制寄存器字段。含有任务使用的页目录物理基地址。控制寄存器 CR3 通常也被称为页目录基地址寄存器PDBR(Page directory base register)。- 特权级 0、1 和 2 的堆栈指针字段。这些堆栈指针由堆栈段选择符(SS0、SS1 和 SS2)和栈中偏移量指针(ESP0、ESP1 和 ESP2)组成。注意,对于指定的一个任务,这些字段的值是不变的。 因此,如果任务中发生堆栈切换,寄存器 SS 和 ESP 的内容将会改变。
- 调试陷阱(Debug Trap)
T
标志字段。该字段位于字节 0x64 比特 0 处。当设置了该位时,处理器 切换到该任务的操作将产生一个调试异常。 - I/O位图基地址字段。该字段含有从 TSS 段开始处到 I/O许可位图处的 16 位偏移值
描述符TSS
类型字段TYPE中的忙地址B用于指明任务是否处于忙状态。忙状态的任务是当前正在执行的任务或者等待执行(被挂起)的任务。值为0b1001的类型字段表明任务处于非活动的状态;而值位0b1011的类型字段标识任务正忙,任务是不可以递归执行的,因此处理器使用忙标志B来检测任何企图对被中断执行任务的调用。
其中基地址、段限长、描述符特权级DPL、颗粒度G和存在位具有与数据段描述符中相应字段同样的功能。当G=0 时,限长字段必须具有等于或大于 103(0x67)的值,即 TSS 段的最小长度不得小于 104 字节。如果 TSS 段中还包含 I/O 许可位图,那么 TSS 段长度需要大一些。另外,如果操作系统还想在 TSS 段中存放其他一些信息,那么 TSS 段就需要更大的长度。
使用调用或跳转指令,任何可以访问 TSS 描述符的程序都能够造成任务切换。可以访问 TSS 描述符的程序其CPL数值必须小于或等于 TSS 描述符的DPL。在大多数系统中,TSS 描述符的DPL字段值应该设置成小于3。这样,只有具有特权级的软件可以执行任务切换操作。然而在多任务应用中,某些 TSS 的 DPL可以设置成3,以使得在用户特权级上也能进行任务切换操作。
但是访问一个 TSS 段描述符并没有给程序读写该描述符的能力。若想读或修改一个 TSS 段描述符,可以使用映射到内存相同位置的数据段描述符(即别名描述符)来操作。把 TSS 描述符加载进任何段寄存器将导致一个异常。企图使用 TI 标志置位的选择符(即当前 LDT 中的选择符)来访问 TSS 段也将导致异常。
任务寄存器
任务寄存器中存档16位的段选择符号和当前任务TSS段的整个描述符号;信息是从当前任务的TSS描述符中复制过来的。指令LTR和STR分别用于加载和保存任务寄存器的可见部分,也就是TSS段的选择符。LTR只能被特权级别0的程序执行,LTR用于系统初始化给TR寄存器初值。
任务门描述符 Task-Gate Descriptor
提供对一个任务间接、受保护的引用。任务门描述符可以被存放在GDT、LDT或者DT中。
任务门描述符中的TSS选择符号字段指向GDT中的一个TSS段描述符。这个TSS段的RPL域不适用。任务门描述符中的DPL用于在任务切换的时候控制对TSS段的访问。当程序通过任务门调用或者跳转到一个任务的时候,程序的CPL以及指向任务们的们选择符的RPL的值必须小于等于任务门描述符中的DPL。
4.3. 任务切换
此部分内容重点掌握:
什么时候发生任务切换?
处理器在下面四种情况下会发生任务切换:
- 当前任务对GDT中的TSS描述符执行JMP或者CALL指令;
- 当前任务对GDT或者LDT中的任务门描述符执行JMP或者CALL指令
- 中断或者异常响亮指向IDT表中的任务门描述符
- 当EFLAGS中的NT标志位为1的时候当前任务执行IRET指令
发生任务切换时,处理器会执行哪些操作?
JMP或者CALL指令能够把控制转移到TSS描述符或者任务门上。使用这两种方式的作用相同;当中断或者异常的向量缩影的是IDT的一个任务门的时候,一个中断或者异常就会造成任务切换。
执行的具体操作是:
步骤 | 描述 | 异常 & 中断 | IRET | JMP | CALL |
---|---|---|---|---|---|
1 | 从JMP或CALL操作数、任务门或当前TSS的前一任务链接字段(IRET引起)中获取新任务的TSS段选择符。 | ✔ | ✔ | ✔ | ✔ |
2 | 检查当前任务是否允许切换到新任务,并应用数据访问权限规则。 | ✔ | ✔ | ✔ | ✔ |
3 | 检查新任务的TSS描述符是否标记为存在(P=1),且TSS段长度有效(大于067)。确保异常处理过程的返回地址指向出错指令而不是其后的指令。 | ✔ | ✔ | ✔ | ✔ |
4 | 忙标志B设置 | - | ✔ | ✔ | - |
5 | 是否复位临时保存的EFLAGS映像中的NT标志 | - | ✔ | - | - |
6 | 将当前任务保存至当前任务的TSS中,获取当前任务TSS的基地址,并将所有寄存器内容复制到当前TSS中。 | ✔ | ✔ | ✔ | ✔ |
7 | 设置从新任务加载的EFLAGS中的NT标志。 | ✔ | - | - | ✔ |
8 | 设置新任务TSS描述符中的忙标志B | ✔ | - | - | ✔ |
9 | 使用新任务TSS的段选择符和描述符加载任务寄存器TR(包括隐藏部分),并设置CR0寄存器的TS标志。 | - | ✔ | - | - |
10 | 加载处理器的新任务TSS状态,包括LDTR寄存器、PDBR(CR3)寄存器、EFLAGS寄存器、EIP寄存器以及通用寄存器和段选择符。同时,将任何检测到的错误记录到新任务的上下文中。 | ✔ | ✔ | ✔ | ✔ |
11 | 开始执行新任务。 | ✔ | ✔ | ✔ | ✔ |
中断或异常向量指向 IDT
表中的中断门或陷阱门,会发生任务切换吗?
如果向量索引的是IDT中的一个中断或者陷阱门,就不会造成任务切换。
4.4. 任务链
如何判断任务是否嵌套?
NT标志指出了当前执行的任务是否嵌套在另一个任务中执行,并且当前任务的前一任务连接字段中存放着嵌套层中更高层任务的TSS选择符。
什么情况会发生任务嵌套?
CALL指令、中断或者异常造成任务切换,处理器把当前的TSS段的选择符复制到新任务TSS段的前一任务链接中,然后在EFLAGS中设置NT标志。如果软件使用IRET指令挂起新任务,处理器会切换到前一个任务链接字段中值和NT标志返回到前一个任务。
任务嵌套时修改了哪些标志位?
任务切换会修改忙标志(B),NT标志(NT),前一任务链字段和TS标志(TS)
修改的方式如下:
标志或字段 | JMP指令的影响 | CALL指令或者中断的影响 | IRET指令的影响 |
---|---|---|---|
新任务忙标志B | 设置标志。以前需要已经被清除掉 | 设置标志。以前需要被清除掉 | 不变,必须已经被设置 |
老任务忙标志 | 被清除 | 不变,当前处于设置状态 | 设置成新任务TSS中的值 |
新任务NT标志 | 不变 | 不变 | 清除标志 |
老任务NT标志 | 不变 | 存放老任务TSS段选择符 | 不变 |
新任务链接字段 | 不变 | 存放老任务TSS段选择符号 | 不变 |
老任务链接字段 | 不变 | 不变 | 不变 |
CR0中TS标志 | 设置标志 | 设置标志 | 设置标志 |
任务嵌套时,如何返回前一任务?
任务嵌套的时候,处理器会检查EFLAGS里面的NT标识符,如果检测到存在任务嵌套,就访问当前TSS段中的前任务链对应的TSS位置,返回到前一个任务的TSS继续执行。
4.5. 任务地址空间
什么是任务地址空间?任务地址空间包括什么?
任务地址空间由任务能够访问的段构成,这些段包括代码段、数据段、堆栈段、TSS中应用的系统段以及任务代码能够访问的任何其他段。这些段都被映射到处理器的线性地址空间中,并且随后被直接地或者通过分页机制映射到处理器的物理地址空间中。
了解把任务映射到线性和物理地址空间的方法
有两种方法可以把任务映射到线性空间和物理地址空间:
-
所有任务共享一个线性到物理地址空间地映射。当没有开启分页机制的时候,就只能使用这个办法。不开启分页的时候,所有线性地址映射到相同物理地址上。当开启了分页机制的时候,那么通过让所有的任务使用一个页目录,我们就可以使用这种从线性到物理地址空间的映射形式。如果支持需求页虚拟存储技术,那么线性地址空间可以超过现有物理地址空间的大小。
-
每个人任务都有自己的线性地址空间,并映射到物理地址空间。通过让每个人物使用不同的页目录,我们就可以使用这种映射模式。因为每次切换都会加载PDBR(控制寄存器CR3),所以每个任务可以有不同的页目录。
此外,所有任务的TSS,GDT都应该存放在共享的物理地址空间区域中,并且所有任务都能访问这个区域。