【0】README(分頁機制)
- 0.0) source code from orange’s implemention of a os and text description from Zhaojiong's perfect analysis of Linux kernel and for complete code ,please visit https://github.com/pacosonTang/Orange-s-OS/blob/master/p67.asm
- 0.1)本代碼旨在演示 怎樣開啟分頁機制 + 怎樣構建頁目錄和頁表
- 0.3)本文 只對 與 分頁機制的 代碼進行簡要注釋,言簡意賅;
- 0.4) 為了讓廣大小白(像我一樣對os分頁機制不覺明里的小白)真真切切了解分頁機制,即使這篇文章是轉載自 "0.0" 中的兩本,但我還是將本文歸為原創以推薦到博客首頁;(版權,我已在0.0中聲明了)
; ==========================================
; pmtest6.asm
; 編譯方法:nasm pmtest6.asm -o pmtest6.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些說明
PageDirBase equ 200000h ; 頁目錄開始地址: 2M
PageTblBase equ 201000h ; 頁表開始地址: 2M+4K
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限, 屬性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
; 頁目錄描述符 [add]
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
; 頁表描述符 [add]
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32 ; 非一致代碼段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代碼段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址
; GDT 結束
GdtLen equ $ - LABEL_GDT ; GDT長度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 選擇子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT
SelectorPageTbl equ LABEL_DESC_PAGE_TBL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 數據段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 進入保護模式后顯示此字符串
OffsetPMMessage equ PMMessage - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆棧段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [SPValueInRealMode], sp
; 初始化 16 位代碼段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
; 初始化 32 位代碼段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 初始化數據段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆棧段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
; 為加載 GDTR 作准備
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加載 GDTR
lgdt [GdtPtr]
; 關中斷
cli
; 打開地址線A20
in al, 92h
or al, 00000010b
out 92h, al
; 准備切換到保護模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正進入保護模式
jmp dword SelectorCode32:0 ; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0 處
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 從保護模式跳回到實模式就到了這里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 關閉 A20 地址線
out 92h, al ; ┛
sti ; 開中斷
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代碼段. 由實模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
; 開啟分頁機制的初始化工作,該指令執行后,cpu即可開啟分頁機制[add]
call SetupPaging
mov ax, SelectorData
mov ds, ax ; 數據段選擇子
mov ax, SelectorVideo
mov gs, ax ; 視頻段選擇子
mov ax, SelectorStack
mov ss, ax ; 堆棧段選擇子
mov esp, TopOfStack
; 下面顯示一個字符串
mov ah, 0Ch ; 0000: 黑底 1100: 紅字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源數據偏移
mov edi, (80 * 10 + 0) * 2 ; 目的數據偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 顯示完畢
; 到此停止
jmp SelectorCode16:0
; [add]啟動分頁機制 --------------------------------------------------------------
SetupPaging:
; 為簡化處理, 所有線性地址對應相等的物理地址.
; 首先初始化頁目錄
mov ax, SelectorPageDir ; 此段首地址為 PageDirBase
mov es, ax
mov ecx, 1024 ; 共 1K 個表項
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW ; PageTblBase equ 201000h 頁表開始地址: 2M+4K
; 這里是在初始化頁目錄中項的內容,即對應頁表的內存地址
.1:
stosd
;stosb, stosw, stosd 把al/ ax/ eax的內容存儲到 es:edi 指向的內存單元中, 該指令執行后,edi自增1
add eax, 4096
; 為了簡化, 所有頁表在內存中是連續的.,每個頁表占用4k字節空間
loop .1
; 初始化頁目錄的項內容 over
; 再初始化所有頁表 (1K 個, 4M 內存空間)的項內容,即頁表的項存儲的是內存地址的高20位地址;
mov ax, SelectorPageTbl ; 此段首地址為 PageTblBase
mov es, ax
mov ecx, 1024 * 1024 ; 共 1M 個頁表項, 也即有 1M 個頁 (因為1k個頁目錄項,每個目錄項映射到1k個頁表項)
xor edi, edi
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一頁指向 4K 的空間
loop .2
; 初始化頁表的項內容 over
mov eax, PageDirBase
; 加載頁目錄的基地址到 cr3
mov cr3, eax
; 設置cr0的PG位=1,開啟分頁機制
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分頁機制啟動完畢 ----------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代碼段. 由 32 位代碼段跳入, 跳出后到實模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回實模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and eax, 7FFFFFFEh ; PE=0, PG=0
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址會在程序開始處被設置成正確的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
##**3.3 頁式存儲總結** 分段機制將邏輯地址轉換成線性地址,線性地址通過分頁機制轉換成物理地址。  **為什么使用分頁?(干貨)** 分頁管理機制的目的在於實現虛擬存儲器,線性地址中的任意一個頁都能映射到物理地址中的任何一個頁,使得內存管理機制特別靈活。 **分頁和分段的最大不同之處在於?(干貨)**
- 1)段的長度不固定: 段的長度通常與存放在其中的代碼和數據結構具有相同的長度;
- 2)頁面長度固定:頁面具有固定的長度;
- 3)如果僅使用分段地址轉換:那么存儲在物理內存中的一個數據結構將包含所有的部分;
- 4)如果使用了分頁,那么一個數據結構就可以一部分存儲在物理內存中,一部分存儲在磁盤上(虛擬存儲器);
- 5)為了減少地址轉換所要求的總線總起數量,最近訪問的頁目錄和頁表會被存放在處理器的緩沖器件中,該緩沖器件被稱為TLB(translation lookaside buffer,翻譯后備緩沖寄存器),提高訪存效率;
3.3.1 分頁機制概述
Why-為什么使用兩極頁表結構?
-
w1)頁表含有2^20(1M)個表項,每項占4字節。如果用一個表來存儲的話,將最多占用4M;為減少內存占用量,X86使用了兩極頁表;
-
w2)每個,僅有一個頁目錄占用4k, 每個頁表占用4k,而要知道頁目錄一定常駐內存,而頁表是在需要的時候才占用內容空間,當然常用的頁表會存儲在TLB中,這在一定程度上減少了
頁表機制進行線性地址與物理地址映射所占用內存空間;(干貨) -
1)第一級頁表——頁目錄(bit31~22):頁目錄項存儲的內容是頁表的基地址(高20位存儲頁表基地址,低12位存儲所指向的頁表屬性)(干貨); 它被存放在1頁4k頁面中, 含有1k個4字節長度的表項,通過線性地址的bit31~22進行索引頁目錄表項;
-
2)第二級頁表——頁表(bit21~12):頁表項存儲的內容是物理頁的基地址(高20位存儲物理頁基地址,低12位存儲物理頁屬性)(干貨); 它被存放在1頁4k頁面中,含有1k個4字節長度的表項,而該頁表的表項由 bit21~12進行定位;
-
3)偏移地址(bit11~0):它存儲的是物理頁的第12位地址(干貨); 把頁表項存儲的高20位地址作為物理頁的高20位地址,而偏移地址12位作為物理頁的低12位地址,這樣就得到了32位的物理地址;
-
4)映射到的物理地址空間:頁長4k,總共有1k個頁目錄項,每個頁目錄項映射一張頁表,每張頁表有1k個頁表項;故總共1k * 1k =1M個頁表項;所以物理地址空間=1M * 4k = 4G
(you should know):
Y1)頁目錄的基地址存儲在cr3中;
Y2)開啟分頁機制,要設置cr0的最高位PG位=1;