x86體系下Linux中的任務切換與TSS
本篇文章主要是向大家介紹了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定義為:
- #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的堆棧上。


2010-04-02 09:15:20
2020-09-23 12:42:08




