Bran的核心開發指南(5)
全域性描述表(GDT)
386的各種保護措施的一個重要組成部分是 全域性描述表(Global Descriptor Table),也就是 GDT。GDT為記憶體的特定部份定義基本訪問許可權。我們能使用GDT的一個入口來建立一種程式段異常處理機制:讓核心能終止一個正在執行非法操作的程式。大部分 現代作業系統使用叫做“記憶體分頁”的記憶體管理模式來實現這一點:這可以更靈活而且彈性更高。GDT同時還能定義記憶體中的某個區域是可被執行的,還是資料。GDT也能 定義任務狀態分段(TSSes)。TSS被用在基於硬體的多工處理系統[譯者注:如SMP,對稱多處理器系統]中,我們 就不在這裡討論了。但請記住,TSS並不是實現多工處理的唯一方法。
你會發現GRUB已經為你安裝了一個GDT,但是如果我們覆蓋了GRUB使用的那部分記憶體,GDT將會失效, 並且會產生被稱為“三鍵錯誤”[譯者注:triple fault,三鍵即熱啟動的那個三個鍵]。簡而言之, 發生錯誤後,計算機會重啟。解決這個問題的方法是在一段我們知道 地址並且可以訪問的記憶體上建立我們自己的GDT。這包括構造 新的GDT, 告訴處理器它在記憶體中的地址,最後用我們 新的入口資料載入CPU的CS、DS、ES、FS 和GS暫存器。CS暫存器就是程式碼段。它將 告訴CPU進入GDT的偏移量。這將獲得執行當前程式碼的許可權。DS暫存器是相似的功能,但它不是針對程式碼,而是針對資料段,它定義的是當前資料的許可權。ES、FS、和GS暫存器只不過是DS暫存器 的替代品,對我們並不重要。
GDT本身就是一連串的64字長的入口。這些入口定義了允許操作的記憶體區域從哪裡開始,哪裡結束,同時定義了每個入口的許可權。一個通常的規則是GDT的第一個入口,入口0,就是我們知道的NULL描述符。任何暫存器都不能被設定為0,否則將發生一般性保護錯誤(General Protection fault),這是CPU的一項保護特徵。一般性保護錯誤和其它的一些異常將在下一章中斷服務例程(ISR)中進行詳細說明。
每一個GDT入口同時也定義處理器正在執行的當前片段是系統程式在佔用(Ring 0)還是應用程式在佔用(Ring 3)。當然還有其他型別,但是那些並不重要。當今主要的作業系統只使用Ring 0和Ring 3。有一條基本規則:任何試圖訪問系統或Ring 0資料的應用程式都將會引起異常。這種保護措施用於保護核心免遭應用程式的破壞 。在GDT作用範圍內,Ring等級告訴CPU它是否被允許執行特定的指令。某些指令的許可權很高,意味著它只能在較高的Ring等級上執行。這樣的例子有cli和sti指令。它們分別禁用和啟用中斷。如果一個應用程式被允許使用匯編指令cli或sti,那麼它就可以 有效地使核心停止執行。你將在後面的章節學到更多的中斷。
每個GDT入口的通道和粒度可以這樣被定義:
7 6 5 4 3 0
P DPL DT Type
P - 這是當前的段嗎?(1 = Yes)
DPL - 哪個Ring等級(0 to 3)
DT - 描述符型別
Type - 什麼型別?
7 6 5 4 3 0
G D 0 A Seg Len. 19:16
G - 粒度(0 = 1byte, 1 = 4kbyte)
D - 運算元大小(0 = 16bit, 1 = 32-bit)
0 - 永遠是0
A - 系統可見(總被設定為0)
在這本核心指南里,我們只用3個入口來建立一個GDT。為什麼是3個?我們需要在一開始有一個“啞元”(dummy)描述符來為作為處理器的記憶體保護功能的NULL段。 我們需要為程式碼段(Code Segment)和資料段(Data Segment)暫存器各準備一個入口。我們用匯編操作符lgdt來告訴CPU新的GDT在哪裡。需要給'lgdt'一個指向特殊的48-bit結構的指標 。因為GDT的限制,這個特殊的48-bit結構由16-bits(同樣,當我們使用一個在GDT中不存在的段時,核心需要它來產生一般性保護錯誤)和32-bits(儲存GDT自身的地址)構成。
我們可以用一個3入口的簡單陣列來定義GDT. 對於這個特殊的GDT指標,我們只需要申明一個。 我們把它叫做gp。建立一個新檔案gdt.c。按指南前部分提到的方法在build.bat中新增內容,以便讓GCC來執行編譯工作。我再一次提醒你:為了建立核心,你需要把gdt.o新增到LD聯結器的檔案列表裡。下面是gdt.c前半部分的原始碼,請仔細分析:
#include
/* 定義一個GDT入口. 我們稱之為包裝,因為
* 他阻止編譯器做他認為最好的事:用包裝的方法阻止
* 編譯器進行所謂的“優化” */
struct gdt_entry
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
/* 包括如下界限的特殊指標: GDT開始的最大位元組, 負1.
* 同樣,這需要被包裝 */
struct gdt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
/* 這是GDT, 3個入口, 最後是特殊的GDT指標*/
struct gdt_entry gdt[3];
struct gdt_ptr gp;
/* 這是 start.asm的一個方法. 我們用這個方法適當的過載
* 新的段暫存器 */
extern void gdt_flush();
用'gdt.c'管理GDT
你會注意到我們對一個並不存在的函式gdt_flush()加了一條申明. gdt_flush()函式使用一個特殊的指標來告訴CPU新的GDT的位置,正如在上面你所看到的。我們需要重新載入新的段暫存器,並且最後跳轉到新的程式碼段。研究下面程式碼,然後把它新增到start.asm中stublet後的那個無窮迴圈後。
這會建立一個新的段暫存器. 我們需要做
一些特別的命令來設定CS. 我們要做的就稱為
far jump. 一個跳轉像偏移量一樣包括一個段.
這裡用'extern void gdt_flush();'來申明
global _gdt_flush ;允許C程式連線
extern _gp ;表明'_gp'在另一個檔案裡
_gdt_flush:
lgdt [_gp] ;用這個特殊的指標'_gp'載如GDT
mov ax, 0x10 ;0x10 is the offset in the GDT to our data segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:flush2 ;0x08 is the offset to our code segment: Far jump!
flush2:
ret ;回到C程式!
把這些內容新增到start.asm中
僅僅在記憶體中為GDT保留空間是不夠的。我們需要向GDT入口裡寫入值,設定“gp”GDT指標,然後呼叫函式gdt_flush()來更新。下面要介紹的是一個特殊的函式gdt_set_entry(),它使用簡單好用的引數來進行所有移位(shift),以將合適的值填充進GDT入口。你必須在system.h中新增這兩個函式的原型(我們至少需要gdt_install),以便我們能在main.c中使用它們。 請仔細分析下面這些程式碼,它們是gdt.c的後半部分。
/* 在全域性描述表(GDT)中建立一個描述符*/
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
/* 設定描述符的基地址*/
gdt[num].base_low = (base &0xFFFF);
gdt[num].base_middle = (base >>16) &0xFF;
gdt[num].base_high = (base >>24) &0xFF;
/* 設定描述符的界限 */
gdt[num].limit_low = (limit &0xFFFF);
gdt[num].granularity = ((limit >>16) &0x0F);
/* 最後,設定粒度和訪問標識*/
gdt[num].granularity |= (gran &0xF0);
gdt[num].access = access;
}
/* 這裡需要被主函式呼叫。這裡要建立特殊的GDT
* 指標, 在GDT裡建立最開始的3個入口, 然後
* 為了告訴處理器新的GDT在哪並且更新新的段寄
* 存器,我們需要在彙編檔案裡呼叫gdt_flush()*/
void gdt_install()
{
/* 設立GDT指標和範圍*/
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = &gdt;
/* NULL描述符 */
gdt_set_gate(0, 0, 0, 0, 0);
/* 第二個入口就是我們的程式碼段(Code Segment)。基地址
* 是0, 大小是4GBytes, 粒度為4KByte,
* 使用32-bit操作碼,是一個程式碼段描述符。
* 請檢查本章前面提到的那個表格,以確保每個
* 變數的意思正確。*/
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/*第三個入口 是我們的資料段(Data Segment)。它完全和 程式碼段(Code Segment)
* 相同, 但是這個入口的訪問標識說明這是一個資料段 */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
/* 把舊的GDT刪除,安裝新的更新! */
gdt_flush();
}
把這些新增到gdt.c。它從事的是一些和GDT相關的骯髒工作!不要忘記在system.h中設定函式原型!
既然GDT載入器基本構架已準備就緒並且我們已經把它編譯連線進了核心,我們需要呼叫gdt_install()以讓它工作。開啟main.c,然後再main()函式的最開頭新增“gdt_install();”。正如你在本章中所學到的,GDT需要在最開始就被初始化。它是十分重要的。你現在可以編譯連線並將核心弄到軟盤裡來測試了。你不會在螢幕上看到任何變化,因為這是一個內在的變化。接下來,開始學下一章 中斷描述符表(IDT)吧!
本文轉自
http://rammaker.cosoft.org.cn/store/bkerndev_zh_CN/Docs/gdt.htm
相關文章
- HTML5移動Web開發指南HTMLWeb
- HTML5 移動開發的叢林: 叢林指南HTML移動開發
- 譯PrestaShop開發者指南第四篇深入PrestaShop核心開發REST
- HTML5移動開發指南(筆記概要)HTML移動開發筆記
- 軟體開發的 5 條核心原則,讓工作事半功倍
- 開發指南
- PHP核心介紹及擴充套件開發指南—類和物件PHP套件物件
- GCC開發指南GC
- Java併發指南7:JUC的核心類AQS詳解JavaAQS
- 面向Java開發人員的Flex開發指南JavaFlex
- Three.js開發指南(5 6):使用Three.js的幾何體JS
- SuperTextView 最全開發指南TextView
- ADO 開發指南
- 離不開的工具之《Docker開發指南》Docker
- SAP FIORI全棧開發——從OData Service到SAPUI5一站式開發指南全棧UI
- MyBatis開發框架的四大核心MyBatis框架
- .NET架構的核心開發技術架構
- 軟體開發命名指南
- 前端離線開發指南前端
- MaxCompute Mars開發指南
- Redis開發使用指南Redis
- Web 開發進階指南Web
- Go RPC 開發指南GoRPC
- SuperWebview開發指南AndroidWebViewAndroid
- Web開發初學指南Web
- iOS開發 - 介面尺寸指南iOS
- Forte for Java開發指南 (轉)Java
- Go微服務開發指南Go微服務
- 鴻蒙開發案例:指南針鴻蒙
- C++ 核心指南 —— 效能C++
- JNI/NDK開發指南(開山篇)
- Python開發網站的完整指南Python網站
- 開發 Diffusers 庫的道德行為指南
- Word的COM載入項開發指南
- 原生開發、H5開發和混合開發的區別H5
- 【原創】eclipse + JBoss 5 + EJB3開發指南(3):使用Session Bean的本地介面EclipseSessionBean
- EBCMS核心後臺開發框架框架
- Spark開發-Spark核心細說Spark