GDT簡介
在intel 8086體系結(jié)構(gòu)中,有6個段寄存器,CPU取址采用段:偏移模式犹撒。從80286開始,為描述不同的段結(jié)構(gòu)粒褒,x86架構(gòu)引入了GDT(Global Descriptor Table识颊,全局描述符表)。GDT可以描述程序各段的組成結(jié)構(gòu)奕坟,其中主要包含了基地址(base address)祥款、大小(limit)、權(quán)限(privilege)等信息月杉。值得一提的是刃跛,GDT中還可以包含LDT的描述符。LDT苛萎,即Local Descriptor Table(局部描述表)桨昙,可以用來描述每個程序獨(dú)立的局部段描述信息。
這種架構(gòu)的出現(xiàn)腌歉,其實(shí)和當(dāng)時操作系統(tǒng)的發(fā)展緊密相關(guān)蛙酪。因為操作系統(tǒng)的實(shí)現(xiàn),除了要依靠軟件設(shè)計翘盖,還需要底層硬件對某些特性的支持桂塞。我們可以簡單地看看GDT的結(jié)構(gòu),沒必要進(jìn)行深入的理解最仑。因為GDT中某些丑陋的設(shè)計是出于對老平臺的兼容以及對當(dāng)時的操作系統(tǒng)的考量藐俺,所以我們沒有必要將精力放在研究這些遺留問題之上。
萬惡的“Backward Campability”泥彤,因此我們在實(shí)現(xiàn)操作系統(tǒng)時沒有辦法繞過GDT欲芹,因此我們必須對其有個簡單的認(rèn)識。
簡要的理論知識(可跳過)
GDT描述符
GDT長度是可變的
因此我們需要告訴底層硬件吟吝,我們操作系統(tǒng)中的GDT位置及大小菱父。為此x86體系定義了如下數(shù)據(jù)結(jié)構(gòu)用以描述GDT信息(包括GDT的大小,以及存放GDT的線性地址)剑逃。
對上圖需要進(jìn)行簡單的補(bǔ)充:Size為GDT所占的字節(jié)大小-1浙宜。因為size為兩字節(jié),所以GDT最大的大小為65535字節(jié)(供8192表項)蛹磺。
x86體系架構(gòu)梭姓,提供lgdt指令來加載gdt描述符烙丛。即lgdt [上圖所示的表的地址]
GDT表項
GDT的每個表項,抽象地可以看成包含四個字段的數(shù)據(jù)結(jié)構(gòu):基地址(Base)赴背,大芯哉搿(Limit),標(biāo)志(Flag),訪問信息(Access Byte)。
GDT中每個表項在內(nèi)存中的實(shí)際布局如下圖所示:
為什么描述段基地址的Base以及段大小的Limit字段會被拆成這種丑陋的結(jié)構(gòu)岁忘?
因為需要兼容286架構(gòu),啊哈哈哈哈~区匠。
Base和Limit字段很好理解干像,F(xiàn)lag和Access Byte字段如下圖所示:
其中每個字段代表的意義在此不做展開,這里只指出一點(diǎn):Privl字段描述了對應(yīng)段的權(quán)限等級(Ring 0~3)驰弄。
LDT
LDT麻汰,Local Descriptor Table,局部描述符表揩懒。在分頁機(jī)制出現(xiàn)以前的操作系統(tǒng)中并沒有虛擬內(nèi)存(Virtual Memory)這個概念什乙。為了讓不同程序的數(shù)據(jù)彼此互不干擾挽封,x86架構(gòu)引入了LDT概念已球,期望操作系統(tǒng)可以通過為不同的應(yīng)用程序設(shè)置不同的LDT而隔離程序間的數(shù)據(jù)。程序在使用分段機(jī)制取址的時候辅愿,可是通過設(shè)置選擇子(selector)的特定位而告訴CPU是從GDT還是從LDT中選擇對應(yīng)的段信息智亮,如下圖所示。更多的細(xì)節(jié)在此不做展開点待。
<a id="gdtend" name="gdtend"></a>
隨著分頁機(jī)制的提出阔蛉,GDT所代表的分段機(jī)制逐漸廢棄。對于現(xiàn)代操作系統(tǒng)而言癞埠,GDT的作用幾乎只是用來改變當(dāng)前CPU執(zhí)行的特權(quán)級状原,并且改變CPU的特權(quán)級也只有這種方式。
用代碼操作GDT
加載GDT描述符的任務(wù)苗踪,必須由匯編指令完成颠区,因此我們實(shí)現(xiàn)了一個匯編方法_flush_gdt, 并將其暴露給C文件通铲。注釋雖然用英文寫的毕莱,但是我相信大家應(yīng)該能閱讀。匯編代碼使用AT&T語法颅夺,使用gas作為編譯器朋截。
使用匯編代碼加載GDT
(完成這段代碼花了我一個小時,淚奔~~吧黄。因為一個小小的typo導(dǎo)致debug了好久部服,各位可直接拿去使用)
#function for loading gdt
.global _flush_gdt
.type _flush_gdt, @function
_flush_gdt:
movl 4(%esp), %eax #Get the first argument, which is the pointer to gdt descriptor
lgdt (%eax) #load gdt descriptor
movw $0x10, %ax #0x10 is the offset in the GDT to our data segment
mov %ax, %ds #copy %ax to ds,es,fs,gs,ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
ljmp $0x08,$flush_lable #Using a long jump to set %cs to 0x8
flush_lable:
ret
使用C語言填充GDT表項
由于代碼里都有很好的注釋,我就不再做過多介紹了拗慨。
gdt.h
主要是定義了RING 0和RING 3下數(shù)據(jù)段和代碼段的Flag與Access Byte字段廓八。
#ifndef _KERNEL_GDT_H
#define _KERNEL_GDT_H
#include <stdint.h>
//Each define here is for a specific bit(bits) flag for a gdt entry
#define SEG_DESCTYPE(x) ((x) << 0x04) //Descriptor type (0 for system, 1 for code/data)
#define SEG_PRES(x) ((x) << 0x07) // Present
#define SEG_SAVL(x) ((x) << 0x0C) // Available for system use
#define SEG_LONG(x) ((x) << 0x0D) // Long mode
#define SEG_SIZE(x) ((x) << 0x0E) // Size (0 for 16-bit, 1 for 32)
#define SEG_GRAN(x) ((x) << 0x0F) // Granularity (0 for 1B - 1MB, 1 for 4KB - 4GB)
#define SEG_PRIV(x) (((x) & 0x03) << 0x05) // Set privilege level (0 - 3)
#define SEG_DATA_RD 0x00 // Read-Only
#define SEG_DATA_RDA 0x01 // Read-Only, accessed
#define SEG_DATA_RDWR 0x02 // Read/Write
#define SEG_DATA_RDWRA 0x03 // Read/Write, accessed
#define SEG_DATA_RDEXPD 0x04 // Read-Only, expand-down
#define SEG_DATA_RDEXPDA 0x05 // Read-Only, expand-down, accessed
#define SEG_DATA_RDWREXPD 0x06 // Read/Write, expand-down
#define SEG_DATA_RDWREXPDA 0x07 // Read/Write, expand-down, accessed
#define SEG_CODE_EX 0x08 // Execute-Only
#define SEG_CODE_EXA 0x09 // Execute-Only, accessed
#define SEG_CODE_EXRD 0x0A // Execute/Read
#define SEG_CODE_EXRDA 0x0B // Execute/Read, accessed
#define SEG_CODE_EXC 0x0C // Execute-Only, conforming
#define SEG_CODE_EXCA 0x0D // Execute-Only, conforming, accessed
#define SEG_CODE_EXRDC 0x0E // Execute/Read, conforming
#define SEG_CODE_EXRDCA 0x0F // Execute/Read, conforming, accessed
#define GDT_CODE_PL0 (SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | SEG_PRIV(0) | SEG_CODE_EXRD)
#define GDT_DATA_PL0 (SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | SEG_PRIV(0) | SEG_DATA_RDWR)
#define GDT_CODE_PL3 (SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | SEG_PRIV(3) | SEG_CODE_EXRD)
#define GDT_DATA_PL3 (SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | SEG_PRIV(3) | SEG_DATA_RDWR)
struct gdt_descriptor_struct{
uint16_t limit;
uint32_t base;
}
__attribute__((packed));
typedef struct gdt_descriptor_struct gdt_descriptor;
typedef uint64_t gdt_entry;
void init_gdt();
#endif
gdt.c
init_gdt()函數(shù)完成了對GDT的設(shè)置和加載厦酬。主要是設(shè)置了ring 0的代碼段和數(shù)據(jù)段(各一個),ring 3的代碼段和數(shù)據(jù)段(各一個)瘫想。最開始的空表項是硬件必須的(如果沒有這項仗阅,bochs會報錯)。
#include <kernel/gdt.h>
gdt_entry entrys[5];
gdt_descriptor gdtd;
extern void _flush_gdt(uint32_t gdtp);
static gdt_entry create_entry(uint32_t base, uint32_t limit, uint16_t flag){
uint64_t entry ;
// Create the high 32 bit segment
entry = limit & 0x000F0000; // set limit bits 19:16
entry |= (flag << 8) & 0x00F0FF00; // set type, p, dpl, s, g, d/b, l and avl fields
entry |= (base >> 16) & 0x000000FF; // set base bits 23:16
entry |= base & 0xFF000000; // set base bits 31:24
// Shift by 32 to allow for low part of segment
entry <<= 32;
// Create the low 32 bit segment
entry |= base << 16; // set base bits 15:0
entry |= limit & 0x0000FFFF; // set limit bits 15:0
return entry;
}
void init_gdt(){
gdtd.limit = (sizeof(gdt_entry) * 5) - 1;
gdtd.base = (uint32_t)entrys;
//Fill data to gdt entries
entrys[0] = create_entry(0, 0, 0); //Needed
entrys[1] = create_entry(0, 0x000FFFFF, (GDT_CODE_PL0)); // Ring 0 code section
entrys[2] = create_entry(0, 0x000FFFFF, (GDT_DATA_PL0)); // Ring 0 data section
entrys[3] = create_entry(0, 0x000FFFFF, (GDT_CODE_PL3)); // Ring 3 code section
entrys[4] = create_entry(0, 0x000FFFFF, (GDT_DATA_PL3)); // Ring 3 data section
_flush_gdt((uint32_t)&gdtd);
}
實(shí)際運(yùn)行結(jié)果
如下圖所示国夜,使用bochs加載我的操作系統(tǒng)之后减噪,通過自帶的debug功能能得到下述的GDT信息,和我們在C代碼中設(shè)置的一致车吹。Succeed~
參考文獻(xiàn)
- https://www.wikiwand.com/en/Global_Descriptor_Table
- http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
- http://wiki.osdev.org/GDT_Tutorial
- http://wiki.osdev.org/Global_Descriptor_Table
- http://www.osdever.net/bkerndev/Docs/gdt.htm
- http://bochs.sourceforge.net/doc/docbook/user/internal-debugger.html