上面的兩篇文章中,我們對(duì)可用內(nèi)存進(jìn)行了統(tǒng)計(jì),并且公道的分配了頁(yè)表的大小。這節(jié)中,我們來(lái)看看分頁(yè)的好處
在此之前不知道你有無(wú)注意過(guò)1個(gè)細(xì)節(jié),如果你寫1個(gè)程序(在Linux或Windows下都可),并改個(gè)名復(fù)制1份,然后同時(shí)調(diào)試,你會(huì)發(fā)現(xiàn),從變量地址到寄存器的值,幾近全部都是1樣的!而這些“1樣的”地址之間完全不會(huì)混淆起來(lái),而是各自完成著自己的職責(zé)。這就是分頁(yè)機(jī)制的功勞,下面我們就來(lái)摹擬1下這個(gè)效果。
先履行某個(gè)線性地址處的模塊,然后通過(guò)改變cr3來(lái)轉(zhuǎn)換地址映照關(guān)系,再履行同1個(gè)線性地址處的模塊,由于地址映照已改變,所以兩次得到的應(yīng)當(dāng)是不同的輸出。
映照關(guān)系轉(zhuǎn)換前的情形以下圖所示:
開(kāi)始,我們讓ProcPagingDemo中的代碼實(shí)現(xiàn)向LinearAddrDemo這個(gè)線性地址的轉(zhuǎn)移,而LinearAddrDemo映照到物理地址空間中
的ProcFoo處。我們讓ProcFoo打印出紅色的字符串Foo,所以履行時(shí)我們應(yīng)當(dāng)可以看到紅色的Foo。隨后我們改變地址映照關(guān)系,變
化成下圖所示的情形。
頁(yè)目錄表和頁(yè)表的切換讓LinearAddrDemo映照到ProcBar(物理地址空間)處,所以當(dāng)我們?cè)?次調(diào)用進(jìn)程ProcPagingDemo時(shí),程序?qū)⑥D(zhuǎn)移到ProcBar處履行,我們將看到紅色的字符串Bar。
接下來(lái)看看新增的代碼:
首先,我們用到了另外1套頁(yè)目錄表和頁(yè)表,所以本來(lái)的頁(yè)目錄段和頁(yè)表段已不再夠用了。事實(shí)上,前面的程序中我們用兩個(gè)段分別寄存頁(yè)目錄表和頁(yè)表,是為了讓我們?yōu)g覽時(shí)更加直觀和形象。在接下來(lái)的程序中,我們把它們放到同1個(gè)段中,同時(shí)把增加的1套頁(yè)目錄和頁(yè)表也放到這個(gè)段中。
為了操作方便,我們新增加1個(gè)段,其線性地址空間為0~4GB。由于分頁(yè)機(jī)制啟動(dòng)之前線性地址同等于物理地址,所以通過(guò)這個(gè)段可以方便地存取特定的物理地址。此段的代碼定義以下:
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
之所以用了兩個(gè)描寫符來(lái)描寫這個(gè)段,是由于我們不單單要讀寫這段內(nèi)存,而且要履行其中的代碼,而這對(duì)描寫符的屬性要求是不1樣的。這兩個(gè)段的段基址都是0,長(zhǎng)度都是4GB。
下面我們就將啟動(dòng)分頁(yè)的代碼做相應(yīng)的修改,以下所示:
257 ; 啟動(dòng)分頁(yè)機(jī)制 --------------------------------------------------------------
258 SetupPaging:
259 ; 根據(jù)內(nèi)存大小計(jì)算應(yīng)初始化多少PDE和多少頁(yè)表
260 xor edx, edx
261 mov eax, [dwMemSize]
262 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 1個(gè)頁(yè)表對(duì)應(yīng)的內(nèi)存大小
263 div ebx
264 mov ecx, eax ; 此時(shí) ecx 為頁(yè)表的個(gè)數(shù),也即 PDE 應(yīng)當(dāng)?shù)膫€(gè)數(shù)
265 test edx, edx
266 jz .no_remainder
267 inc ecx ; 如果余數(shù)不為 0 就需增加1個(gè)頁(yè)表
268 .no_remainder:
269 mov [PageTableNumber], ecx ; 暫存頁(yè)表個(gè)數(shù)
270
271 ; 為簡(jiǎn)化處理, 所有線性地址對(duì)應(yīng)相等的物理地址. 并且不斟酌內(nèi)存空洞.
272
273 ; 首先初始化頁(yè)目錄
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 ; 為了簡(jiǎn)化, 所有頁(yè)表在內(nèi)存中是連續(xù)的.
282 loop .1
283
284 ; 再初始化所有頁(yè)表
285 mov eax, [PageTableNumber] ; 頁(yè)表個(gè)數(shù)
286 mov ebx, 1024 ; 每一個(gè)頁(yè)表 1024 個(gè) PTE
287 mul ebx
288 mov ecx, eax ; PTE個(gè)數(shù) = 頁(yè)表個(gè)數(shù) * 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 ; 每頁(yè)指向 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 ; 分頁(yè)機(jī)制啟動(dòng)終了 ----------------------------------------------------------
我們?cè)瓉?lái)并沒(méi)有把頁(yè)表個(gè)數(shù)保存起來(lái),而現(xiàn)在情況產(chǎn)生了變化,我們不只有1個(gè)頁(yè)目錄和頁(yè)表,為了初始化另外的頁(yè)表時(shí)方便起見(jiàn),在這里增加了1個(gè)變量PageTableNumber,頁(yè)表的個(gè)數(shù)就存在里面。
在全部初始化頁(yè)目錄和頁(yè)表的進(jìn)程中,es始終為SelectorFlatRW。這樣,想存取物理地址的時(shí)候,只需將地址賦值給edi,那末es:edi指向的就是相應(yīng)物理地址。比如頁(yè)目錄物理地址為PageDirBase0,第276即將edi賦值為PageDirBase0,es:edi因而指向地址PageDirBase0處,賦值通過(guò)指令stosd來(lái)實(shí)現(xiàn)。初始化頁(yè)表也是一樣的道理。
這樣,頁(yè)目錄和頁(yè)表的準(zhǔn)備工作就完成了。不過(guò)我們不再在原來(lái)的位置調(diào)用它,而是新建1個(gè)函數(shù)PagingDemo,把所有與分頁(yè)有關(guān)的內(nèi)容全都放進(jìn)里面,這樣,程序看起來(lái)結(jié)構(gòu)清晰1些。
根據(jù)上面兩幅圖,我們可以認(rèn)為在這個(gè)程序的實(shí)現(xiàn)中有4個(gè)要關(guān)注的要素,分別是ProcPagingDemo、LinearAddrDemo、ProcFoo和ProcBar,我們把它們稱為F4。由于程序開(kāi)始時(shí)LinearAddrDemo指向ProcFoo并且線性地址和物理地址是對(duì)等的,所以LinearAddrDemo應(yīng)當(dāng)?shù)扔赑rocFoo。而ProcFoo和ProcBar應(yīng)當(dāng)是指定的物理地址,所以LinearAddrDemo也應(yīng)當(dāng)是指定的物理地址。也正由于如此,我們使用它們時(shí)應(yīng)當(dāng)確保使用的是FLAT段,即段選擇子應(yīng)當(dāng)是SelectorFlatC或SelectorFlatRW。
為了將我們的代碼放置在ProcFoo和ProcBar這兩處地方,我們先寫兩個(gè)函數(shù),在程序運(yùn)行時(shí)將這兩個(gè)函數(shù)的履行碼復(fù)制過(guò)去就能夠了。
ProcPagingDemo要調(diào)用FLAT段中的LinearAddrDemo,所以如果不想使用段間轉(zhuǎn)移,我們需要把ProcPagingDemo也放進(jìn)FLAT段中。我們需要寫1個(gè)函數(shù),然后把代碼復(fù)制到ProcPagingDemo處。
這樣看來(lái),F4雖然都是當(dāng)作函數(shù)來(lái)使用,但實(shí)際上卻都是內(nèi)存中指定的地址。我們把它們定義為常量。以下:
13 LinearAddrDemo equ 00401000h
14 ProcFoo equ 00401000h
15 ProcBar equ 00501000h
16 ProcPagingDemo equ 00301000h
將代碼填充進(jìn)這些內(nèi)存地址的代碼就在上文我們提到的PagingDemo中,以下:
310 ; 測(cè)試分頁(yè)機(jī)制 --------------------------------------------------------------
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 ; 數(shù)據(jù)段選擇子
337 mov es, ax
338
339 call SetupPaging ; 啟動(dòng)分頁(yè)
340
341 call SelectorFlatC:ProcPagingDemo
342 call PSwitch ; 切換頁(yè)目錄,改變地址映照關(guān)系
343 call SelectorFlatC:ProcPagingDemo
344
345 ret
346 ; ---------------------------------------------------------------------------
其中用到了名為MemCpy的函數(shù),它復(fù)制3個(gè)進(jìn)程到指定的內(nèi)存地址,類似于C語(yǔ)言中的memcpy。但有1點(diǎn)不同,它假定源數(shù)據(jù)放在ds段中,而目的在es段中。所以在函數(shù)的開(kāi)頭,你可以找到分別為ds和es賦值的語(yǔ)句。MemCpy代碼以下:
131 ; ------------------------------------------------------------------------
132 ; 內(nèi)存拷貝,仿 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 ; 判斷計(jì)數(shù)器
149 jz .2 ; 計(jì)數(shù)器為零時(shí)跳出
150
151 mov al, [ds:esi] ; ┓
152 inc esi ; ┃
153 ; ┣ 逐字節(jié)移動(dòng)
154 mov byte [es:edi], al ; ┃
155 inc edi ; ┛
156
157 dec ecx ; 計(jì)數(shù)器減1
158 jmp .1 ; 循環(huán)
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 ; 函數(shù)結(jié)束,返回
169 ; MemCpy 結(jié)束-------------------------------------------------------------
被復(fù)制的3個(gè)進(jìn)程以下:
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
接下來(lái)繼續(xù)看PagingDemo中的代碼,這部份代碼最重要的部份是4個(gè)call語(yǔ)句,以下:
339 call SetupPaging ; 啟動(dòng)分頁(yè)
340
341 call SelectorFlatC:ProcPagingDemo
342 call PSwitch ; 切換頁(yè)目錄,改變地址映照關(guān)系
343 call SelectorFlatC:ProcPagingDemo
首先啟動(dòng)分頁(yè)機(jī)制,然后調(diào)用ProcPagingDemo,再切換頁(yè)目錄,最后又調(diào)用1遍ProcPagingDemo。
現(xiàn)在ProcPagingDemo、ProcFoo和ProcBar的內(nèi)容我們都已知道了,由于LinearAddrDemo和ProcFoo相等,并且函數(shù)SetupPaging建立起來(lái)的是對(duì)等的映照關(guān)系,所以第1次對(duì)ProcPagingDemo的調(diào)用反應(yīng)的就是開(kāi)始時(shí)的內(nèi)存映照關(guān)系圖。
接下來(lái)看看調(diào)用的PSwitch:
349 ; 切換頁(yè)表 ------------------------------------------------------------------
350 PSwitch:
351 ; 初始化頁(yè)目錄
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 ; 為了簡(jiǎn)化, 所有頁(yè)表在內(nèi)存中是連續(xù)的.
361 loop .1
362
363 ; 再初始化所有頁(yè)表
364 mov eax, [PageTableNumber] ; 頁(yè)表個(gè)數(shù)
365 mov ebx, 1024 ; 每一個(gè)頁(yè)表 1024 個(gè) PTE
366 mul ebx
367 mov ecx, eax ; PTE個(gè)數(shù) = 頁(yè)表個(gè)數(shù) * 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 ; 每頁(yè)指向 4K 的空間
374 loop .2
375
376 ; 在此假定內(nèi)存是大于 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 ; ---------------------------------------------------------------------------
這個(gè)函數(shù)前面初始化頁(yè)目錄表和頁(yè)表的進(jìn)程與SetupPaging是差不多的,只是緊接著程序增加了改變線性地址LinearAddrDemo對(duì)應(yīng)的物理地址的語(yǔ)句。改變后,LinearAddrDemo將不再對(duì)應(yīng)ProcFoo,而是對(duì)應(yīng)ProcBar。<font color=”red>具體是如何改變的,不是很懂
所以,此函數(shù)調(diào)用完成以后,對(duì)ProcPagingDemo的調(diào)用就變成了后來(lái)的映照關(guān)系圖。
在代碼PSwitch的后半部份,我們把cr3的值改成了PageDirBase1,這個(gè)切換進(jìn)程宣布完成。
程序的運(yùn)行情況以下:
我們看到紅色的Foo和Bar,這說(shuō)明我們的頁(yè)表切換起作用了。其實(shí),我們先條件到的不同進(jìn)程有相同的地址,原理跟本例是類似的,也是在任務(wù)切換時(shí)通過(guò)改變cr3的值來(lái)切換頁(yè)目錄,從而改變地址映照關(guān)系。
這就是分頁(yè)的妙處。其實(shí),妙處還不單單如此。由于分頁(yè)機(jī)制的存在,程序使用的都是線性地址空間,而不再直接是物理地址。這好像操作系統(tǒng)為利用程序提供了1個(gè)不依賴于硬件(物理內(nèi)存)的平臺(tái),利用程序沒(méi)必要關(guān)心實(shí)際上有多少物理內(nèi)存,也沒(méi)必要關(guān)心正在使用的是哪1段內(nèi)存,乃至沒(méi)必要關(guān)心某1個(gè)地址是在物理內(nèi)存里面還是在硬盤中。總之,操作系統(tǒng)全權(quán)負(fù)責(zé)了這其中的轉(zhuǎn)換工作。
; ==========================================
; pmtest8.asm
; 編譯方法:nasm pmtest8.asm -o pmtest8.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 和1些說(shuō)明
PageDirBase0 equ 200000h ; 頁(yè)目錄開(kāi)始地址: 2M
PageTblBase0 equ 201000h ; 頁(yè)表開(kāi)始地址: 2M + 4K
PageDirBase1 equ 210000h ; 頁(yè)目錄開(kāi)始地址: 2M + 64K
PageTblBase1 equ 211000h ; 頁(yè)表開(kāi)始地址: 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⑴, DA_CR|DA_32 ; 非1致代碼段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非1致代碼段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen⑴, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA|DA_32 ; Stack, 32 位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址
; GDT 結(jié)束
GdtLen equ $ - LABEL_GDT ; GDT長(zhǎng)度
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] ; 數(shù)據(jù)段
ALIGN 32
[BITS 32]
LABEL_DATA:
; 實(shí)模式下使用這些符號(hào)
; 字符串
_szPMMessage: db "In Protect Mode now. ^-^", 0Ah, 0Ah, 0 ; 進(jìn)入保護(hù)模式后顯示此字符串
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 ; 進(jìn)入保護(hù)模式后顯示此字符串
_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
; 保護(hù)模式下使用這些符號(hào)
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
; 得到內(nèi)存數(shù)
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
; 初始化數(shù)據(jù)段描寫符
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 作準(zhǔn)備
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]
; 關(guān)中斷
cli
; 打開(kāi)地址線A20
in al, 92h
or al, 00000010b
out 92h, al
; 準(zhǔn)備切換到保護(hù)模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正進(jìn)入保護(hù)模式
jmp dword SelectorCode32:0
; 履行這1句會(huì)把 SelectorCode32 裝入 cs, 并跳轉(zhuǎn)到 Code32Selector:0 處
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 從保護(hù)模式跳回到實(shí)模式就到了這里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [_wSPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 關(guān)閉 A20 地址線
out 92h, al ; ┛
sti ; 開(kāi)中斷
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代碼段. 由實(shí)模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 數(shù)據(jù)段選擇子
mov es, ax
mov ax, SelectorVideo
mov gs, ax ; 視頻段選擇子
mov ax, SelectorStack
mov ss, ax ; 堆棧段選擇子
mov esp, TopOfStack
; 下面顯示1個(gè)字符串
push szPMMessage
call DispStr
add esp, 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ; 顯示內(nèi)存信息
call PagingDemo ; 演示改變頁(yè)目錄的效果
; 到此停止
jmp SelectorCode16:0
; 啟動(dòng)分頁(yè)機(jī)制 --------------------------------------------------------------
SetupPaging:
; 根據(jù)內(nèi)存大小計(jì)算應(yīng)初始化多少PDE和多少頁(yè)表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 1個(gè)頁(yè)表對(duì)應(yīng)的內(nèi)存大小
div ebx
mov ecx, eax ; 此時(shí) ecx 為頁(yè)表的個(gè)數(shù),也即 PDE 應(yīng)當(dāng)?shù)膫€(gè)數(shù)
test edx, edx
jz .no_remainder
inc ecx ; 如果余數(shù)不為 0 就需增加1個(gè)頁(yè)表
.no_remainder:
mov [PageTableNumber], ecx ; 暫存頁(yè)表個(gè)數(shù)
; 為簡(jiǎn)化處理, 所有線性地址對(duì)應(yīng)相等的物理地址. 并且不斟酌內(nèi)存空洞.
; 首先初始化頁(yè)目錄
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 ; 為了簡(jiǎn)化, 所有頁(yè)表在內(nèi)存中是連續(xù)的.
loop .1
; 再初始化所有頁(yè)表
mov eax, [PageTableNumber] ; 頁(yè)表個(gè)數(shù)
mov ebx, 1024 ; 每一個(gè)頁(yè)表 1024 個(gè) PTE
mul ebx
mov ecx, eax ; PTE個(gè)數(shù) = 頁(yè)表個(gè)數(shù) * 1024
mov edi, PageTblBase0 ; 此段首地址為 PageTblBase0
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每頁(yè)指向 4K 的空間
loop .2
mov eax, PageDirBase0
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分頁(yè)機(jī)制啟動(dòng)終了 ----------------------------------------------------------
; 測(cè)試分頁(yè)機(jī)制 --------------------------------------------------------------
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 ; 數(shù)據(jù)段選擇子
mov es, ax
call SetupPaging ; 啟動(dòng)分頁(yè)
call SelectorFlatC:ProcPagingDemo
call PSwitch ; 切換頁(yè)目錄,改變地址映照關(guān)系
call SelectorFlatC:ProcPagingDemo
ret
; ---------------------------------------------------------------------------
; 切換頁(yè)表 ------------------------------------------------------------------
PSwitch:
; 初始化頁(yè)目錄
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 ; 為了簡(jiǎn)化, 所有頁(yè)表在內(nèi)存中是連續(xù)的.
loop .1
; 再初始化所有頁(yè)表
mov eax, [PageTableNumber] ; 頁(yè)表個(gè)數(shù)
mov ebx, 1024 ; 每一個(gè)頁(yè)表 1024 個(gè) PTE
mul ebx
mov ecx, eax ; PTE個(gè)數(shù) = 頁(yè)表個(gè)數(shù) * 1024
mov edi, PageTblBase1 ; 此段首地址為 PageTblBase1
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每頁(yè)指向 4K 的空間
loop .2
; 在此假定內(nèi)存是大于 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
; 顯示內(nèi)存信息 --------------------------------------------------------------
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到1個(gè)ARDS(Address Range Descriptor Structure)結(jié)構(gòu)
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到1個(gè)ARDS中的成員,共5個(gè)成員
mov edi, ARDStruct ; { // 順次顯示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1: ;
push dword [esi] ;
call DispInt ; DispInt(MemChkBuf[j*4]); // 顯示1個(gè)成員
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" ; 庫(kù)函數(shù)
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代碼段. 由 32 位代碼段跳入, 跳出后到實(shí)模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回實(shí)模式:
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 ; 段地址會(huì)在程序開(kāi)始處被設(shè)置成正確的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]