x86体系下Linux中的任务切换与TSS

运维 系统运维
本篇文章主要是向大家介绍了x86体系下Linux中的任务切换与TSS的相关知识,作者通过程序实例进行讲解,相信看后大家对Linux系统会理解的更加深刻。

本篇文章主要是向大家介绍了x86体系下Linux中的任务切换TSS的相关知识,作者通过程序实例进行讲解,相信看后大家对Linux系统会理解的更加深刻。

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定义为:

  1. #define INIT_TSS  {                              
  2. .esp0        = sizeof(init_stack) + (long)&init_stack,      
  3. .ss0        = __KERNEL_DS,                      
  4. .esp1        = sizeof(init_tss[0]) + (long)&init_tss[0],      
  5. .ss1        = __KERNEL_CS,                      
  6. .ldt        = GDT_ENTRY_LDT,                  
  7. .io_bitmap_base    = INVALID_IO_BITMAP_OFFSET,              
  8. .io_bitmap    = { [ 0 ... IO_BITMAP_LONGS] = ~0 },          

2.初始化tss:

  1. struct tss_struct * t = init_tss + cpu;  
  2. ...  
  3. load_esp0(t, thread);  
  4. set_tss_desc(cpu,t);  
  5. cpu_gdt_table[cpu][GDT_ENTRY_TSS].b &= 0xfffffdff;  
  6. load_TR_desc();  

相关函数或者宏为:

  1. #define load_TR_desc() __asm__ __volatile__("ltr %%ax"::"a" (GDT_ENTRY_TSS*8))  
  2. static inline void __set_tss_desc(unsigned int cpu, unsigned int entry, void *addr)  
  3. {  
  4. _set_tssldt_desc(&cpu_gdt_table[cpu][entry], (int)addr,  
  5. offsetof(struct tss_struct, __cacheline_filler) - 1, 0x89);  
  6. }  
  7. #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中:

  1. struct tss_struct *tss = init_tss + cpu;  
  2. ...  
  3. load_esp0(tss, next); 

从而改变了tss的esp0。
此时如果进程在用户态被中断,机器切到ring0,从tr中取出唯一的tss段,找到它的esp0,将堆栈切过去即可,然后把所有的其它寄存器都保存在tss当前的esp0指示的内核也就是ring0的堆栈上。

责任编辑:冯宇 来源: Linux社区
相关推荐

2011-12-19 10:55:58

云计算中国电信

2010-12-09 14:22:08

2021-08-16 13:26:49

Linuxx86 Linux

2011-05-16 15:31:51

英特尔x86关键任务

2011-12-01 11:09:48

AMDx86服务器英特尔

2009-06-18 09:11:03

微软Windows 7下载

2010-04-02 09:15:20

服务器

2021-07-07 11:35:17

Linux内存段寻址

2011-12-14 13:02:05

Power虚拟机X86平台服务器

2023-02-22 09:53:55

架构芯片

2021-06-07 15:20:22

Linux X861MB内存BIOS

2012-09-19 09:51:45

Windows Serx86服务器选型

2011-06-23 08:33:58

大型机x86服务器

2015-05-12 17:16:05

戴尔云计算

2011-11-10 09:26:48

Solaris 11

2009-08-28 14:38:33

2011-02-20 22:23:43

X86虚拟化XenServer

2011-10-27 22:13:22

vForum虚拟化VMware

2014-03-17 16:52:10

ARMx86服务器

2020-10-13 10:51:10

Linux内核
点赞
收藏

51CTO技术栈公众号