第四章 保護模式入門
第四章 保護模式入門
真實模式
什麼是真實模式
真實模式,又叫實地址模式.從80386開始,CPU有三種工作模式:真實模式、保護模式和虛擬8086模式。80286開始的CPU引入保護模式,實際上,真實模式概念是在保護模式推出之後為了區別保護模式之前的8086CPU工作模式才有的,在8086時代CPU工作模式只有一種,自然沒有真實模式之說。
真實模式的“實”體現在程式中用到的地址都是真實的實體地址,“段基址:段內偏移地址”產生的邏輯地址就是實體地址,即程式設計師可見的地址完全是真實的記憶體地址。
真實模式的缺點
不安全
- 沒有許可權劃分,作業系統程式和使用者程式屬於同一特權級.
- 程式中用到的地址都是真實的實體地址,邏輯地址等於實體地址.
- 程式可以自由修改段地址,也就是可以訪問所有記憶體地址.
- 段大小受限,段內偏移最大為64K,如果程式超過64k,需要佔用多個段,段暫存器需要切換段基址.
保護模式之暫存器擴充套件
原有暫存器寬度增加
除段暫存器外,通用暫存器、指令指標暫存器、標誌暫存器都由原來的 16 位擴充套件到了 32 位。
經過 extend 後的暫存器,統一在名字前加了 E表示擴充套件.
暫存器中低 16 位的部分是為了相容真實模式,可以單獨使用。高 16 位沒辦法單獨使用,只能在用 32位暫存器時才有機會用到它們。
新增新的暫存器
除了容量的擴充套件,還有新的暫存器新增;
新增新暫存器的目的:解決真實模式的安全問題
安全問題之一:段沒有級別劃分,程式可以隨意訪問任意段,任意記憶體地址;
解決段隨意訪問問題->段新增描述資訊->新增描述資訊,即段描述符->段描述符有多個,要描述多個段,全域性描述符表包括多個段描述符->全域性描述符表很大,不能直接放在暫存器,只能放在記憶體中->為了能找到全域性描述符表,把全域性描述符表的地址放在GDTR暫存器->段暫存器中不再儲存段基址,儲存"選擇子"(selector),是段描述符在全域性描述符表中的索引->獲取段描述符的步驟:1.讀取GDTR暫存器獲取全域性描述符表的地址;2.讀取段暫存器獲取選擇子;3.全域性描述符表地址+選擇子,從記憶體中讀取段描述符;->獲取段描述符耗時長,而且段資訊在執行同一個程式時,一般不會變動,所以可以在獲取段描述符後,把段描述符放到暫存器中做快取,此暫存器式段描述符快取暫存器->在段暫存器的選擇子更新時,就會重新獲取段描述符,更新段描述符快取暫存器;
保護模式之定址擴充套件
真實模式下暫存器的功能比較固定.真實模式下對於記憶體定址來說,其中的基址定址、變址定址、基址變址定址,這三種形式中的基址暫存器只能是bx,bp,變址暫存器只能si、di,也就是說,只能用這個暫存器。其中bx預設的段暫存器是由,它經常用於訪問資料段,bp預設的段暫存器是SS,它經常用於訪問棧。
mov ax, [si]
mov ax, [di]
mov ax, [bx]
mov ax, [bx+si]
mov ax, [bx+si+Oxl234]
mov ax, [bx+di]
mov ax, [bx+di+Oxl234]
在保護模式下,這一切都不同了,同樣是記憶體定址中,基址暫存器不再只是bx,bp,而是所有32位的通用暫存器,變址暫存器也是一樣,不再只是si,di,而是除esp之外的所有32通用暫存器,偏移量由真實模式的16位變成了32位。並且,還可以對變址暫存器乘以一個比例因子
mov eax, [eax+edx*8+0x12345678]
mov eax, [eax+edx*2+0x8]
mov eax, [ecx*4+0x1234]
保護模式之執行反轉
相容真實模式和保護模式,cpu很難區分,需要生成不同的機器碼來做處理;編譯器提供偽指令bits,來區分16位和32位.
[bits 16]
mov ax, Oxl234
mov dx, Oxl234
[bits 32]
mov eax , Oxl234
mov edx, Oxl234
全域性描述符表
全域性描述符表( Global Descriptor Table, GDT )是保護模式下記憶體段的登記表,這是不同於真實模式的顯著特徵之一。
一個段描述符描述一個段的資訊,一個專門的資料結構儲存著多個段描述符,稱為“全域性描述符表”,其實就是一個儲存著段描述符的陣列。
段描述符
-
段描述符大小為8個位元組
-
段界限被分為兩部分,段基址被分為三部分,原因是為了相容80286,80286是第一款有保護模式的cpu,但是隻有16位。
-
段界限:表示段的大小,共20位。具體邊界值要結合23位的G來看,G=1時,表示段界限的粒度為4KB,最大值為2的(20+12)次方,即4GB,G=0時,表示段界限的粒度為1位元組,最大值為2的20次方,即1MB實際的段界限=(描述符裡的段界限+1)*段界限粒度大小-1。
-
段基址:共32位。
-
S:代表一個段是系統段還是資料段,在CPU眼裡,凡是硬體使用到的東西稱為系統,凡是軟體使用到的東西稱為資料。所以程式碼段、資料段、棧段等也屬於S中所代表的的資料段。S為0時表示系統段,S為1時表示資料段。
-
Type:共四位,指定段的型別。只有S決定了,Type才有它的意義。下圖是Type在系統段和資料段裡不同的意義。
- 非系統段:當段為程式碼段時,Type由X、R、C、A組成,分別代表是否可執行、是否可讀、是否一致、是否被訪問過。當段位資料段時,Type由X、W、E、A組成,分別代表是否可執行、是否可寫、擴充套件方向、是否被訪問過。
- DPL代表段的特權級。
- P代表記憶體段是否存在,0代表段不存在,1代表段存在。
- AVL代表可用的位,是對使用者而言,但作業系統可以隨意使用。
- L代表程式碼段是64位還是32位。
- D/B:有效地址和運算元的大小。對於程式碼段來說此位是D,為0時,有效地址和運算元的大小為16位,使用ip暫存器;為1時,有效地址和運算元的大小為32位,使用eip暫存器。對於棧段來說此位是B,為0時,運算元為16位,使用sp暫存器;為1時,運算元為32位,使用esp暫存器。
全域性描述符表
全域性描述符表示全域性的,共用的,多個程式都可以在這個表定義自己的段描述符。我們進入保護模式的其中一個步驟之一就是載入全域性描述符表,讓CPU知道全域性描述符表的位置,在操作記憶體的時候,CPU就會根據描述符的資訊檢查這操作是否有效。
全域性描述符表位於記憶體中,由GDTR暫存器指向全域性描述符表的記憶體地址。GDTR暫存器大小為48位,初始化GDTR的指令為lgdt 48位數值
。
這48位數值的後32位表示GDT的起始地址,前16位表示GDT的大小,單位為位元組,即2的16次方,大小為65536位元組;每個描述符大小為8位元組,所以GDT最多容納65536/8=8192個段。
選擇子
段暫存器中存入的是選擇子(selector),段暫存器是16位,所以選擇子也是16位。
![image-20210221212035412](/Users/liufq/Documents/文件/作業系統真相還原/第四章 保護模式入門/第四章 保護模式入門.assets/image-20210221212035412.png)
- RPL:特權級,有0、1、2、3,四種。
- TI:為0,描述符索引值為GDT中索引值;為1,描述符索引值為LDT中索引值。
保護模式下,使用選擇子獲取端描述符,從段描述符中取出段基址。
LDT:區域性描述符表,很少使用。
進入保護模式
A20地址線
在真實模式下,A20地址線是預設禁用的,原因是還未進入保護模式之前,地址匯流排還是要模擬20位的效果,即只保留20位以內的地址,如果地址超過20位,地址就會迴繞到0,將地址20位(從0開始算)捨棄,所以要將A20地址線給禁用掉。但進入保護模式後,我們需要恢復地址匯流排的原貌,即使地址超過20位,地址也不應該回繞到0,所以此時將A20地址線開啟,我們就能訪問超過20位的地址了。因此,開啟A20地址線,是進入保護模式的步驟之一。
CR0的PE位
進入保護模式的最後一個步驟是,開啟CR0的PE位,CR0是控制暫存器。控制暫存器是CPU的視窗,它既可以展示CPU的內部狀態,也可以控制CPU的執行機制。CR0的第0位,PE位,就是保護模式的開關,我們開啟PE位,就是告訴CPU接下來我們要進入保護模式。
進入保護模式
由上面可以知道,進入保護模式的步驟如下:
① 開啟A20地址線
② 載入GDT
③ 將CR0的PE位置為1
總共三個檔案:include/boot.inc,mbr.S,loader.S
boot.inc
增加段描述符的常量
;------------- loader和kernel ----------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;-------------- gdt描述符屬性 -------------
DESC_G_4K equ 1_00000000000000000000000b
DESC_D_32 equ 1_0000000000000000000000b
DESC_L equ 0_000000000000000000000b ; 64位程式碼標記,此處標記為0便可。
DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暫置為0
DESC_LIMIT_CODE2 equ 1111_0000000000000000b
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 0000_000000000000000b
DESC_P equ 1_000000000000000b
DESC_DPL_0 equ 00_0000000000000b
DESC_DPL_1 equ 01_0000000000000b
DESC_DPL_2 equ 10_0000000000000b
DESC_DPL_3 equ 11_0000000000000b
DESC_S_CODE equ 1_000000000000b
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0_000000000000b
DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 程式碼段是可執行的,非依從的,不可讀的,已訪問位a清0.
DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0 資料段是不可執行的,向上擴充套件的,可寫的,已訪問位a清0.
DESC_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 + 0x00
DESC_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 + 0x00
DESC_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 + 0x0b
;-------------- 選擇子屬性 ---------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
mbr.S
此前只讀入了一個扇區,由於 loader.bin 超過了 512 位元組,52行增加讀入的扇區為4
;主載入程式
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
; 清屏
;利用0x06號功能,上卷全部行,則可清屏。
; -----------------------------------------------------------
;INT 0x10 功能號:0x06 功能描述:上卷視窗
;------------------------------------------------------
;輸入:
;AH 功能號= 0x06
;AL = 上卷的行數(如果為0,表示全部)
;BH = 上卷行屬性
;(CL,CH) = 視窗左上角的(X,Y)位置
;(DL,DH) = 視窗右下角的(X,Y)位置
;無返回值:
mov ax, 0600h
mov bx, 0700h
mov cx, 0 ; 左上角: (0, 0)
mov dx, 184fh ; 右下角: (80,25),
; 因為VGA文字模式中,一行只能容納80個字元,共25行。
; 下標從0開始,所以0x18=24,0x4f=79
int 10h ; int 10h
; 輸出字串:MBR
mov byte [gs:0x00],'1'
mov byte [gs:0x01],0xA4
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4 ;A表示綠色背景閃爍,4表示前景色為紅色
mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4
mov eax,LOADER_START_SECTOR ; 起始扇區lba地址
mov bx,LOADER_BASE_ADDR ; 寫入的地址
mov cx,4 ; 待讀入的扇區數
call rd_disk_m_16 ; 以下讀取程式的起始部分(一個扇區)
jmp LOADER_BASE_ADDR
;-------------------------------------------------------------------------------
;功能:讀取硬碟n個扇區
rd_disk_m_16:
;-------------------------------------------------------------------------------
; eax=LBA扇區號
; ebx=將資料寫入的記憶體地址
; ecx=讀入的扇區數
mov esi,eax ;備份eax
mov di,cx ;備份cx
;讀寫硬碟:
;第1步:設定要讀取的扇區數
mov dx,0x1f2
mov al,cl
out dx,al ;讀取的扇區數
mov eax,esi ;恢復ax
;第2步:將LBA地址存入0x1f3 ~ 0x1f6
;LBA地址7~0位寫入埠0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位寫入埠0x1f4
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;LBA地址23~16位寫入埠0x1f5
shr eax,cl
mov dx,0x1f5
out dx,al
shr eax,cl
and al,0x0f ;lba第24~27位
or al,0xe0 ; 設定7~4位為1110,表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7埠寫入讀命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:檢測硬碟狀態
.not_ready:
;同一埠,寫時表示寫入命令字,讀時表示讀入硬碟狀態
nop
in al,dx
and al,0x88 ;第4位為1表示硬碟控制器已準備好資料傳輸,第7位為1表示硬碟忙
cmp al,0x08
jnz .not_ready ;若未準備好,繼續等。
;第5步:從0x1f0埠讀資料
mov ax, di
mov dx, 256
mul dx
mov cx, ax ; di為要讀取的扇區數,一個扇區有512位元組,每次讀入一個字,
; 共需di*512/2次,所以di*256
mov dx, 0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
times 510-($-$$) db 0
db 0x55,0xaa
loader.S
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start ; 此處的實體地址是:
;構建gdt及其內部的描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k=0x7
dd DESC_VIDEO_HIGH4 ; 此時dpl已改為0
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 60 dq 0 ; 此處預留60個描述符的slot
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_LIMIT
dd 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——字串中只含顯示字元,其顯示屬性在BL中。顯示後,游標位置改變
; 2——字串中含顯示字元和顯示屬性。顯示後,游標位置不變
; 3——字串中含顯示字元和顯示屬性。顯示後,游標位置改變
;無返回值
mov sp, LOADER_BASE_ADDR
mov bp, loadermsg ; ES:BP = 字串地址
mov cx, 17 ; CX = 字串長度
mov ax, 0x1301 ; AH = 13, AL = 01h
mov bx, 0x001f ; 頁號為0(BH = 0) 藍底粉紅字(BL = 1fh)
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
;jmp dword SELECTOR_CODE:p_mode_start ; 重新整理流水線,避免分支預測的影響,這種cpu優化策略,最怕jmp跳轉,
jmp SELECTOR_CODE:p_mode_start ; 重新整理流水線,避免分支預測的影響,這種cpu優化策略,最怕jmp跳轉,
; 這將導致之前做的預測失效,從而起到了重新整理的作用。
[bits 32]
p_mode_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp,LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax
mov byte [gs:160], 'P'
jmp $