x86體系下Linux中的任務(wù)切換與TSS
本篇文章主要是向大家介紹了x86體系下Linux中的任務(wù)切換與TSS的相關(guān)知識,作者通過程序?qū)嵗M(jìn)行講解,相信看后大家對Linux系統(tǒng)會理解的更加深刻。
TSS的作用舉例:保存不同特權(quán)級別下任務(wù)所使用的寄存器,特別重要的是esp,因為比如中斷后,涉及特權(quán)級切換時(一個任務(wù)切換),首先要切換棧,這個棧顯然是內(nèi)核棧,那么如何找到該棧的地址呢,這需要從tss段中得到,這樣后續(xù)的執(zhí)行才有所依托(在x86機(jī)器上,c語言的函數(shù)調(diào)用是通過棧實現(xiàn)的)。只要涉及地特權(quán)環(huán)到高特權(quán)環(huán)的任務(wù)切換,都需要找到高特權(quán)環(huán)對應(yīng)的棧,因此需要esp2,esp1,esp0起碼三個esp,然而Linux只使用esp0。
TSS是什么:TSS是一個段,段是x86的概念,在保護(hù)模式下,段選擇符參與尋址,段選擇符在段寄存器中,而tss段則在tr寄存器中。
Intel的建議:為每一個進(jìn)程準(zhǔn)備一個獨立的TSS段,進(jìn)程切換的時候切換tr寄存器使之指向該進(jìn)程對應(yīng)的TSS段,然后在任務(wù)切換時(比如涉及特權(quán)級切換的中斷)使用該段保留所有的寄存器。
Linux的做法:
1.Linux沒有為每一個進(jìn)程都準(zhǔn)備一個tss段,而是每一個cpu使用一個tss段,tr寄存器保存該段。進(jìn)程切換時,只更新唯一tss段中的esp0字段到新進(jìn)程的內(nèi)核棧。
2.Linux的tss段中只使用esp0和iomap等字段,不用它來保存寄存器,在一個用戶進(jìn)程被中斷進(jìn)入ring0的時候,tss中取出esp0,然后切到esp0,其它的寄存器則保存在esp0指示的內(nèi)核棧上而不保存在tss中。
3.結(jié)果,Linux中每一個cpu只有一個tss段,tr寄存器永遠(yuǎn)指向它。符合x86處理器的使用規(guī)范,但不遵循intel的建議,這樣的后果是開銷更小了,因為不必切換tr寄存器了。
Linux的實現(xiàn):
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();
相關(guān)函數(shù)或者宏為:
- #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)
經(jīng)過上述的初始化,tr永遠(yuǎn)指向唯一的tss段,然而tss段中的esp0以及iomap卻是不斷隨著進(jìn)程切換而變化的。
3.進(jìn)程切換時切換全局唯一tss段中的esp0以及iomap即可:
在__switch_to中:
- struct tss_struct *tss = init_tss + cpu;
- ...
- load_esp0(tss, next);
從而改變了tss的esp0。
此時如果進(jìn)程在用戶態(tài)被中斷,機(jī)器切到ring0,從tr中取出唯一的tss段,找到它的esp0,將堆棧切過去即可,然后把所有的其它寄存器都保存在tss當(dāng)前的esp0指示的內(nèi)核也就是ring0的堆棧上。


2020-09-23 12:42:08




