首页 > 代码库 > [转载]x86 的 TSS 任务切换机制

[转载]x86 的 TSS 任务切换机制

segment descriptors 构建保护模式下的最基本、最根本的执行环境。system descriptors 则构建保护模式下的核心组件:

1、TSS descriptor 提供硬件级的进程切换机制 2、LDT descriptor 供进程使用多个 descriptor 3、Gate descriptor 提供 processor 权限级别的切换机制。

5.7.1、 TSS 提供的进程切换机制
TSS 是一段内存区域,存放进程相关的执行环境信息。初始化的 TSS 是由用户提供,进程切换时的保存信息由 processor 执行。
5.7.1.1、  三个元素构成 TSS 环境:
1、 TSS descriptor:这个 descriptor 属于 system descriptor 类型,它的 S (system)位是 0。
  下面列出 TSS descriptors 的类型值:
0001:  16-bit TSS 0

011: busy 16-bit TSS

1001:  32-bit TSS

1011: busy 32-bit TSS

---------------------------------------------------   

以上是 x86 下的 TSS descriptor 类型,分为 16 和 32 位 TSS,x64 的 long mode 下 32 位的 TSS descriptor 将变为 64 位 TSS descriptor。
情景提示:

关于 TSS 的 busy 与 available 状态:   1、 TSS descriptor 的类型指明是 busy 与 available 状态。   2、 TSS descriptor 的 busy 与 available 状态由 processor 去控制。即:由 processor 置为 busy 或 avaibable。 除了初始的 TSS descriptor 外。

  TSS 的 busy 状态主要用来支持任务的嵌套。TSS descriptor 为 busy 状态时是不可进入执行的。同时防止 TSS 进程切换机制出现递归现象。
2、 TSS selector 以及 TR(Task Register)寄存器
  TR 寄存器的结构与 segment registers 是完全一致的,即:由软件可见的 selector 部分与 processor 可见的隐藏部分(信息部分)构成。   TR.selector 与 CS.selector 中的 selector 意义是完全一样的。其 descriptor 的加载也是一样的。
  即: TR.selector 在 GDT / LDT 中索引查找到 TSS descriptor 后,该 TSS descriptor 将被加载到 TR 寄存的隐藏部分。当然在加载到 TR 寄存器之前,要进行检查通过了才可加载到 TR 寄存器。若加载 non-TSS descriptor 进入 TR 则会产生 #GP 异常。
3、 TSS 块(Task Status Segment)   

像 code segment 或 data segments 一样,最终的 TSS segment 由 TSS descriptor 来决定。 TSS descriptor 指出 TSS segment 的 base、limit 及 DPL 等信息。   T

SS segment 存放 eflags 寄存器、GPRs 寄存器及相关的权限级别的 stack pointer (ss & sp)、CR3 等等信息。

5.7.1.2、 TSS 机制的建立
  对于多任务 OS 来说,TSS segment 是必不可少的,系统至少需要一个 TSS segment,但是现在的 OS 系统不使用 TSS 机制来进行任务的切换。

情景提示:   TSS 存在的唯一理由是:需要提供 0 ~ 2 权限级别的 stack pointer,当发生 stack 切换时,必须使用 TSS 提供的相应的 stack pointer。   但是:若提供空的 TSS segment,或者可以考虑以直接传递 stack pointer 的方式实现 stack 切换,即便是这样设计 processor 要读取 TSS segment 这一工作是必不可少的。

下面的指令用来建立初始的 TSS segment:   

LTR word ptr [TSS_Selector]                /* 在 [TSS_selector] 提供 TSS selector */

或:

LTR ax                                               /* 在 ax 寄存器里提供 TSS selector  */
  ltr 指令使用提供的 selector 在 GDT / LDT 里索引查找到 TSS descriptor 后,加载到 TR 寄存器里。初始的 TSS descriptor 必须设为 available 状态,否则不能加载到 TR。processor 加载 TSS descriptor 后,将 TSS descriptor 置为 busy 状态。

5.7.1.3、  TSS 进程切换的过程
  当前进程要切换另一个进程时,可以使用 2 种 selector 进行:使用 TSS selector 以及 Task gate selector(任务门符)。
如:   

call 0x2b:0x00000000          /* 假设 0x2b 为 TSS selector */      

call 0x3b:0x00000000          /* 假设 0x3b 为 Task-gate  selector */
  TSS 提供的硬件级进程切换机制较为复杂,大多数 OS 不使用 TSS 机制,是因为执行的效能太差了。上面的两条指令的 TSS 进程切换的过程如下:
1、使用 TSS selector  
  call 0x2b:0x00000000        /* 0x2b 为 TSS selector */
  这里使用 jmp 指令与 call 指令会有些差别,call 允许 TSS 进程切换的嵌套,jmp 不允许嵌套。
(1)processor 使用 TSS selector (0x2b) 在 GDT 索引查找第 5 个 descriptor
(2)processor 检查找到的 descriptor 类型是否是 TSS descriptor,不是的话将产生 #GP 异常。是否为 available TSS,若目标 TSS descriptor 是 busy 的话,同样将产生 #GP 异常。
(3)processor 进行另一项检查工作:权限的检查,CPL <= DPL 并且 selector.RPL <= DPL 即为通过,否则产生 #GP 异常。
(4)当前进程的执行环境被保存在当前进程的 TSS segment 中。

