tss 的作用举例: 保存不同特权级别下任务所使用的寄存器, 特别重要的是 esp, 因为比如中断后, 涉及特权级切换时(一个任务切换), 首先要切换栈, 这个栈显然是内核栈, 那么如何找到该栈的地址呢, 这需要从 tss 段中得到, 这样后续的执行才有所依托(在 x86 机器上, c 语言的函数调用是通过栈实现的). 只要涉及地特权环到高特权环的任务切换, 都需要找到高特权环对应的栈, 因此需要 esp2,esp1,esp0 起码三个 esp, 然而 Linux 只使用 esp0.
tss 是什么: tss 是一个段, 段是 x86 的概念, 在保护模式下, 段选择符参与寻址, 段选择符在段寄存器中, 而 tss 段则在 tr 寄存器中.
intel 的建议: 为每一个进程准备一个独立的 tss 段, 进程切换的时候切换 tr 寄存器使之指向该进程对应的 tss 段, 然后在任务切换时 (比如涉及特权级切换的中断) 使用该段保留所有的寄存器.
Linux 的做法:
1.Linux 没有为每一个进程都准备一个 tss 段, 而是每一个 CPU 使用一个 tss 段, tr 寄存器保存该段. 进程切换时, 只更新唯一 tss 段中的 esp0 字段到新进程的内核栈.
2.Linux 的 tss 段中只使用 esp0 和 iomap 等字段, 不用它来保存寄存器, 在一个用户进程被中断进入 ring0 的时候, tss 中取出 esp0, 然后切到 esp0, 其它的寄存器则保存在 esp0 指示的内核栈上而不保存在 tss 中.
3. 结果, Linux 中每一个 CPU 只有一个 tss 段, tr 寄存器永远指向它. 符合 x86 处理器的使用规范, 但不遵循 intel 的建议, 这样的后果是开销更小了, 因为不必切换 tr 寄存器了.
Linux 的实现:
1. 定义 tss:
struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };(arch/i386/kernel/init_task.c)
INIT_TSS 定义为:
- #define INIT_TSS {
- /
- .esp0 = sizeof(init_stack) + (long)&init_stack, /
- .ss0 = __KERNEL_DS, /
- .esp1 = sizeof(init_tss[0]) + (long)&init_tss[0], /
- .ss1 = __KERNEL_CS, /
- .ldt = GDT_ENTRY_LDT, /
- .io_bitmap_base = INVALID_IO_BITMAP_OFFSET, /
- .io_bitmap = {
- [ 0 ... IO_BITMAP_LONGS] = ~0
- }, /
- }
2. 初始化 tss:
- struct tss_struct * t = init_tss + CPU;
- ...
- load_esp0(t, thread);
- set_tss_desc(CPU,t);
- cpu_gdt_table[CPU][GDT_ENTRY_TSS].b &= 0xfffffdff;
- load_TR_desc();
相关函数或者宏为:
- #define load_TR_desc() __asm__ __volatile__("ltr %%ax"::"a" (GDT_ENTRY_TSS*8))
- static inline void __set_tss_desc(unsigned int CPU, unsigned int entry, void *addr)
- {
- _set_tssldt_desc(&cpu_gdt_table[CPU][entry], (int)addr,
- offsetof(struct tss_struct, __cacheline_filler) - 1, 0x89);
- }
- #define set_tss_desc(CPU,addr) __set_tss_desc(CPU, GDT_ENTRY_TSS, addr)
经过上述的初始化, tr 永远指向唯一的 tss 段, 然而 tss 段中的 esp0 以及 iomap 却是不断随着进程切换而变化的.
3. 进程切换时切换全局唯一 tss 段中的 esp0 以及 iomap 即可:
在__switch_to 中:
- struct tss_struct *tss = init_tss + CPU;
- ...
- load_esp0(tss, next);
从而改变了 tss 的 esp0.
此时如果进程在用户态被中断, 机器切到 ring0, 从 tr 中取出唯一的 tss 段, 找到它的 esp0, 将堆栈切过去即可, 然后把所有的其它寄存器都保存在 tss 当前的 esp0 指示的内核也就是 ring0 的堆栈上.
来源: http://www.bubuko.com/infodetail-2957525.html