天天看點

linux程序tss和ldt,x86體系下linux中的任務切換與TSS

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的堆棧上。

再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow

标簽:__,x86,寄存器,cpu,tss,切換,linux,esp0,TSS

來源: https://www.cnblogs.com/ksiwnhiwhs/p/10390327.html