上面的兩篇文章中,我們對可用內存進行了統計,並且合理的分配了頁表的大小。這節中,我們來看看分頁的好處
在此之前不知道你有沒有注意過一個細節,如果你寫一個程序(在Linux或Windows下均可),並改個名復制一份,然後同時調試,你會發現,從變量地址到寄存器的值,幾乎全部都是一樣的!而這些“一樣的”地址之間完全不會混淆起來,而是各自完成著自己的職責。這就是分頁機制的功勞,下面我們就來模擬一下這個效果。
線性地址到物理地址的映射
先執行某個線性地址處的模塊,然後通過改變cr3來轉換地址映射關系,再執行同一個線性地址處的模塊,由於地址映射已經改變,所以兩次得到的應該是不同的輸出。
映射關系轉換前的情形如下圖所示:
開始,我們讓ProcPagingDemo中的代碼實現向LinearAddrDemo這個線性地址的轉移,而LinearAddrDemo映射到物理地址空間中
的ProcFoo處。我們讓ProcFoo打印出紅色的字符串Foo,所以執行時我們應該可以看到紅色的Foo。隨後我們改變地址映射關系,變
化成下圖所示的情形。
頁目錄表和頁表的切換讓LinearAddrDemo映射到ProcBar(物理地址空間)處,所以當我們再一次調用過程ProcPagingDemo時,程序將轉移到ProcBar處執行,我們將看到紅色的字符串Bar。
接下來看看新增的代碼:
改變映射關系的代碼實現
首先,我們用到了另外一套頁目錄表和頁表,所以原先的頁目錄段和頁表段已經不再夠用了。事實上,前面的程序中我們用兩個段分別存放頁目錄表和頁表,是為了讓我們閱讀時更加直觀和形象。在接下來的程序中,我們把它們放到同一個段中,同時把增加的一套頁目錄和頁表也放到這個段中。
為了操作方便,我們新增加一個段,其線性地址空間為0~4GB。由於分頁機制啟動之前線性地址等同於物理地址,所以通過這個段可以方便地存取特定的物理地址。此段的代碼定義如下:
26 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G
27 LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW|DA_LIMIT_4K ; 0~4G
...
41 SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT
42 SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT
之所以用了兩個描述符來描述這個段,是因為我們不僅僅要讀寫這段內存,而且要執行其中的代碼,而這對描述符的屬性要求是不一樣的。這兩個段的段基址都是0,長度都是4GB。
下面我們就將啟動分頁的代碼做相應的修改,如下所示:
257 ; 啟動分頁機制 --------------------------------------------------------------
258 SetupPaging:
259 ; 根據內存大小計算應初始化多少PDE以及多少頁表
260 xor edx, edx
261 mov eax, [dwMemSize]
262 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一個頁表對應的內存大小
263 div ebx
264 mov ecx, eax ; 此時 ecx 為頁表的個數,也即 PDE 應該的個數
265 test edx, edx
266 jz .no_remainder
267 inc ecx ; 如果余數不為 0 就需增加一個頁表
268 .no_remainder:
269 mov [PageTableNumber], ecx ; 暫存頁表個數
270
271 ; 為簡化處理, 所有線性地址對應相等的物理地址. 並且不考慮內存空洞.
272
273 ; 首先初始化頁目錄
274 mov ax, SelectorFlatRW
275 mov es, ax
276 mov edi, PageDirBase0 ; 此段首地址為 PageDirBase0
277 xor eax, eax
278 mov eax, PageTblBase0 | PG_P | PG_USU | PG_RWW
279 .1:
280 stosd
281 add eax, 4096 ; 為了簡化, 所有頁表在內存中是連續的.
282 loop .1
283
284 ; 再初始化所有頁表
285 mov eax, [PageTableNumber] ; 頁表個數
286 mov ebx, 1024 ; 每個頁表 1024 個 PTE
287 mul ebx
288 mov ecx, eax ; PTE個數 = 頁表個數 * 1024
289 mov edi, PageTblBase0 ; 此段首地址為 PageTblBase0
290 xor eax, eax
291 mov eax, PG_P | PG_USU | PG_RWW
292 .2:
293 stosd
294 add eax, 4096 ; 每一頁指向 4K 的空間
295 loop .2
296
297 mov eax, PageDirBase0
298 mov cr3, eax
299 mov eax, cr0
300 or eax, 80000000h
301 mov cr0, eax
302 jmp short .3
303 .3:
304 nop
305
306 ret
307 ; 分頁機制啟動完畢 ----------------------------------------------------------
我們原來並沒有把頁表個數保存起來,而現在情況發生了變化,我們不只有一個頁目錄和頁表,為了初始化另外的頁表時方便起見,在這裡增加了一個變量PageTableNumber,頁表的個數就存在裡面。
在整個初始化頁目錄和頁表的過程中,es始終為SelectorFlatRW。這樣,想存取物理地址的時候,只需將地址賦值給edi,那麼es:edi指向的就是相應物理地址。比如頁目錄物理地址為PageDirBase0,第276行將edi賦值為PageDirBase0,es:edi於是指向地址PageDirBase0處,賦值通過指令stosd來實現。初始化頁表也是同樣的道理。
這樣,頁目錄和頁表的准備工作就完成了。不過我們不再在原來的位置調用它,而是新建一個函數PagingDemo,把所有與分頁有關的內容全都放進裡面,這樣,程序看起來結構清晰一些。
根據上面兩幅圖,我們可以認為在這個程序的實現中有4個要關注的要素,分別是ProcPagingDemo、LinearAddrDemo、ProcFoo和ProcBar,我們把它們稱為F4。因為程序開始時LinearAddrDemo指向ProcFoo並且線性地址和物理地址是對等的,所以LinearAddrDemo應該等於ProcFoo。而ProcFoo和ProcBar應該是指定的物理地址,所以LinearAddrDemo也應該是指定的物理地址。也正因為如此,我們使用它們時應該確保使用的是FLAT段,即段選擇子應該是SelectorFlatC或者SelectorFlatRW。
為了將我們的代碼放置在ProcFoo和ProcBar這兩處地方,我們先寫兩個函數,在程序運行時將這兩個函數的執行碼復制過去就可以了。
ProcPagingDemo要調用FLAT段中的LinearAddrDemo,所以如果不想使用段間轉移,我們需要把ProcPagingDemo也放進FLAT段中。我們需要寫一個函數,然後把代碼復制到ProcPagingDemo處。
這樣看來,F4雖然都是當做函數來使用,但實際上卻都是內存中指定的地址。我們把它們定義為常量。如下:
13 LinearAddrDemo equ 00401000h
14 ProcFoo equ 00401000h
15 ProcBar equ 00501000h
16 ProcPagingDemo equ 00301000h
將代碼填充進這些內存地址的代碼就在上文我們提到的PagingDemo中,如下:
310 ; 測試分頁機制 --------------------------------------------------------------
311 PagingDemo:
312 mov ax, cs
313 mov ds, ax
314 mov ax, SelectorFlatRW
315 mov es, ax
316
317 push LenFoo
318 push OffsetFoo
319 push ProcFoo
320 call MemCpy
321 add esp, 12
322
323 push LenBar
324 push OffsetBar
325 push ProcBar
326 call MemCpy
327 add esp, 12
328
329 push LenPagingDemoAll
330 push OffsetPagingDemoProc
331 push ProcPagingDemo
332 call MemCpy
333 add esp, 12
334
335 mov ax, SelectorData
336 mov ds, ax ; 數據段選擇子
337 mov es, ax
338
339 call SetupPaging ; 啟動分頁
340
341 call SelectorFlatC:ProcPagingDemo
342 call PSwitch ; 切換頁目錄,改變地址映射關系
343 call SelectorFlatC:ProcPagingDemo
344
345 ret
346 ; ---------------------------------------------------------------------------
其中用到了名為MemCpy的函數,它復制三個過程到指定的內存地址,類似於C語言中的memcpy。但有一點不同,它假設源數據放在ds段中,而目的在es段中。所以在函數的開頭,你可以找到分別為ds和es賦值的語句。MemCpy代碼如下:
131 ; ------------------------------------------------------------------------
132 ; 內存拷貝,仿 memcpy
133 ; ------------------------------------------------------------------------
134 ; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
135 ; ------------------------------------------------------------------------
136 MemCpy:
137 push ebp
138 mov ebp, esp
139
140 push esi
141 push edi
142 push ecx
143
144 mov edi, [ebp + 8] ; Destination
145 mov esi, [ebp + 12] ; Source
146 mov ecx, [ebp + 16] ; Counter
147 .1:
148 cmp ecx, 0 ; 判斷計數器
149 jz .2 ; 計數器為零時跳出
150
151 mov al, [ds:esi] ; ┓
152 inc esi ; ┃
153 ; ┣ 逐字節移動
154 mov byte [es:edi], al ; ┃
155 inc edi ; ┛
156
157 dec ecx ; 計數器減一
158 jmp .1 ; 循環
159 .2:
160 mov eax, [ebp + 8] ; 返回值
161
162 pop ecx
163 pop edi
164 pop esi
165 mov esp, ebp
166 pop ebp
167
168 ret ; 函數結束,返回
169 ; MemCpy 結束-------------------------------------------------------------
被復制的三個過程如下:
402 PagingDemoProc:
403 OffsetPagingDemoProc equ PagingDemoProc - $$
404 mov eax, LinearAddrDemo
405 call eax
406 retf
407 LenPagingDemoAll equ $ - PagingDemoProc
408
409 foo:
410 OffsetFoo equ foo - $$
411 mov ah, 0Ch ; 0000: 黑底 1100: 紅字
412 mov al, 'F'
413 mov [gs:((80 * 17 + 0) * 2)], ax ; 屏幕第 17 行, 第 0 列。
414 mov al, 'o'
415 mov [gs:((80 * 17 + 1) * 2)], ax ; 屏幕第 17 行, 第 1 列。
416 mov [gs:((80 * 17 + 2) * 2)], ax ; 屏幕第 17 行, 第 2 列。
417 ret
418 LenFoo equ $ - foo
419
420 bar:
421 OffsetBar equ bar - $$
422 mov ah, 0Ch ; 0000: 黑底 1100: 紅字
423 mov al, 'B'
424 mov [gs:((80 * 18 + 0) * 2)], ax ; 屏幕第 18 行, 第 0 列。
425 mov al, 'a'
426 mov [gs:((80 * 18 + 1) * 2)], ax ; 屏幕第 18 行, 第 1 列。
427 mov al, 'r'
428 mov [gs:((80 * 18 + 2) * 2)], ax ; 屏幕第 18 行, 第 2 列。
429 ret
430 LenBar equ $ - bar
接下來繼續看PagingDemo中的代碼,這部分代碼最重要的部分是四個call語句,如下:
339 call SetupPaging ; 啟動分頁
340
341 call SelectorFlatC:ProcPagingDemo
342 call PSwitch ; 切換頁目錄,改變地址映射關系
343 call SelectorFlatC:ProcPagingDemo
首先啟動分頁機制,然後調用ProcPagingDemo,再切換頁目錄,最後又調用一遍ProcPagingDemo。
現在ProcPagingDemo、ProcFoo以及ProcBar的內容我們都已經知道了,由於LinearAddrDemo和ProcFoo相等,並且函數SetupPaging建立起來的是對等的映射關系,所以第一次對ProcPagingDemo的調用反映的就是開始時的內存映射關系圖。
接下來看看調用的PSwitch:
349 ; 切換頁表 ------------------------------------------------------------------
350 PSwitch:
351 ; 初始化頁目錄
352 mov ax, SelectorFlatRW
353 mov es, ax
354 mov edi, PageDirBase1 ; 此段首地址為 PageDirBase1
355 xor eax, eax
356 mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW
357 mov ecx, [PageTableNumber]
358 .1:
359 stosd
360 add eax, 4096 ; 為了簡化, 所有頁表在內存中是連續的.
361 loop .1
362
363 ; 再初始化所有頁表
364 mov eax, [PageTableNumber] ; 頁表個數
365 mov ebx, 1024 ; 每個頁表 1024 個 PTE
366 mul ebx
367 mov ecx, eax ; PTE個數 = 頁表個數 * 1024
368 mov edi, PageTblBase1 ; 此段首地址為 PageTblBase1
369 xor eax, eax
370 mov eax, PG_P | PG_USU | PG_RWW
371 .2:
372 stosd
373 add eax, 4096 ; 每一頁指向 4K 的空間
374 loop .2
375
376 ; 在此假設內存是大於 8M 的
377 mov eax, LinearAddrDemo
378 shr eax, 22
379 mov ebx, 4096
380 mul ebx
381 mov ecx, eax
382 mov eax, LinearAddrDemo
383 shr eax, 12
384 and eax, 03FFh ; 1111111111b (10 bits)
385 mov ebx, 4
386 mul ebx
387 add eax, ecx
388 add eax, PageTblBase1
389 mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
390
391 mov eax, PageDirBase1
392 mov cr3, eax
393 jmp short .3
394 .3:
395 nop
396
397 ret
398 ; ---------------------------------------------------------------------------
這個函數前面初始化頁目錄表和頁表的過程與SetupPaging是差不多的,只是緊接著程序增加了改變線性地址LinearAddrDemo對應的物理地址的語句。改變後,LinearAddrDemo將不再對應ProcFoo,而是對應ProcBar。具體是如何改變的,不是很懂
所以,此函數調用完成之後,對ProcPagingDemo的調用就變成了後來的映射關系圖。
在代碼PSwitch的後半部分,我們把cr3的值改成了PageDirBase1,這個切換過程宣告完成。
程序的運行情況如下:
我們看到紅色的Foo和Bar,這說明我們的頁表切換起作用了。其實,我們先前提到的不同進程有相同的地址,原理跟本例是類似的,也是在任務切換時通過改變cr3的值來切換頁目錄,從而改變地址映射關系。
這就是分頁的妙處。其實,妙處還不僅僅如此。由於分頁機制的存在,程序使用的都是線性地址空間,而不再直接是物理地址。這好像操作系統為應用程序提供了一個不依賴於硬件(物理內存)的平台,應用程序不必關心實際上有多少物理內存,也不必關心正在使用的是哪一段內存,甚至不必關心某一個地址是在物理內存裡面還是在硬盤中。總之,操作系統全權負責了這其中的轉換工作。
源代碼
; ==========================================
; pmtest8.asm
; 編譯方法:nasm pmtest8.asm -o pmtest8.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些說明
PageDirBase0 equ 200000h ; 頁目錄開始地址: 2M
PageTblBase0 equ 201000h ; 頁表開始地址: 2M + 4K
PageDirBase1 equ 210000h ; 頁目錄開始地址: 2M + 64K
PageTblBase1 equ 211000h ; 頁表開始地址: 2M + 64K + 4K
LinearAddrDemo equ 00401000h
ProcFoo equ 00401000h
ProcBar equ 00501000h
ProcPagingDemo equ 00301000h
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限, 屬性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G
LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW|DA_LIMIT_4K ; 0~4G
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_CR|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
SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT
SelectorFlatRW equ LABEL_DESC_FLAT_RW - 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:
; 實模式下使用這些符號
; 字符串
_szPMMessage: db "In Protect Mode now. ^-^", 0Ah, 0Ah, 0 ; 進入保護模式後顯示此字符串
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 ; 進入保護模式後顯示此字符串
_szRAMSize db "RAM size:", 0
_szReturn db 0Ah, 0
; 變量
_wSPValueInRealMode dw 0
_dwMCRNumber: dd 0 ; Memory Check Result
_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_dwMemSize: dd 0
_ARDStruct: ; Address Range Descriptor Structure
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
_PageTableNumber dd 0
_MemChkBuf: times 256 db 0
; 保護模式下使用這些符號
szPMMessage equ _szPMMessage - $$
szMemChkTitle equ _szMemChkTitle - $$
szRAMSize equ _szRAMSize - $$
szReturn equ _szReturn - $$
dwDispPos equ _dwDispPos - $$
dwMemSize equ _dwMemSize - $$
dwMCRNumber equ _dwMCRNumber - $$
ARDStruct equ _ARDStruct - $$
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
MemChkBuf equ _MemChkBuf - $$
PageTableNumber equ _PageTableNumber- $$
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 [_wSPValueInRealMode], sp
; 得到內存數
mov ebx, 0
mov di, _MemChkBuf
.loop:
mov eax, 0E820h
mov ecx, 20
mov edx, 0534D4150h
int 15h
jc LABEL_MEM_CHK_FAIL
add di, 20
inc dword [_dwMCRNumber]
cmp ebx, 0
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:
; 初始化 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, [_wSPValueInRealMode]
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:
mov ax, SelectorData
mov ds, ax ; 數據段選擇子
mov es, ax
mov ax, SelectorVideo
mov gs, ax ; 視頻段選擇子
mov ax, SelectorStack
mov ss, ax ; 堆棧段選擇子
mov esp, TopOfStack
; 下面顯示一個字符串
push szPMMessage
call DispStr
add esp, 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ; 顯示內存信息
call PagingDemo ; 演示改變頁目錄的效果
; 到此停止
jmp SelectorCode16:0
; 啟動分頁機制 --------------------------------------------------------------
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
; 分頁機制啟動完畢 ----------------------------------------------------------
; 測試分頁機制 --------------------------------------------------------------
PagingDemo:
mov ax, cs
mov ds, ax
mov ax, SelectorFlatRW
mov es, ax
push LenFoo
push OffsetFoo
push ProcFoo
call MemCpy
add esp, 12
push LenBar
push OffsetBar
push ProcBar
call MemCpy
add esp, 12
push LenPagingDemoAll
push OffsetPagingDemoProc
push ProcPagingDemo
call MemCpy
add esp, 12
mov ax, SelectorData
mov ds, ax ; 數據段選擇子
mov es, ax
call SetupPaging ; 啟動分頁
call SelectorFlatC:ProcPagingDemo
call PSwitch ; 切換頁目錄,改變地址映射關系
call SelectorFlatC:ProcPagingDemo
ret
; ---------------------------------------------------------------------------
; 切換頁表 ------------------------------------------------------------------
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
; ---------------------------------------------------------------------------
PagingDemoProc:
OffsetPagingDemoProc equ PagingDemoProc - $$
mov eax, LinearAddrDemo
call eax
retf
LenPagingDemoAll equ $ - PagingDemoProc
foo:
OffsetFoo equ foo - $$
mov ah, 0Ch ; 0000: 黑底 1100: 紅字
mov al, 'F'
mov [gs:((80 * 17 + 0) * 2)], ax ; 屏幕第 17 行, 第 0 列。
mov al, 'o'
mov [gs:((80 * 17 + 1) * 2)], ax ; 屏幕第 17 行, 第 1 列。
mov [gs:((80 * 17 + 2) * 2)], ax ; 屏幕第 17 行, 第 2 列。
ret
LenFoo equ $ - foo
bar:
OffsetBar equ bar - $$
mov ah, 0Ch ; 0000: 黑底 1100: 紅字
mov al, 'B'
mov [gs:((80 * 18 + 0) * 2)], ax ; 屏幕第 18 行, 第 0 列。
mov al, 'a'
mov [gs:((80 * 18 + 1) * 2)], ax ; 屏幕第 18 行, 第 1 列。
mov al, 'r'
mov [gs:((80 * 18 + 2) * 2)], ax ; 屏幕第 18 行, 第 2 列。
ret
LenBar equ $ - bar
; 顯示內存信息 --------------------------------------------------------------
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一個ARDS(Address Range Descriptor Structure)結構
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一個ARDS中的成員,共5個成員
mov edi, ARDStruct ; { // 依次顯示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1: ;
push dword [esi] ;
call DispInt ; DispInt(MemChkBuf[j*4]); // 顯示一個成員
pop eax ;
stosd ; ARDStruct[j*4] = MemChkBuf[j*4];
add esi, 4 ;
dec edx ;
cmp edx, 0 ;
jnz .1 ; }
call DispReturn ; printf("\n");
cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
jne .2 ; {
mov eax, [dwBaseAddrLow] ;
add eax, [dwLengthLow] ;
cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
jb .2 ;
mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
.2: ; }
loop .loop ;}
;
call DispReturn ;printf("\n");
push szRAMSize ;
call DispStr ;printf("RAM size:");
add esp, 4 ;
;
push dword [dwMemSize] ;
call DispInt ;DispInt(MemSize);
add esp, 4 ;
pop ecx
pop edi
pop esi
ret
; ---------------------------------------------------------------------------
%include "lib.inc" ; 庫函數
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]