Chapter 4:保護模式入門
===
:::info
這是讀書筆記

作者:鄭鋼
出版社:佳魁資訊股份有限公司
出版日期:2017/05/31
:::
---
# 保護模式概述
* 第一次出現在 Intel 80286 CPU,address line change from 20-bit to 24-bit。
* 1985年推出首款 32 bits CPU: Intel 80386。
# 保護模式
* 定址範圍達到 4GB。
## 暫存器擴充
* 暫存器寬度達到 32 bits。
* Descriptor Cache registers:記憶體段的 base address。
## 定址擴充

## 模式反轉
* [bits 16] : 告訴編譯器要編譯成 16 bits machine code。
* [bits 32] : 告訴編譯器要編譯成 32 bits machine code。
* 編譯器預設是 [bits 16]。
* 0x66 : 運算元大小模式反轉。
* 0x67 : 定址方式模式反轉。
## 指令擴充
* push 指令:同時 support 8-bit, 16-bit, 32-bit。
# Global Descriptor Table(GDT)
## Segment Descriptor
>參考資料:[Segment descriptor](https://en.wikipedia.org/wiki/Segment_descriptor)

## Selector

## GDTR / LDTR

:::warning
Q:如果 GDT element 的範圍涵蓋另外一個 GDT element 的範圍會怎樣?
Q:如果同個範圍有兩個不同 GDT 規則,該怎麼辦?
當我們在存取記憶體的時候,會利用 selector 來選擇 GDT,然後再搭配 base register 就可以操作該記憶體位置。所以是因應各種情境,來選擇適合的 GDT element。
:::
# 開啟 A20 位址線
>參考資料:[Bochs' map of I/O ports to functions](https://bochs.sourceforge.io/techspec/PORTS.LST)

# 保護模式開關

# 正式啟動保護模式
## Source Code
>mbr.S
```asm=
;主引導程序
;------------------------------------------------------------
%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
```
>boot.inc
```asm=
;------------- 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
```
>loader.S
```asm=
%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:
;打印字符,"2 LOADER"說明loader已經成功加載
; 輸出背景色綠色,前景色紅色,並且跳動的字符串"1 MBR"
mov byte [gs:160],'2'
mov byte [gs:161],0xA4 ; A表示綠色背景閃爍,4表示前景色為紅色
mov byte [gs:162],' '
mov byte [gs:163],0xA4
mov byte [gs:164],'L'
mov byte [gs:165],0xA4
mov byte [gs:166],'O'
mov byte [gs:167],0xA4
mov byte [gs:168],'A'
mov byte [gs:169],0xA4
mov byte [gs:170],'D'
mov byte [gs:171],0xA4
mov byte [gs:172],'E'
mov byte [gs:173],0xA4
mov byte [gs:174],'R'
mov byte [gs:175],0xA4
;------------------------------------------------------------
;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:320], 'P'
jmp $
```
## Compile
```
nasm -I inc/ -o ./out/mbr.bin mbr.S
nasm -I inc/ -o ./out/loader.bin loader.S
```
## Hard Disk Image
```
dd if=../code/out/mbr.bin of=./sr_hd60m.img bs=512 count=1 conv=notrunc
dd if=../code/out/loader.bin of=./sr_hd60m.img bs=512 count=4 seek=2 conv=notrunc
```
## Result
### Before Protected-mode

### Check GDT

### After Protected-mode

# CPU 簡介
## 管線化

>[!Warning] CPU 遇到 jmp 指令,會直接清空 pipeline。
## Descriptor Cache Registers
>[!Warning] 重新載入 selector 就可以更新 Descriptor Cache Registers。
# 記憶體段的保護
根據 GDT 進行記憶體位址的檢查。