從零開始寫一個操作系統內核 筆記(四) 番外篇 8086CPU 之保護模式

在這裏插入圖片描述

在這裏插入圖片描述

操作系統內核加載過程

在這裏插入圖片描述上圖是 linux 0.1.2 應到程序加載操作系統的步驟。

在實模式下的1M 空間下,BIOS啓動後經過一系列自檢(CPU、內存、硬盤等)後,將控制權 交給主引導記錄MBR(Main Boot Record)位於硬盤的第0盤0道1扇區(LBA=0)的512個字節,BIOS事先規定將代碼執行權移交到了0x7c00處地址,所以需要將MBR讀寫到0x7c00處,由於MBR只有 512個字節如果你想一步上天是不可能的 512 個字節能做的事情是非常有限的,所以 MBR能做的 就只是從磁盤上 讀取次 下一個代替MBR完成任務的能遞交接力棒的程序稱爲OBR(OS Boot Record)操作系統引導記錄,如果這個程序佔 7個字節 那麼MBR就要讀取 14個扇區 然後將其加載到指定內存儲並且jmp跳轉,將代碼執行權交給它,在Linux 0.12中 這個OS加載程序 被加載到了 0x9000:0000處這塊內存,從上圖可以看到0x90000沒有被其他程序佔用,引導程序主要功能是 加載kernel內核程序,檢查內存 顯卡等一系列硬件參數存放起來,以便後面操作系統內核使用。設GBTR表進入保護模式,最後跳轉到 內核起始處。

段描述符緩衝寄存器

在這裏插入圖片描述
在80286 之後 ,保護模式下 當 段寄存器 被賦予 16位的選擇子
mov ds,selector
段描述符緩衝器就會 從 IGDR中取到 對應 選擇子的 base limit 等 加載到段描述緩衝器,直到下一次段描述符緩衝器,再次被賦值。

段描述符緩衝器實際包含 96 位 但是可見能操作的只有16位,
其中32 位Lmit表示段的限長.
32位 Base表示段的起始地址
16 位 Selector表示段的選擇子 (可見部分)

可以看到 在80286 處理器下 base 和Limit 都是 0~23 可以訪問 2的23次方 也就是 16M的內存,但是 由於 尋址方式還是通過 段基址 << 4 + 偏移 能訪問到的還是20 位,所以在80286 下 一個段 你還是隻能訪問 64 KB 的空間,然後你要 換下一個段 就該改變段寄存器,不能只通過一個段寄存器 就能訪問全部16M的空間,感覺有點不爽,所以在 以後的 80386 機器上 將地址總線改成32根 在32位下, 一個寄存器 擁有了 32位 2^32次方 能表示 完整的4G空間。
在這裏插入圖片描述

保護模式後下的段選擇子

在這裏插入圖片描述
RPL :請求特權級別(00 01 10 11)四個取值 對應Ring 0 ~ Ring3
TI: 爲0 查GDT表 爲1 查LDT表
Index: 處理器會乘以8 在IDT或者GDT中查找相應的段描述符。
這裏 我們 舉個例子 :
45 轉到2進制 01000 1 01
得到:
RPL:1(權限 Ring33爲最低 Ring30 能訪問Ring3 1、2、3 Ring3只能訪問Ring33)
TI:1
Index:8

進入32位保護模式,段寄存器 CS 、DS、 ES、 FS、 GS、 SS ,它們還是16位的,但是不再存着所謂的段地址了,而是成爲段選擇器,裏面存着段選擇子,段選擇子裏面有描述符索引,根據這個索引去位於內存的GDT(Global Descriptor Table 全局描述符表)裏找真正的段基地址(線性地址);
GDT有很多描述符:代碼段描述符、數據段描述符、棧段描述符;
在GDT裏,每個描述符,佔用8個字節單元;
索引號 乘以 8 得到要找的描述符在GDT表內的偏移地址;
在這裏插入圖片描述

總結:在保護模式下,每個段寄存器 包含96位 ,16位爲選擇子,通過選擇子的索引CPU會獲取GDT表或者IDT表的段描述信息。

段描述符格式

段描述符,是由 64Bit 也就是 8個字節 構成的,分爲 前 32位 和 後 32位。
一般情況下,規定用途的屬性一般在後32位 所以我們不需要去看 前32位。
在這裏插入圖片描述

某描述符是64位的0x4F9AFFFFFFFFFF,請問,段基地址是多少?段界限是多少?G、D、L、AVL、P、DPL、S和TYPE各是多少?

首先 分成前 32 位和 後32位,0x4F9AFF 0xFFFFFFFF

  1. D/B位(如果該段是代碼段我們稱爲B 如果該段是數據段我們稱爲D):用來表示 段以 16位 或者 32位 操作,如果D/B 爲 0 則段的limit 尋址方式爲 16位,limit最大FFFFH 64KB大小,當指定爲 1是 最大FFFFFFFF = 4GB的尋址空間。
  2. 數據段 TYPE 的 E位 可以設置向上向下擴展 向上擴展 就是從內存高位到內存低位,0~0xFFFFFFFF(如果DB位設置爲 0 則只能尋址64K) 計算方式爲 :Base ~Limit (base > limit) ,而向下 擴展 是從 高地址到低地址, base < limit 此時 limit 作爲起始地址,以下的部分 爲可用部分B爲 0或1 做限長度, base > limit 用 limit + base 做起始地址,B爲 0或1 做限長度。
  3. P位表示當前段 是否在內存中。
  4. G 表示代碼段粒度,1爲4K 0爲 1字節。同樣 0xFFFFF 地址 1字節 就能表示 1M 空間,4K 就能表示4G。
  5. A 表示 當前段描述符 是不是被加載過了。

