其實分頁機制不難,但是不做記錄的話又感覺會忘,所以還是做點記錄。
分頁的主要目的在於實現虛擬存儲器。線性地址中任意一個頁都能映射到物理地址中的任何一個頁,這使得內存管理變得相當靈活。
看圖:
在未打開分頁機制時,線性地址等同於物理地址,於是可以認爲,邏輯地址通過分段機制直接轉換成物理地址。但當分頁開啓時,分段機制將邏輯地址轉換成線性地址,線性地址再通過分頁機制轉換成物理地址。
線性地址通過分頁機制進行轉換時,先是從由寄存器cr3指定的頁目錄中根據線性地址的高10位得到頁表地址,然後在頁表中根據線性地址的第12到21位得到物理頁首地址,將這個首地址加上線性地址低12位便得到了物理地址。
分頁機制是否生效的開關位於cr0的最高位PG位。如果PG=1,則分頁機制生效。當我們準備好了頁目錄表和頁表,並將cr3指向頁目錄表之後,只需要置PG位,分頁機制就開始工作了。
關於頁目錄(PDE),頁表(PTE)和cr3這裏就不記錄了,網上的資料一大堆,主要是看對頁目錄和頁表以及cr3的操作。
下面就通過改變地址映射關係執行同一個線性地址處的模塊得到兩次不同結果這個例子來體會分頁機制。
1.分頁機制的啓動
代碼:
; 啓動分頁機制 --------------------------------------------------------------
SetupPaging:
; 根據內存大小計算應初始化多少PDE以及多少頁表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一個頁表對應的內存大小
div ebx
mov ecx, eax ; 此時 ecx 爲頁表的個數,也即 PDE 應該的個數
test edx, edx
jz .no_remainder
inc ecx ; 如果餘數不爲 0 就需增加一個頁表
.no_remainder:
mov [PageTableNumber], ecx ; 暫存頁表個數
; 爲簡化處理, 所有線性地址對應相等的物理地址. 並且不考慮內存空洞.
; 首先初始化頁目錄
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase0 ; 此段首地址爲 PageDirBase0
xor eax, eax
mov eax, PageTblBase0 | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 爲了簡化, 所有頁表在內存中是連續的.
loop .1
; 再初始化所有頁表
mov eax, [PageTableNumber] ; 頁表個數
mov ebx, 1024 ; 每個頁表 1024 個 PTE
mul ebx
mov ecx, eax ; PTE個數 = 頁表個數 * 1024
mov edi, PageTblBase0 ; 此段首地址爲 PageTblBase0
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一頁指向 4K 的空間
loop .2
mov eax, PageDirBase0
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分頁機制啓動完畢 ----------------------------------------------------------
這段代碼第一部分是; 根據內存大小計算應初始化多少PDE以及多少頁表,關於這個的具體實現可以參考:根據內存大小計算頁表個數,把頁表的個數計算好後存儲在[PageTableNumber]中,因爲PageTableNumber是定義在數據段中的,所以[PageTableNumber]的段是數據段。
第二部分初始化頁目錄,填充頁目錄包括填充頁目錄的結構和頁目錄的表項,先填充頁目錄結構,頁目錄結構包括頁表的基址和一些屬性位,將頁目錄結構放在eax中,通過stosd指令寫入內存;頁目錄的表項就是頁表,每個表項4字節長,其中存的就是每個頁表的地址,一個頁表是4KB(4096)大小。代碼中所有頁表在內存中是連續的,所以每個表項的值相差4096,注意,是每個表項的值相差4096,不是表項的地址相差4096。
第三部分初始化頁表,每個頁表1024個PTE,將PTE個數算出後,先將頁表屬性寫入頁表開始地址,然後寫入每個PTE項(每個PTE項指向的是一個頁的地址,書中每頁同樣是連續的)。
頁目錄和頁表初始化完成後,將cr3指向頁目錄表,然後設置cr3的PG位,這樣,分頁機制就啓動完成了。
2.切換頁目錄,改變地址映射關係
; 切換頁表 ------------------------------------------------------------------
PSwitch:
; 初始化頁目錄
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase1 ; 此段首地址爲 PageDirBase1
xor eax, eax
mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW
mov ecx, [PageTableNumber]
.1:
stosd
add eax, 4096 ; 爲了簡化, 所有頁表在內存中是連續的.
loop .1
; 再初始化所有頁表
mov eax, [PageTableNumber] ; 頁表個數
mov ebx, 1024 ; 每個頁表 1024 個 PTE
mul ebx
mov ecx, eax ; PTE個數 = 頁表個數 * 1024
mov edi, PageTblBase1 ; 此段首地址爲 PageTblBase1
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一頁指向 4K 的空間
loop .2
; 在此假設內存是大於 8M 的
mov eax, LinearAddrDemo
shr eax, 22
mov ebx, 4096
mul ebx
mov ecx, eax
mov eax, LinearAddrDemo
shr eax, 12
and eax, 03FFh ; 1111111111b (10 bits)
mov ebx, 4
mul ebx
add eax, ecx
add eax, PageTblBase1
mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
mov eax, PageDirBase1
mov cr3, eax
jmp short .3
.3:
nop
ret
; ---------------------------------------------------------------------------
這裏初始化頁目錄和頁表和啓動分頁機制一樣,主要的代碼就是這段:
; 在此假設內存是大於 8M 的
mov eax, LinearAddrDemo ;LinearAddrDemo equ 00401000h
shr eax, 22
mov ebx, 4096
mul ebx
mov ecx, eax
mov eax, LinearAddrDemo
shr eax, 12
and eax, 03FFh ; 1111111111b (10 bits)
mov ebx, 4
mul ebx
add eax, ecx
add eax, PageTblBase1 ;PageTblBase1 equ 211000h ; 頁表開始地址:2M + 64K + 4K
mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
這段代碼是改變線性地址LinearAddrDemo對應的物理地址的語句。改變後,LinearAddrDemo將不再對應ProcFoo,而是對應ProcBar。
線性地址的各部分功能如圖所示,
mov eax, LinearAddrDemo
shr eax, 22
取線性地址的高10位,即PTE在PDE中的索引,
mov ebx, 4096
mul ebx
mov ecx, eax
因爲一個PTE是4K大小,所以乘以4096來算出對應頁表的地址,
mov eax, LinearAddrDemo
shr eax, 12
and eax, 03FFh ; 1111111111b (10 bits)
取中間10位,即對應頁在PTE中的表項號,
mov ebx, 4
mul ebx
add eax, ecx
add eax, PageTblBase1
因爲每頁大小4K,所以乘以4,這算出來是一個偏移,然後再加上頁表的首地址就算出要修改的地址。
mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
將這個地址寫入ProcBar就成功修改了線性地址LinearAddrDemo對應的物理地址。