Linux從頭學(xué)08:Linux 是如何保護(hù)內(nèi)核代碼的?【從實(shí)模式到保護(hù)模式】
作 者:道哥,10 年的嵌入式開發(fā)老兵。公眾號(hào):【IOT物聯(lián)網(wǎng)小鎮(zhèn)】,專注于:C/C 、Linux操作系統(tǒng)、應(yīng)用程序設(shè)計(jì)、物聯(lián)網(wǎng)、單片機(jī)和嵌入式開發(fā)等領(lǐng)域。 公眾號(hào)回復(fù)【書籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書籍。
轉(zhuǎn) 載:歡迎轉(zhuǎn)載文章,轉(zhuǎn)載需注明出處。
-
從 16 位進(jìn)入到 32 位
-
8086 的 16 位模式
-
80386 的 32 位模式
-
從實(shí)模式進(jìn)入到保護(hù)模式
-
如何進(jìn)入保護(hù)模式
-
GDT 全局描述符表
-
GDTR 全局描述符表寄存器
-
段描述符的查找原理
PS: 相應(yīng)的,之前 8086 中的處理器執(zhí)行模式就叫做“實(shí)模式”。雖然80286沒有形成一定的氣候,但是它對(duì)后來的80386處理器提供了基礎(chǔ),讓386獲得了極大的成功。
從 16 位進(jìn)入到 32 位
8086 的 16 位模式
在8086處理器中,所有的寄存器都是16位的。
例如:在訪問代碼段的時(shí)候,把 cs 寄存器左移 4 位,再加上 ip 寄存器,就得到 20 位的物理地址了;還記得我們第1篇文章Linux 從頭學(xué) 01:CPU 是如何執(zhí)行一條指令的?中的寄存器示意圖嗎?20 位的地址,最大尋址范圍就是 2 的 20 次方 = 1 MB 的空間;
在訪問代碼段的時(shí)候,使用 cs:ip 寄存器;在訪問數(shù)據(jù)段的時(shí)候,使用 ds 寄存器;
在訪問棧的時(shí)候,使用 ss:sp 寄存器;
80386 的 32 位模式
進(jìn)入到32位的處理器之后,這些寄存器就擴(kuò)展到32位了:
注意:高 16 位不可以獨(dú)立使用。下面這張圖是32位處理器的另外4個(gè)通用寄存器(注意它們是不能按照8位寄存器來使用的):
在 32 位處理器中,依然可以兼容 16 位的處理模式,此時(shí)依然使用 16 位的寄存器;是不是感覺到上面的寄存器示意圖中漏掉了什么東西?如果不兼容的話,就會(huì)失去很大的市場(chǎng)占有率;
有些書上把段寄存器稱之為:段選擇子;正是因?yàn)樘幚砥饔?2根地址線,可尋址的范圍已經(jīng)非常大了(4 GB),因此理論上它是不需要像8086中那樣的尋址方式(段地址左移4位 偏移地址)。也有一些書上把段寄存器中的值稱之索引值,稱之為選擇子;
不必糾結(jié)于稱呼,明白其中的道理就可以了;
找到了這個(gè)段的基地址之后,在訪問內(nèi)存的時(shí)候,仍然是按照段機(jī)制 偏移量的方式。
- 對(duì)于 8086 來說,段寄存器中的內(nèi)容左移 4 位之后,就是段的基地址;
- 對(duì)于 80386 來說,段寄存器中的內(nèi)容是一個(gè)表的索引號(hào),通過這個(gè)索引號(hào),去查找表中相應(yīng)位置中的內(nèi)容,這個(gè)內(nèi)容中就有段的基地址(如何查找,下文有描述);
從實(shí)模式進(jìn)入到保護(hù)模式
如何進(jìn)入保護(hù)模式
CPU是如何判斷:當(dāng)前是執(zhí)行的是實(shí)模式?還是保護(hù)模式?
bit0 = 0: 實(shí)模式;在處理器上電之后,默認(rèn)狀態(tài)下是工作在實(shí)模式。
bit1 = 1: 保護(hù)模式;
GDT 全局描述符表
由于這張表中的每一個(gè)條目(Entry),描述的是一個(gè)段的基本信息,包括:基地址、段的長(zhǎng)度界限、安全級(jí)別等等,因此我們稱之為全局描述符表(Global Descriper Table, GDT)。
之所以稱之為全局的,是因?yàn)槊恳粋€(gè)應(yīng)用程序還可以把段描述符信息,放在自己的一個(gè)私有的局部描述符表中(Local Descriper Table,LDT),在以后的文章中一定會(huì)介紹到。
處理器規(guī)定:第一個(gè)描述符必須為空,主要是為了規(guī)避一些程序錯(cuò)誤。從上圖中可以看出:GDT中每一個(gè)條目的長(zhǎng)度是8個(gè)字節(jié),其中描述了一個(gè)段的具體信息,如下所示:
第一個(gè)問題的答案是:歷史原因(兼容性)。
- 為什么段的基地址不是用連續(xù)的 32 bit 位來表示?
- 段的界限怎么是 20 位的?20 位只能表示 1 MB 的范圍啊?
為了完整性,我把所有標(biāo)志位的含義都匯總?cè)缦?,方便參考?
- 如果 G = 0: 表示段界限是以字節(jié)為單位,此時(shí),段界限的最大表示范圍就是 1 MB;
- 如果 G = 1:表示段界限是以 4 KB 為單位,此時(shí),段界限的最大表示范圍就是 4 GB( 1 MB 乘以 4KB);
在 Linux 操作系統(tǒng)中,只利用了 0 和 3 這兩個(gè)特權(quán)級(jí)別。S (bit12):決定這個(gè)段的類型。
GDTR 全局描述符表寄存器
還有一個(gè)問題需要處理:GDT表本身也是數(shù)據(jù),也是需要存放在內(nèi)存中的。
也就是說:處理器是到固定的地址0處,查找中斷向量表的,這是一個(gè)固定的地址。
- 程序代碼把每一個(gè)中斷的處理程序地址,放在中斷向量表中的對(duì)應(yīng)位置;
- 中斷向量表的起始地址放在內(nèi)存的 0 地址處;
其實(shí),GDT 在上電剛開始的時(shí)候,也不能放在內(nèi)存中的任意位置。從GDTR寄存器中的內(nèi)容可以看出,它不僅存儲(chǔ)了GDT的起始地址,而且還限制了GDT的長(zhǎng)度。因?yàn)樵谶M(jìn)入保護(hù)模式之前,處理器還是工作在實(shí)模式,只能尋址 1 MB 的內(nèi)存空間,因此,GDT 只能放在 1 MB 內(nèi)的地址空間中。
在進(jìn)入保護(hù)模式之后,能尋址更大的地址空間了,此時(shí)就可以重新把 GDT 放在更大的地址空間中了,然后把這個(gè)新的起始地址,存儲(chǔ)到 GDTR 寄存器中。
段描述符的查找原理
在上面的段寄存器示意圖中,我們只說明了段寄存器依然是16位的。
RPL: 表示當(dāng)前正在執(zhí)行的這個(gè)代碼段的請(qǐng)求特權(quán)級(jí);假設(shè)當(dāng)前代碼段寄存器cs的值為0x0008,處理器按照保護(hù)模式的機(jī)制來解釋其中的內(nèi)容:TI: 表示到哪一個(gè)表中去找這個(gè)段的描述信息:全局描述符表(GDT) or 局部描述符表(LDT)?
TI = 0 時(shí),到 GDT 中找段描述符;
TI = 1 時(shí),到 LDT 中找段描述符;
找到了這個(gè)段描述符條目之后,就可以從中獲取到這個(gè)代碼段的具體信息了:
- TI = 0,表示到 GDT 中查找段描述符;
- RPL = 0,表示請(qǐng)求特權(quán)級(jí)別是 0;
- 描述符索引是 1,表示這個(gè)段描述符在 GDT 中的第 1 個(gè)條目中。由于每一個(gè)描述符占用 8 個(gè)字節(jié),因此這個(gè)描述符的開始地址位于 GDT 中的偏移地址為 8 的位置(1 * 8 = 8);
另外,從上文描述的GDTR寄存內(nèi)容知道,它限制了GDT中最多一共可以存放8192個(gè)描述符。
- 代碼段的基地址在內(nèi)存中什么位置;
- 代碼段的最大長(zhǎng)度是多少(在獲取指令時(shí),如果偏移地址超過這個(gè)長(zhǎng)度,就引發(fā)異常);
- 代碼段的特權(quán)級(jí)別是多少,當(dāng)前是否駐留在物理內(nèi)存中等等;
2 的 13 次方 = 8192。至此,處理器就在保護(hù)模式下,查找到了一個(gè)段的所有信息。
------ End ------
這篇文章主要描述了80386處理器中的保護(hù)模式下,段寄存器的使用,以及通過段描述符來查找段的具體信息。