从零开始操作系统------探析保护模式
本文基于鄭綱的《操作系統還原》,僅為個人學習筆記,前期的虛擬機配置等不再詳細記錄,其中不理解或者出錯的地方還望提出意見!
從零開始操作系統------MBR直操硬盤、內核加載器
為什么有保護模式
(1)實模式下操作系統和用戶程序屬于同一特權級,沒有區別對待;
(2)實模式下用戶所引用的地址都是真實的物理地址;
(3)實模式下用戶程序可以自由修改段基址,可以訪問所有內存;
(4)實模式下共 20 條地址線,最大可用內存為 1MB;
寄存器擴展
CPU 發展到 32 位后,地址總線和數據總線也發展到 32 位,其尋址空間更達到了 2 的 32 次方, 4GB。除段寄存器外,通用寄存器、指令指針寄存器、標志寄存器都由原來的 16 位擴展到了 32 位。
寄存器中低 16 位的部分是為了兼容實模式,可以單獨使用。高 16 位沒辦法單獨使用,只能在用 32位寄存器時才有機會用到它們。
尋址方式與實模式下一樣:“段基址:段內偏移地址”。其中段基址不再只是一個地址,還增加了許多對內存段的描述信息(安全性的體現),為此專門找了個數據結構—全局描述符表。既然叫表,就說明里面有表項,表中至少有一個表項,其中每一個表項稱為段描述符,其大小為 64 字節,用來描述各個內存段的起始地址、大小、權限等信息。
這樣,段寄存器中保存的再也不是段基址了,里面保存的內容叫“選擇子”,selector,該選擇子其實就是個數,用這個數來索引全局描述符表中的段描述符,把全局描述符表當成數組,選擇子就像數組下標一樣。
另外由于段描述符是保存在內存中,讀取效率是十分低的,因此對段寄存器率先應用了緩存技術,將段信息用一個寄存器來緩存,這就是段描述符緩沖寄存器(Descriptor Cache Registers)。
尋址擴展
保護模式下,同樣是內存尋址中,基址寄存器不再只是 bx、bp,而是所有 32 位的通用寄存器,變址寄存器也是一樣,不再只是 si、 di,而是除 esp 之外的所有 32 位通用寄存器,偏移量由實模式的 16 位變成了 32 位。并且,還可以對變址寄存器乘以一個比例因子。比例因子只能是 1、 2、 4、 8。
運行模式反轉
在實模式下,指令和操作數都是 16 位的,但我們也說過啦,它可以使用 32 位的資源。同樣在保護模式下,指令和操作數都是 32 位的,它也可以使用 16 位的資源。也就是說,在某個模式下,可以使用另一模式下的資源。
CPU無法知道需要生成16位還是32位機器碼,需要人為告知。編譯器提供了偽指令 bits,用它來向編譯器傳達:我下面的指令都要編譯成 xx位的,因為我知道下面的代碼的運行環境是 xx 模式。
bits 的指令格式是[bits 16]或[bits 32]
操作數反轉前綴0x66
模式之間可以互相使用對方環境下的資源。比如, 16 位實模式下可以用 32 位保護模式下的寄存器。但這種福利的得來卻是稍費功夫的,如果要用另一模式下的操作數大小,需要在指令前添加指令前綴 0x66,將當前模式臨時改變成另一模式。
尋址方式反轉前綴 0x67
不同模式之間不僅可以使用對方模式下的操作數,還可以使用對方模式下的尋址方式。
bits 偽指令用于指定運行模式,操作數大小反轉前綴 0x66 和尋址方式反轉前綴 0x67,用于臨時將當前運行模式下的操作數大小和尋址方式轉變成另外一種模式下的操作數大小及尋址方式。
全局描述符表
保護模式下,內存段(如數據段、代碼段等)不再是簡單地用段寄存器加載一下段基址就能用啦,段的信息增加了很多,需要提前把段定義好才能使用。
全局描述符表(Global Descriptor Table, GDT)是保護模式下內存段的登記表,這是不同于實模式的顯著特征之一。
段描述符
將描述內存段的屬性放到了一個稱為段描述符的結構中,該結構專門用來描述一個內段,存該結構是 8 字節大小(順便說一句,在本書中提到的各種描述符大小都是 8 字節)。
在段描述符的高 32 位中,
8~11 位是 type 字段,共 4 位,用來指定本描述符的類型,用于表示內存段或門的子類型。
一個段描述符,在 CPU 眼里分為兩大類,要么描述的是系統段,要么描述的是數據段,這是由段描述符中的 S 位決定的,用它指示是否是系統段。在 CPU 眼里,凡是硬件運行需要用到的東西都可稱之為系統,凡是軟件(操作系統也屬于軟件, CPU 眼中,它與用戶程序無區別)需要的東西都稱為數據,無論是代碼,還是數據,甚至包括棧,它們都作為硬件的輸入,都是給硬件的數據而已,所以代碼段在段描述符中也屬于數據段(非系統段)。 S 為 0 時表示系統段, S 為 1 時表示數據段。 type 字段是要和 S 字段配合在一起才能確定段描述符的確切類型。
表中的 A 位表示 Accessed 位,這是由 CPU 來設置的,每當該段被 CPU 訪問過后, CPU 就將此位置 1。所以,創建一個新段描述符時,應該將此位置 0。
段描述符的第 13~14 位是 DPL 字段, Descriptor Privilege Level,即描述符特權級。
這兩位能表示 4 種特權級,分別是 0、 1、 2、 3 級特權,數字越小,特權級越大。特權級是保護模式下才有的東西, CPU 由實模式進入保護模式后,特權級自動為 0。因為保護模式下的代碼已經是操作系統的一部分啦,所以操作系統應該處于最高的 0 特權級。用戶程序通常處于 3 特權級,權限最小。某些指令只能在 0 特權級下執行,從而保證了安全。
段描述符的第 15 位是 P 字段, Present,即段是否存在。如果段存在于內存中, P 為 1,否則 P 為 0。P 字段是由 CPU 來檢查的,如果為 0, CPU 將拋出異常,轉到相應的異常處理程序,此異常處理程序是咱們來寫的,在異常處理程序處理完成后要將 P 置 1。也就是說,對于 P 字段, CPU 只負責檢查,咱們負責賦值。
段描述符的第 20 位為 AVL 字段,從名字上看它是 AVaiLable,可用的。
段描述符的第 21 位為 L 字段,用來設置是否是 64 位代碼段。 L 為 1 表示 64 位代碼段,否則表示 32位代碼段。這目前屬于保留位,在我們 32 位 CPU 下編程,將其置為 0 便可。
段描述符的第 22 位是 D/B 字段,用來指示有效地址(段內偏移地址)及操作數的大小。
對于代碼段來說,此位是 D 位,若 D 為 0,表示指令中的有效地址和操作數是 16 位,指令有效地址用 IP 寄存器。若 D 為 1,表示指令中的有效地址及操作數是 32 位,指令有效地址用 EIP 寄存器。
對于棧段來說,此位是 B 位,用來指定操作數大小,此操作數涉及到棧指針寄存器的選擇及棧的地址上限。若 B 為 0,使用的是 sp 寄存器,也就是棧的起始地址是 16 位寄存器的最大尋址范圍, 0xFFFF。若 B 為 1,使用的是 esp 寄存器,也就是棧的起始地址是 32 位寄存器的最大尋址范圍, 0xFFFFFFFF。
段描述符的第 23 位是 G 字段, Granularity,粒度,用來指定段界限的單位大小。所以此位是用來配合段界限的,它與段界限一起來決定段的大小。若 G 為 0,表示段界限的單位是 1 字節,這樣段最大是 2的 20 次方1 字節,即 1MB。若 G 為 1,表示段界限的單位是 4KB,這樣段最大是 2 的 20 次方4KB 字節,即 4GB。
GDT
全局描述符表 GDT 相當于是描述符的數組,數組中的每個元素都是8 字節的描述符。可以用選擇子中提供的下標在 GDT 中索引描述符。
全局描述符表位于內存中, 需要用專門的寄存器指向它后, CPU 才知道它在哪里。 這個專門的寄存器便是 GDTR,即 GDT Register,專門用來存儲 GDT 的內存地址及大小。 GDTR 是個 48 位的寄存器。
這 48 位內存數據劃分為兩部分,其中前 16 位是 GDT 以字節為單位的界限值,所以這 16 位相當于GDT 的字節大小減 1。后 32 位是 GDT 的起始地址。由于 GDT 的大小是 16 位二進制,其表示的范圍是 2的16次方等于65536字節。每個描述符大小是8字節, 故, GDT中最多可容納的描述符數量是65536/8=8192個,即 GDT 中可容納 8192 個段或門。
GDTR有專門的指令lgdt進行初始化:
lgdt 的指令格式是: lgdt48 位內存數據
進入保護模式需要有 GDT,但進入保護模式后,還可以再重新換個 GDT 加載。在保護模式下重新換個 GDT 的原因是實模式下只能訪問低端 1MB 空間,在這之前 GDT 只能位于 1MB 之內。
選擇子
段寄存器 CS、 DS、 ES、 FS、 GS、 SS,在實模式下時,段中存儲的是段基地址,即內存段的起始地址。而在保護模式下時,由于段基址已經存入了段描述符中,所以段寄存器中再存放段基址是沒有意義的,在段寄存器中存入的是一個叫作選擇子的東西—selector。選擇子“基本上”是個索引值。
由于段寄存器是 16 位,所以選擇子也是 16 位,在其低 2 位即第 0~1 位,用來存儲 RPL,即請求特權級,可以表示 0、 1、 2、 3 四種特權級。
選擇子的第 2 位是 TI 位,即 Table Indicator,用來指示選擇子是在 GDT 中,還是 LDT 中索引描述符。 TI為 0 表示在 GDT 中索引描述符, TI 為 1 表示在 LDT 中索引描述符。
選擇子的高 13 位,即第 3~15 位是描述符的索引值,用此值在 GDT 中索引描述符。選擇子的索引值部分是 13 位,即 2 的 13 次方是 8192,故最多可以索引 8192 個段,這和 GDT中最多定義 8192 個描述符是吻合的。
保護模式下的段寄存器中已經是選擇子,不再是直接的段基址。段基址在段描述符中,用給出的選擇子索引到描述符后, CPU 自動從段描述符中取出段基址,這樣再加上段內偏移地址,便湊成了“段基址:段內偏移地址”的形式。
1、保護模式下后,由于已經是 32 位地址線和 32 位寄存器啦,任意一寄存器都能夠提供 32 位地址,故不需要再將段基址乘以 16 后再與段內偏移地址相加,直接用選擇子對應的“段描述符中的段基址”加上“段內偏移地址”就是要訪問的內存地址;
2、 GDT 中的第 0 個段描述符是不可用的, 原因是定義在 GDT 中的段描述符是要用選擇子來訪問的,如果使用的選擇子忘記初始化,選擇子的值便會是 0,這便會訪問到第 0 個段描述符。為了避免出現這種因忘記初始化選擇子而選擇到第 0 個段描述符的情況, GDT 中的第 0 個段描述符不可用。也就是說,若選擇到了 GDT 中的第 0 個描述符,處理器將發出異常;
進入保護模式
進入保護模式需要三個步驟。
(1)打開 A20。
(2)加載 gdt。
(3)將 cr0 的 pe 位置 1。
打開A20
打開 A20Gate 的方式是極其簡單的,將端口 0x92 的第 1 位置 1 就可以了,以下三個步驟就可以實現啦。
將 cr0 的 pe 位置 1
控制寄存器系列 CRx。控制寄存器是 CPU 的窗口,既可以用來展示 CPU的內部狀態,也可用于控制 CPU 的運行機制。這次我們要用到的是 CR0 寄存器。更準確地說,我們要用到 CR0寄存器的第 0 位,即 PE 位, Protection Enable,此位用于啟用保護模式,是打開此位后, CPU 才真正進入保護模式。
PE 為 0 表示在實模式下運行, PE 為 1 表示在保護模式下運行。所以,我們的任務是將此位置 1。示例代碼如下。
代碼
MBR更新
由于 loader.bin 超過了 512 字節,所以我們要把 mbr.S 中加載 loader.bin 的讀入扇區數增大,目前它是 1 扇區,為了避免將來再次修改,直接改成讀入 4 扇區。
boot.inc更新
;----------------------- loader and kernel -----------------------LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2 ;--------------------------- gdt描述符屬性 ------------------------ DESC_G_4K equ 1_00000000000000000000000b ; 23 DESC_D_32 equ 1_0000000000000000000000b ; 22 DESC_L equ 0_000000000000000000000b ; 21 ; 64位代碼標記,此處標記為0便可 DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暫置為0 ; 20 DESC_LIMIT_CODE2 equ 1111_0000000000000000b ; 16-19 DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 DESC_LIMIT_VIDEO2 equ 0000_0000000000000000b ; 16-19 DESC_P equ 1_000000000000000b ; 15 DESC_DPL_0 equ 00_0000000000000b ; 13-14 DESC_DPL_1 equ 01_0000000000000b DESC_DPL_2 equ 10_0000000000000b DESC_DPL_3 equ 11_0000000000000b DESC_S_CODE equ 1_000000000000b ; 12 DESC_S_DATA equ DESC_S_CODE DESC_S_sys equ 0_000000000000b ; 12 DESC_TYPE_CODE equ 1000_00000000b ; 8-11 ; x=1,r=0,c=0,a=0 代碼段是可執行的,非一致性,不可讀,已訪問位a清0DESC_TYPE_DATA equ 0010_00000000b ; 8-11 ; x=0,w=1,e=0,a=0 代碼斷不可執行,向上擴展的,可寫,已訪問為a清0DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0Bloader.s
%include "boot.inc" section loader vstart=LOADER_BASE_ADDR LOADER_STACK_TOP equ LOADER_BASE_ADDR jmp loader_start ; 構建gdt及其內部描述符 GDT_BASE: dd 0x00000000dd 0x00000000CODE_DESC: dd 0x0000FFFFdd DESC_CODE_HIGH4DATA_STACK_DESC: dd 0x0000FFFFdd DESC_DATA_HIGH4VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k =0x7dd DESC_VIDEO_HIGH4 ; 此時dpl為0GDT_SIZE equ $ - GDT_BASE GDT_LIMIT equ GDT_SIZE - 1 times 60 dq 0 ; 此處預留60個描述符的空位 SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0 ; 相當于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0 SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0; 以下所gdt 的指針,前2個字節所gdt界限,后4字節的gdt起始地址gdt_ptr dw GDT_LIMITdd GDT_BASE loadermsg db '2 loader in real.'loader_start:;-------------------------------------------------------------------------------------------------- ; INT 0x10 功能號:0x13 功能描述符:打印字母 ;-------------------------------------------------------------------------------------------------- ; 輸入: ; AH 子功能號=13H ; BH = 頁碼 ; BL = 屬性(若 AL = 00H 或 01H) ; CX = 字符串長度 ; (DH、DL) = 坐標(行、列) ; ES:BP = 字符串地址 ; AL = 顯示輸出方式 ; 0————字符串中只含顯示字符,其顯示屬性在BL中 ; 顯示后,光標位置不變 ; 1————字符串中 ; ; 2———— ; 3———— ; 無返回值 mov sp, LOADER_BASE_ADDR mov bp, loadermsg ; ES:BP = 字符串地址 mov cx, 17 ; CX = 字符串長度 mov ax, 0x1301 ; AH=13, AL=01 mov bx, 0x001f ; 頁號為0 藍底粉紅字 mov dx, 0x1800 int 0x10 ; 10h 號中斷;----------------------------------------準備進入保護模式----------------------------------------------- ; 1 打開A20 ; 2 加載gdt ; 3 將cr0的pe位置1;-----------------------打開A20--------------------------- in al,0x92 or al,0000_0010b out 0x92,al;------------------------加載GDT-------------------------- lgdt [gdt_ptr];----------------------將cr0第0位置1----------------------- mov eax, cr0 or eax, 0x00000001 mov cr0, eax;mov byte [gs:160], 'P'jmp dword SELECTOR_CODE:p_mode_start ; 刷新流水線[bits 32] p_mode_start:mov ax, SELECTOR_DATAmov ds, axmov es, axmov ss, axmov esp, LOADER_STACK_TOPmov ax, SELECTOR_VIDEOmov gs, axmov byte [gs:160], 'P'jmp $結果
總結
以上是生活随笔為你收集整理的从零开始操作系统------探析保护模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【移动端网页布局】移动端网页布局基础概念
- 下一篇: 逃离华强北后 他们去哪儿?采访身边真实