跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

13-任务状态段(TSS)

1. 任务状态段 不要被名字所吓倒,它不过是一块位于内存中的结构体而已。

有一点需要注意的是,不要把它和 任务切换 关联起来( 切记 ),否则你会被搞晕,它只是位于内存中的一段数据。

Intel 白皮书给出TSS在内存中的图是这样的,它保存了一些重要的值。

抽象成结构体就是下面这个样子。

typedef struct TSS {

DWORD link; // 保存前一个 TSS 段选择子,使用 call 指令切换寄存器的时候由CPU填写。

// 这 6 个值是固定不变的,用于提权,CPU 切换栈的时候用 DWORD esp0; // 保存 0 环栈指针 DWORD ss0; // 保存 0 环栈段选择子 DWORD esp1; // 保存 1 环栈指针 DWORD ss1; // 保存 1 环栈段选择子 DWORD esp2; // 保存 2 环栈指针 DWORD ss2; // 保存 2 环栈段选择子 // 下面这些都是用来做切换寄存器值用的,切换寄存器的时候由CPU自动填写。

DWORD cr3; DWORD eip; DWORD eflags; DWORD eax; DWORD ecx; DWORD edx; DWORD ebx; DWORD esp; DWORD ebp; DWORD esi; DWORD edi; DWORD es; DWORD cs; DWORD ss; DWORD ds; DWORD fs; DWORD gs; DWORD ldt; // 这个暂时忽略 DWORD io_map; } TSS;

在学习调用门,中断门和陷阱门已经知道,代码发生提权的时候,是需要切换栈的。

之前遗留的一个问题是,栈段描述符和栈顶指针从哪里来?

那时只是简单的讲了一下是从 TSS 中来的。

如果代码从3环跨到0环,现在观察上面的图或者结构体,可以看到确实存在这么一个 SS0 和 ESP0。

提权的时候,CPU就从这个TSS里把SS0和ESP0取出来,放到 ss 和 esp 寄存器中。

2. CPU怎么找到TSS 前面已经知道,CPU可以通过 gdtr 寄存器来知道 GDT表在哪里,通过 idtr 寄存器知道 IDT 表在哪里。

实际上,CPU是通过 tr 寄存器来确定 TSS 的位置的。

和 gdtr,idtr 这些不同的是,tr 寄存器是 段寄存器 ,之前已经知道的段寄存器有 cs, ds, es, ss, fs, gs 。

也知道段寄存器有96位,还做过实验验证。

tr 寄存器中存放的就是描述了TSS段的相关信息,比如TSS段的基址,大小和属性。

可以通过

ltr

指令跟上TSS段描述符的选择子来加载TSS段。

该指令是特权指令,只能在特权级为0的情况下使用。

3. TSS 段描述符

| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节

|76543210|7 6 5 4 3210 |7 65 4 3210|76543210|76543210|76543210|76543210|76543210| 比特 |--------|-|-|-|-|---- |-|--|-|----|--------|--------|--------|--------|--------| 占位 | BASE |G|D|0|A|LIMIT|P|D |S|TYPE|<------- BASE 23-0 ------>|<-- LIMIT 15-0 ->| 含义 | 31-24 | |/| |V|19-16| |P | |B| |L| | |L |

当S=0, TYPE=1001或者TYPE=1011的时候,表示这是一个TSS段描述符。

当TSS段没被加载进 tr 寄存器时,TYPE=1001,一旦TSS被加载进 tr 寄存器,TYPE就变成了1011. 4. TSS的用途 保存0环、1环和2环的栈段选择子和栈顶指针 前面讲到了,在跨段提权的时候,需要切换栈,CPU会通过 tr 寄存器找到 TSS,取出其中的 SS0 和 ESP0 复制到 ss 和 esp 寄存器中。

这只是 TSS 的一个用途,也是现代 Windows 操作系统使用到的功能。

一次性切换一堆寄存器 TSS 的另一个用途是什么?

通过观察 TSS 的结构还发现 TSS 不仅存储了不同特权级下的 SS 和 ESP,还有 cs, esp, ss, esp 等等,这些后面不带数字的变量名,有着各自的用途。

可以通过

call/jmp + TSS段选择子

指令一次性把这些值加载到 CPU 对应的寄存器中。

同时,旧值将保存在旧的 TSS 中。

GDT 表中可以存放多个TSS描述符,这意味着内存中可以存在多份不同的TSS。

总有一个 TSS 是在当前使用中的,也就是 tr 寄存器指向的那个 TSS。

当使用

call/jmp + TSS段选择子

的时候,CPU做了以下几件事情。

把当前所有寄存器(TSS结构中有的那些寄存器)的值填写到当前 tr 段寄存器指向的 TSS 中 把新的 TSS 段选择子指向的段描述符加载到 tr 段寄存器中 把新的 TSS 段中的值覆盖到当前所有寄存器(TSS结构中有的那些寄存器)中 总结 本节主要讲了 TSS 的两个功能: 提权时栈切换用到了 TSS 切换一堆寄存器 本文始终没有把 TSS 和 任务切换 关联起来,只是为了避免给初学者造成困扰。

虽然 Intel 设计的初衷是用它来做任务切换,然而,在现代操作系统中(无论是 Windows 还是 Linux),都没有使用这种方式来执行任务切换,比如线程切换和进程切换。

主要原因是这种切换速度非常慢,一条指令要消耗200多个时钟周期。

至于现代操作系统如何进行线程或进程的切换,确实是用到了 TSS,但却不是靠切换call/jmp TSS 来切换任务。

(你可参考这一篇: http://guojing.me/linux-kernel-architecture/posts/process-switch/) 下一小节,会对 TSS 的第二个功能做一个实验。

有兴趣,可以自己尝试着挑战一下。

相关文章