情景提示:   在这一步里,此时还没发生 TSS selector 切换,processor 把当前进程的环境信息保存在当前的 TSS segment 中。

(5)这里发生了 TSS selector 切换。新的 TSS selector 被加载到 TR.selector,而新的 TSS descriptor 也被加载到 TR 寄存的隐藏部分。

情景提示:   (1)这里,processor 还要将旧的 TSS selector 保存在当前的 TSS segment(新加载的 TSS)中的 link 域。这个 TSS segment 中的 link 其实就是 old TSS selector 域,用来进程返回时获得原来的 TSS selector 。从而实现任务的嵌套机制。   (2)processor 将当前 eflags 寄存器的 NT(Nest Task)标志位置为 1,表明当前的进程是嵌套内层。

(6)processor 从当前的 TSS segment 取出新进程的执行环境,包括:各个 selector registers(segment registers)、GPRs、stack pointer (ss & sp)、CR3 寄存器以及 eflags 寄存器等。   在这一步,在加载 selectors 进入 segment registers 之前,还必须经过相关的 selector & descriptor  的常规检查以及权限检查。通过之后才真正加载。否则同样产生 #GP 异常。

情景提示:   processor 还要做另一项工作,就是:将新进程的 TSS descriptor 置为 busy 状态。使得新进程不能重入。

(7)processor 从当前的 CS:RIP 继续往下执行,完成这个 TSS 进程的切换。这个 CS: RIP 就是新加载的新进程的 cs : rip
---------------------------------------------------------------------------------------   

从上面的过程可以看出,使用 TSS 进程切换机制异常复杂,导致进程切换的效能太差了。比使用 call gate 以及 trap gate 慢上好多。若当中发生权限的改变,还要发生 stack 切换。   

进程的返回同样复杂。同样需要这么多步骤,总结来说,主要的时间消耗发生在新旧进程的信息保存方面。

2、 使用 task gate selector      

另一种情况是使用 task gate selector 进行 TSS 进程切换,使用 task gate selector 除了可以 call/jmp 外,还可用在中断机制上,下面的两种情况:
  call 0x3b:0x00000000            /* 0x3b 为 task gate selector */

或:

int 0x3e                              /* 假设 0x3e 是 task gate 的向量 */
  这两种情形差不多,除了一些细微的差别外,不过是触发的机制不同而已。
  processor 在 GDT 索引查找到的是一个 task gate descriptor,这个 task gate descriptor 中指出了目标的 TSS selector 。processor 从 task gate descriptor 里加载 TSS selector,剩下的工作和使用 TSS selector  进行切换进程是一致。

  processor 访问 task gate descriptor 仅需对 task gate descriptor 作出权限检查:CPL <= DPL 并且 RPL <= DPL。在这里不需要作出对 TSS descriptor 的 DPL 权限进行检查。

  gate descriptor 机制提供了一层间接的访问层,主要用来控制权限的切换。
实际上:   

对于 call 0x2b:0x00000000 这条指令,processor 并不知道这个 selector 是 TSS selector 还是 task gate selector,在查找到 descriptor 后,processor 查看这个 descriptor 的 type 才能确定是 TSS selector 还是 task gate selector。

最后需注意:   (1)使用 jmp 指令进行转移,processor 不会将 eflags 中 NT 标志位置 1   (2)使用中断机制下的 TSS 进程切换,processor 将不作任务的权限检查动作。

5.7.1.4、 TSS 进程的返回
  从 TSS 机制切换的进程在执行完后使用 iret 指令返回原进程进,同样会发生新旧 TSS segment 的更新动作。
  进程通过使用 ret 返回时,processor 将不会从嵌套内层返回到的嵌套外层进程,也就是不会返回原进程。processor 对 ret 指令的处理,只会从 stack  pointer(ss : esp) 取返回地址。

对于使用进程使用了 iret 中断返回指令时:
(1)processor 将会检查当前的 eflags.NT 标志位是否为 1,也就是检查当前进程是否处于嵌套的内层。
(2)eflags.NT = 1,processor 将从当前 TSS segment 中的 link(old TSS selector)域中取出原来进程的 TSS selector。
(3)processor 将不作任何的权限检查,TSS selector 被加载到 TR.selector,TSS descriptor 同时被加载到 TR 的隐藏部分。
(4)processor 将清除当前的 eflags.NT 为 0。若是使用 ret 指令返回的,processor 是不会清 eflags.NT 为 0
(5)从 TSS segment 中加载新的进程执行环境,从新的 CS:EIP 处继续执行。 将原来的 TSS descriptor 重新置为 available 状态,使得可以再次进入。
-----------------------------------------------------------------------------
  由上可得,processor 遇到 ret 指令时,是不会对 eflags.NT 进行检查的。而使用 iret 指令,processor 将对 eflags.NT 进行检查。

[转载]x86 的 TSS 任务切换机制