下面提供我寫了個 提取段描述符信息的腳本:


a =0x00cff70000000000 # 段描述符 64位
binstr ='{:064b}'.format(a)
pre31bit = binstr[:32]
next31bit = binstr[32:]

_G = {"0":"1Byte","1":"4KB"}
_P = {"0":"當前段不在內存中存在","1":"當前段實際存在內存中"}
_DPL = {"00":"當前段特權級爲 0(最高 可以訪問其他所有特權級)","01":"當前段特權級爲 1(可以訪問 1 2 3 特權級)"
        ,"10":"當前段特權級爲 2 (可以訪問 2 3 特權級)","11":"當前段特權級爲 3 只可以訪問 3特權級"}

_TYPED = {"E":{"0":"向上拓展","1":"向下拓展"},"W":{"0":"只讀","1":"可讀/寫"},"A":{"0":"這個段描述符沒有被訪問過(沒有被選擇子加載過)","1":"已訪問過"}}
_TYPEC = {"C":{"0":"非一致代碼段(Ring3 不能調用 Ring0)","1":"一致代碼段(Ring3 能調用Ring0)"},"R":{"0":"僅執行","1":"執行/可讀"},"A":{"0":"這個段描述符沒有被訪問過(沒有被選擇子加載過)","1":"已訪問過"}}
_S = {"0":"系統","1":"數據/代碼段"}
_DB = {"0":"16位的代碼和數據段","1":"32位的代碼和數據段"}
base3 =  pre31bit[0:8]
G = pre31bit[8]
DB = pre31bit[9]
L = pre31bit[10]
AVL = pre31bit[11]
P = pre31bit[16]
DPL = pre31bit[17:19]
S = pre31bit[19]
TYPE = pre31bit[20:24]
base2 = pre31bit[24:]
seglen2 = pre31bit[12:16]
base1 = next31bit[0:16]
segl1= next31bit[16:]


limit = hex(int(segl1 + seglen2,2))
base =hex(int(base3 +base2+base1,2))
print("段界限:",limit)
print("段基地址: ",base)
print("G 粒度:",_G[G])
print("L:",L)

print("S:",_S[S])
print("P(標誌指出該段當前是否在內存中):",_P[P])
print("DB:",_DB[DB])
print("AVL:",AVL)
print("DPL(訪問該段所需的最低權限):",_DPL[DPL])

if S == "0":
        print("TYPE:", _TYPED["E"][TYPE[0]], _TYPED["W"][TYPE[1]], _TYPED["A"][TYPE[2]])
else:
        print("TYPE:", _TYPEC["C"][TYPE[0]], _TYPEC["R"][TYPE[1]], _TYPEC["A"][TYPE[2]])
段權限檢查

CPL是當前進程的權限級別(Current Privilege Level),是當前正在執行的代碼所在的段的特權級,存在於cs寄存器的低兩位。
     RPL說明的是進程對段訪問的請求權限(Request Privilege Level),是對於段選擇子而言的,每個段選擇子有自己的RPL,它說明的是進程對段訪問的請求權限,有點像函數參數。而且RPL對每個段來說不是固定的,兩次訪問同一段時的RPL可以不同。RPL可能會削弱CPL的作用,例如當前CPL=0的進程要訪問一個數據段,它把段選擇符中的RPL設爲3,這樣雖然它對該段仍然只有特權爲3的訪問權限。
    DPL存儲在段描述符中,規定訪問該段的權限級別(Descriptor Privilege Level),每個段的DPL固定。
當進程訪問一個段時,需要進程特權級檢查,一般要求DPL <= max {CPL, RPL}
    下面打一個比方,中國官員分爲6級國家主席1、總理2、省長3、市長4、縣長5、鄉長6,假設我是當前進程,級別總理(CPL=2),我去聊城市(DPL=4)考察(呵呵),我用省長的級別(RPL=3 這樣也能嚇死他們:-))去訪問,可以吧,如果我用縣長的級別,人家就不理咱了(你看看電視上的微服私訪,呵呵),明白了吧!爲什麼採用RPL,是考慮到安全的問題,就好像你明明對一個文件用有寫權限,爲什麼用只讀打開它呢,還不是爲了安全!
舉個栗子:
mov ax,0x20 此時選擇子 是 0x20 轉成二進制 100 0 00
RPL:0 TI:0(0爲查GDT表) GDTindex = 100
mov ds,ax 如果要正確執行 那麼當前CS代碼段 的權限 要 大於等於 RPL的權限
也就是說 CPL >= RPL ,如果不滿足CPU是無法通過運行的。
如果 ds = ax,如果 ds 已經等於 ax了 不做任何操作,
上面檢測通過 CPL >= RPL 此時通過選擇子在 GDT表內 查找 並且此時 要求 GDT的DPL <= RPL 才能正確被加載,寫入緩存描述符表的96位中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章