您的位置:首页 > 运维架构 > Linux

任务状态段TSS

2017-05-02 15:28 211 查看

任务状态段

不要被名字所吓倒,它不过是一块位于内存中的结构体而已。有一点需要注意的是,不要把它和任务切换关联起来(切记),否则你会被搞晕,它只是位于内存中的一段数据。

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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
在学习调用门,中断门和陷阱门已经知道,代码发生提权的时候,是需要切换栈的。

之前遗留的一个问题是,栈段描述符和栈顶指针从哪里来?那时只是简单的讲了一下是从 TSS 中来的。

如果代码从3环跨到0环,现在观察上面的图或者结构体,可以看到确实存在这么一个 SS0 和 ESP0。提权的时候,CPU就从这个TSS里把SS0和ESP0取出来,放到 ss 和 esp 寄存器中。

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的情况下使用。

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 |
1
2
3
4
5
6
1
2
3
4
5
6
当S=0, TYPE=1001或者TYPE=1011的时候,表示这是一个TSS段描述符。当TSS段没被加载进 tr 寄存器时,TYPE=1001,一旦TSS被加载进 tr 寄存器,TYPE就变成了1011.

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多个时钟周期。

至于现代操作系统如何进行线程或进程的切换,这是以后要讲的事情。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内核 linux GDT TSS