1.
void sched_init(void)
{...
00392
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
00393
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
...}
gdt:全局描述符,一共定義256個描述符,GDT本身不是一個段而是線形空間
的一個數據結構。GDT的基地址和長度需要加載進GDTR寄存器。GDT的基地
址要以8個字節對齊獲得最佳的運行效率。因爲GDT描述符號以8字節爲單位
。
2.結構相關解釋
typedef struct desc_struct {
unsigned long a,b;
} desc_table;
這裏使用兩個標準long四字節的存放gdt和ldt。
首先程序運行在虛擬地址上的(實際程序都如此在保護模式下)虛擬地址是
由兩部分組成 段選擇符號(16)+偏梁量(有效地址32位);通過段選擇符號來
訪問相應的段描述符號,由段描述符中提供的段基地址,限長,屬性及虛擬地
址另外的偏移來構造線形地址。
a.段選擇符:是虛擬地址組成部分見上面描述。其中前16位是段的唯一標誌
,並且提供了段描述符表GDT中的一個字段的偏移量。具體的組成如下面表
示:
|15-3描述符索引index|2是TI|1-0是RPL|
index:索引值用於尋找對應的描述符表中的具體段
TI: 表示是GDT還是LDT
RPL:類似權限的東西
Remark:段選擇符總共提供6個,CS DS SS ES FS GS,其實段描述符信息在訪
問的時候由cpu自動通過對16位段選擇符的解碼來加載到一個所謂的影子寄
存器中,這樣速度會非常快。可以直接使用當然對於我們用戶可能是看不到
這個寄存器的存在和操作。
b.段描述符表,如同gdt上面的也屬於段描述符表的一種,用於提供段選擇符
號的最終訪問獲得存儲的段信息。
c:最終的線形地址確立是由段描述符中提供的段基址+虛擬地址的段偏移。
然後再根據分頁的方法獲得最終的物理地址。
3.描述格式
//這個兩個低層調用及最後一個實現宏用於調用設置TSS,LDT段描述孵內容
。
//n:爲描述符的地址,即後面會往n開始到n+7位置中寫入一個完整的8個字
節的段描述符。
//add:基地址,即段信息中所保存的段基址
//0x8X是屬性描述表示是何種類型的段TSS or LDT or the other
#define set_tss_desc(n, addr) _set_tssldt_desc(((char *)
(n)),((int)(addr)),"0x89")
#define set_ldt_desc(n, addr) _set_tssldt_desc(((char *)
(n)),((int)(addr)),"0x82")
段描述符的格式
高32位中
|31-24:基地址
31..24|23:G|22:D/B|21:0|20:AVL|19-16:Limit19..16|15:P|14-13:DPL|1
2:S|11-8:TYPE|7-0:基地址23..16|
低32位中
|31-16:base address 15..0|15-0:Segment Limit|
4.
//看具體實現加載段描述符的過程
#define _set_tssldt_desc ( n, addr, type)
//104是段限長,TSS最長104個字節放到低2個字節 15-0:Segment Limit
__asm__ ("movw $104,%1/n/t" /
//ax段基地址的低16位放入 31-16:base address 15..0
"movw %%ax,%2/n/t" /
//右移16位 ax中變成高16位地址
"rorl $16,%%eax/n/t" /
//將高16位地址中的低8位放入 7-0:基地址23..16
"movb %%al,%3/n/t" /
//將type放入|15:P|14-13:DPL|12:S|11-8:TYPE
"movb $" type ",%4/n/t" /
//|23:G|22:D/B|21:0|20:AVL|19-16:Limit19..16|全部置零
"movb $0x00,%5/n/t" /
//高基地址8位放入最高位
"movb %%ah,%6/n/t" /
"rorl $16,%%eax" /
//輸入規則%0指定eax存放addr段基址,%1~%6均爲指定m內存地址分別
爲n,n+2,n+4...地址內容
::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), /
"m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) /
)
5.加載任務TSS,LDT等段選擇符和段描述符加載到任務寄存器中的實現
n:第n號任務或局部表的段偏移
//ltr或者lldt命令加載%%ax的段選擇符,此段選擇可以看到是由低層的一
個宏_TSS和_LDT來返回來實現。
#define ltr (n) __asm__("ltr %%ax"::"a" (_TSS(n)))
#define lldt (n) __asm__("lldt %%ax"::"a" (_LDT(n)))
//_TSS _LDT將返回一個當前任務信息或者局部描述符的對應段偏移是相對
於GDT中的偏移,這裏使用一個簡單的計算公式 n*(8+8)+起始偏移數目*8,
這裏我們要看下這些TSS和LDT在GDT中位置。
#define _TSS (n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
#define _LDT (n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
6.GDT具體分佈
0 |NULL | 不可以使用的GDT(0)
1 |系統代碼段CS | 操作系統的自己使用
2 |系統數據段DS | 操作系統的數據段
3 |系統調用 | 一些調用的信息符
4 |TSS0 | 這裏很容易看到FIRST_TSS_ENTRY=4
5 |LDT0 | 這裏很容易看到FIRST_LDT_ENTRY=5
....
每個域都是8個字節所以第n個TSS地址 = TSS0的基址+ (n*LSSi-LSSi-1)
= FIRST_TSS_ENTRY*8 + n*(8+8)
= FIRST_TSS_ENTRY<<3+n<<4
同理可以推導出LDT的地址進行加載
至此基本清楚段選擇符 描述符 段一些關係和如何設置的操作
#define str (n)
__asm__("str %%ax/n/t" /
"subl %2,%%eax/n/t" /
"shrl $4,%%eax" /
:"=a" (n) /
:"a" (0),"i" (FIRST_TSS_ENTRY<<3))