在前面的程序中,我們用了4MB的空間來寄存頁表,并用它映照了4GB的內(nèi)存空間,而我們的物理內(nèi)存不見得有這么大,這明顯是太浪費(fèi)了。如果我們的內(nèi)存總數(shù)只有16MB的話,只是頁表就占用了25%的內(nèi)存空間。而實(shí)際上,如果僅僅是對等映照的話,16MB的內(nèi)存只要4個頁表就夠了。所以,我們有必要知道內(nèi)存有多大,然后根據(jù)內(nèi)存大小肯定多少頁表是夠用的。而且,1個操作系統(tǒng)也必須知道內(nèi)存的容量,以便進(jìn)行內(nèi)存管理。
這里利用中斷15h
來獲得計算機(jī)的內(nèi)存。
在調(diào)用中斷15h之前,我們需要填充以下寄存器:
e a x
int 15h可完成許多工作,主要由ax的值決定,我們想要獲得內(nèi)存信息,需要將ax賦值為0E820h。
e b x
放置著“后續(xù)值(continuation value)”,第1次調(diào)用時ebx必須為0。
e s : d i
指向1個地址范圍描寫符結(jié)構(gòu)ARDS(Address Range Descriptor Structure),BIOS將會填充此結(jié)構(gòu)。
e c x
es:di所指向的地址范圍描寫符結(jié)構(gòu)的大小,以字節(jié)為單位。不管es:di所指向的結(jié)構(gòu)如何設(shè)置,BIOS最多將會填充ecx個字節(jié)。不過,通常情況下不管ecx為多大,BIOS只填充20字節(jié),有些BIOS疏忽ecx的值,總是填充20字節(jié)。
e d x
0534D4150h(‘SMAP’)──BIOS將會使用此標(biāo)志,對調(diào)用者將要要求的系統(tǒng)映像信息進(jìn)行校驗,這些信息會被BIOS放置到es:di所指向的結(jié)構(gòu)中。
調(diào)用中斷15h以后,結(jié)果寄存于以下寄存器中:
C F
CF=0表示沒有毛病,否則存在毛病。
e a x
0534D4150h(‘SMAP’)。
e s : d i
返回的地址范圍描寫符結(jié)構(gòu)指針,和輸入值相同。
e c x
BIOS填充在地址范圍描寫符中的字節(jié)數(shù)量,被BIOS所返回的最小值是20字節(jié)。
e b x
這里放置著為等到下1個地址描寫符所需要的后續(xù)值,這個值的實(shí)際情勢依賴于具體的BIOS的實(shí)現(xiàn),調(diào)用者沒必要關(guān)心它的具體情勢,只需在下次迭代時將其原封不動地放置到ebx中,就能夠通過它獲得下1個地址范圍描寫符。如果它的值為0,并且CF沒有進(jìn)位,表示它是最后1個地址范圍描寫符。
上面提到的地址范圍描寫符結(jié)構(gòu)(Address Range Descriptor Structure)以下表所示:
偏移 | 名稱 | 意義 |
0 | BaseAddrLow | 基地址的低32位 |
4 | BaseAddrHigh | 基地址的高32位 |
8 | LengthLow | 長度(字節(jié))的低32位 |
12 | LengthHigh | 長度(字節(jié))的高32位 |
16 | Type | 這個地址范圍的地址類型 |
其中,Type的取值及其意義以下表所示:
取值 | 名稱 | 意義 |
1 | AddressRangeMemory | 這個內(nèi)存段是1段可以被OS使用的RAM |
2 | AddressRangeReserved | 這個地址段正在被使用,或被系統(tǒng)保存, 所以1定不要被OS使用 |
其他 | 未定義 | 保存,為未來使用,任何其他置都必須被OS 認(rèn)為是AddressRangeReserved |
由上面的說明我們看出,ax=0E820h時調(diào)用int 15h得到的不單單是內(nèi)存的大小,還包括對不同內(nèi)存段的1些描寫。而且,這些描寫都被保存在1個緩沖區(qū)中。所以,在我們調(diào)用int 15h之前,必須先有緩沖區(qū)。我們可以在每得到1次內(nèi)存描寫時都使用同1個緩沖區(qū),然后對緩沖區(qū)里的數(shù)據(jù)進(jìn)行處理,也能夠?qū)⒚看蔚玫降臄?shù)據(jù)放進(jìn)不同的位置,比如1塊連續(xù)的內(nèi)存,然后在想要處理它們時再讀取。后1種方式可能更方便1些,所以在這里定義了1塊256字節(jié)的緩沖區(qū)(代碼第65行),它最多可以寄存12個20字節(jié)大小的結(jié)構(gòu)體。我們現(xiàn)在還不知道它到底夠不夠用,這個大小僅僅是憑猜想設(shè)定。我們將把每次得到的內(nèi)存信息連續(xù)寫入這塊緩沖區(qū),構(gòu)成1個結(jié)構(gòu)體數(shù)組。然后在保護(hù)模式下把它們讀出來,顯示在屏幕上,并且憑仗它們得到內(nèi)存的容量。
下面是調(diào)用中斷15h的代碼:
65 _MemChkBuf: times 256 db 0
...
111 ; 得到內(nèi)存數(shù)
112 mov ebx, 0
113 mov di, _MemChkBuf
114 .loop:
115 mov eax, 0E820h
116 mov ecx, 20
117 mov edx, 0534D4150h
118 int 15h
119 jc LABEL_MEM_CHK_FAIL
120 add di, 20
121 inc dword [_dwMCRNumber]
122 cmp ebx, 0
123 jne .loop
124 jmp LABEL_MEM_CHK_OK
125 LABEL_MEM_CHK_FAIL:
126 mov dword [_dwMCRNumber], 0
127 LABEL_MEM_CHK_OK:
可以看到,代碼使用了1個循環(huán),1旦CF被置位或ebx為零,循環(huán)將結(jié)束。在第1次循環(huán)開始之前,eax為0000E820h,ebx為0,ecx為20,edx為0534D4150h,es:di指向_MemChkBuf的開始處。在每次循環(huán)進(jìn)行時,寄存器di的值將會遞增,每次的增量為20字節(jié)。另外,eax、ecx和edx的值都不會變,ebx的值我們置之不理。同時,每次循環(huán)我們讓_dwMCRNumber的值加1,這樣到循環(huán)結(jié)
束時它的值會是循環(huán)的次數(shù),同時也是地址范圍描寫符結(jié)構(gòu)的個數(shù)。
接下來在保護(hù)模式下的32位代碼中添加顯示內(nèi)存信息的進(jìn)程。
305 DispMemSize:
306 push esi
307 push edi
308 push ecx
309
310 mov esi, MemChkBuf
311 mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到1個ARDS
312 .loop: ;{
313 mov edx, 5 ; for(int j=0;j<5;j++) //每次得到1個ARDS中的成員
314 mov edi, ARDStruct ; {//順次顯示BaseAddrLow,BaseAddrHigh,LengthLow,
315 .1: ; LengthHigh,Type
316 push dword [esi] ;
317 call DispInt ; DispInt(MemChkBuf[j*4]); //顯示1個成員
318 pop eax ;
319 stosd ; ARDStruct[j*4] = MemChkBuf[j*4];
320 add esi, 4 ;
321 dec edx ;
322 cmp edx, 0 ;
323 jnz .1 ; }
324 call DispReturn ; printf("\n");
325 cmp dword [dwType], 1 ; if(Type == AddressRangeMemory)
326 jne .2 ; {
327 mov eax, [dwBaseAddrLow];
328 add eax, [dwLengthLow];
329 cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
330 jb .2 ;
331 mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
332 .2: ; }
333 loop .loop ;}
334 ;
335 call DispReturn ;printf("\n");
336 push szRAMSize ;
337 call DispStr ;printf("RAM size:");
338 add esp, 4 ;
339 ;
340 push dword [dwMemSize] ;
341 call DispInt ;DispInt(MemSize);
342 add esp, 4 ;
343
344 pop ecx
345 pop edi
346 pop esi
347 ret
對比右側(cè)注釋中的C代碼,可以很容易了解這段代碼的目的:程序的主題是1個循環(huán),循環(huán)的次數(shù)為地址范圍描寫符結(jié)構(gòu)(下文用ARDStruct代替)的個數(shù),每次循環(huán)將會讀取1個ARDStruct。首先打印其中每個成員的各項,然后根據(jù)當(dāng)前結(jié)構(gòu)的類型,得到可以被操作系統(tǒng)使用的內(nèi)存的上限。結(jié)果會被寄存在變量dwMemSize中,并在此模塊的最后打印到屏幕。
其中,
DispInt
函數(shù)定義以下:
42 ;; 顯示1個整形數(shù)
43 DispInt:
44 mov eax, [esp + 4]
45 shr eax, 24
46 call DispAL
47
48 mov eax, [esp + 4]
49 shr eax, 16
50 call DispAL
51
52 mov eax, [esp + 4]
53 shr eax, 8
54 call DispAL
55
56 mov eax, [esp + 4]
57 call DispAL
58
59 mov ah, 07h ; 0000b: 黑底 0111b: 灰字
60 mov al, 'h'
61 push edi
62 mov edi, [dwDispPos]
63 mov [gs:edi], ax
64 add edi, 4
65 mov [dwDispPos], edi
66 pop edi
67
68 ret
69 ;; DispInt 結(jié)束
DispStr
定義以下:
71 ;; 顯示1個字符串
72 DispStr:
73 push ebp
74 mov ebp, esp
75 push ebx
76 push esi
77 push edi
78
79 mov esi, [ebp + 8] ; pszInfo
80 mov edi, [dwDispPos]
81 mov ah, 0Fh
82 .1:
83 lodsb
84 test al, al
85 jz .2
86 cmp al, 0Ah ; 是回車嗎?
87 jnz .3
88 push eax
89 mov eax, edi
90 mov bl, 160
91 div bl
92 and eax, 0FFh
93 inc eax
94 mov bl, 160
95 mul bl
96 mov edi, eax
97 pop eax
98 jmp .1
99 .3:
100 mov [gs:edi], ax
101 add edi, 2
102 jmp .1
103
104 .2:
105 mov [dwDispPos], edi
106
107 pop edi
108 pop esi
109 pop ebx
110 pop ebp
111 ret
112 ;; DispStr 結(jié)束
113
114 ;; 換行
115 DispReturn:
116 push szReturn
117 call DispStr ;printf("\n");
118 add esp, 4
119
120 ret
121 ;; DispReturn 結(jié)束
DispInt和DispStr函數(shù)連同DispAL、DispReturn被放在了lib.inc
中,并且通過以下語句包括進(jìn)pmtest7.asm中:
%include "lib.inc"
這與直接把代碼寫進(jìn)這個位置的效果是1樣的,把他們單獨(dú)放到1個文件有益于瀏覽。
在DispInt中,[esp+4]即為已入棧的參數(shù),函數(shù)通過4次對DispAL的調(diào)用顯示了1個整數(shù),并且最后顯示1個灰色的字母“h”。函數(shù)DispStr通過1個循環(huán)來顯示字符串,每次復(fù)制1個字符入顯存,遇到\0則結(jié)束循環(huán)。同時,DispStr加入了對回車的處理,遇到0Ah就會從下1行的開始處繼續(xù)顯示。由于這1點(diǎn),DispReturn也做了簡化,通過DispStr來處理回車。
在之前的程序中,我們用edi保存當(dāng)前的顯示位置,從這個程序開始,我們改成用變量dwDispPos來保存。這樣我們就能夠放心腸使用edi這個寄存器。
至此,我們新增的內(nèi)容已準(zhǔn)備得差不多了,另外還需要提到的1點(diǎn)是,在數(shù)據(jù)段中,幾近每一個變量都有類似的兩個符號,比如:
57 _dwMemSize: dd 0
和
73 dwMemSize equ _dwMemSize - $$
在實(shí)模式下應(yīng)使用_dwMemSize,而在保護(hù)模式下應(yīng)使用dwMemSize。由于程序是在實(shí)模式下編譯的,地址只適用于實(shí)模式,在保護(hù)模式下,數(shù)據(jù)的地址應(yīng)當(dāng)是其相對段基址的偏移。
接下來就是調(diào)用DispMemSize來顯示內(nèi)存信息啦:
238 push szMemChkTitle
239 call DispStr
240 add esp, 4
241
242 call DispMemSize ; 顯示內(nèi)存信息
在調(diào)用DispMemSize之前,我們顯示了1個字符串作為將要打印的內(nèi)存信息的表格頭。現(xiàn)在來看看結(jié)果:
上面的結(jié)果圖,總共有6段內(nèi)存被列出來,對列出的內(nèi)存情況解釋以下表:
內(nèi)存段 | 屬性 | 是不是可被OS使用 |
00000000h~0009EFFFh | AddressRangeMemory | 可 |
0009F000h~0009FFFFh | AddressRangeReserved | 不可 |
000E8000h~000FFFFFh | AddressRangeReserved | 不可 |
00100000h~01FEFFFFh | AddressRangeMemory | 可 |
01FF0000h~01FFFFFFh | 未定義 | 不可 |
FFFC0000h~FFFFFFFFh | AddressRangeReserved | 不可 |
從上面可以看出,操作系統(tǒng)能夠使用的最大內(nèi)存地址是01FEFFFFh,所以此極為具有32MB⑴6KB的內(nèi)存。而且榮幸的是,我們指定的256字節(jié)的內(nèi)存MemChkBuf是夠用的。
你可能沒有想到,得到內(nèi)存容量還要這么多代碼,不過,實(shí)際上我們除得到了內(nèi)存的大小,還得到了可用內(nèi)存的散布信息。由于歷史緣由,系統(tǒng)可用內(nèi)存散布得其實(shí)不連續(xù),所以在使用的時候,我們要根據(jù)得到的信息謹(jǐn)慎行事。
內(nèi)存容量得到了,你是不是還記得我們?yōu)楹我玫絻?nèi)存?我們是為了節(jié)儉使用,不再初始化所有PDE和所有頁表。現(xiàn)在,我們已可以根據(jù)內(nèi)存大小計算應(yīng)初始化多少PDE和多少頁表,下面來修改1下函數(shù)SetupPaging。
249 ; 啟動分頁機(jī)制 --------------------------------------------------------------
250 SetupPaging:
251 ; 根據(jù)內(nèi)存大小計算應(yīng)初始化多少PDE和多少頁表
252 xor edx, edx
253 mov eax, [dwMemSize]
254 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 1個頁表對應(yīng)的內(nèi)存大小
255 div ebx
256 mov ecx, eax ; 此時 ecx 為頁表的個數(shù),也即 PDE 應(yīng)當(dāng)?shù)膫€數(shù)
257 test edx, edx
258 jz .no_remainder
259 inc ecx ; 如果余數(shù)不為 0 就需增加1個頁表
260 .no_remainder:
261 push ecx ; 暫存頁表個數(shù)
262
263 ; 為簡化處理, 所有線性地址對應(yīng)相等的物理地址. 并且不斟酌內(nèi)存空洞.
264
265 ; 首先初始化頁目錄
266 mov ax, SelectorPageDir ; 此段首地址為 PageDirBase
267 mov es, ax
268 xor edi, edi
269 xor eax, eax
270 mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
271 .1:
272 stosd
273 add eax, 4096 ; 為了簡化, 所有頁表在內(nèi)存中是連續(xù)的.
274 loop .1
275
276 ; 再初始化所有頁表
277 mov ax, SelectorPageTbl ; 此段首地址為 PageTblBase
278 mov es, ax
279 pop eax ; 頁表個數(shù)
280 mov ebx, 1024 ; 每一個頁表 1024 個 PTE
281 mul ebx
282 mov ecx, eax ; PTE個數(shù) = 頁表個數(shù) * 1024
283 xor edi, edi
284 xor eax, eax
285 mov eax, PG_P | PG_USU | PG_RWW
286 .2:
287 stosd
288 add eax, 4096 ; 每頁指向 4K 的空間
289 loop .2
290
291 mov eax, PageDirBase
292 mov cr3, eax
293 mov eax, cr0
294 or eax, 80000000h
295 mov cr0, eax
296 jmp short .3
297 .3:
298 nop
299
300 ret
301 ; 分頁機(jī)制啟動終了 ----------------------------------------------------------
在函數(shù)的開頭,我們就用內(nèi)存大小除以4MB來得到應(yīng)初始化的PDE的個數(shù)(同時也是頁表的個數(shù))。在初始化頁表的時候,通過剛剛計算出的頁表個數(shù)乘以1024(每一個頁表含1024個PTE)得出要填充的PTE個數(shù),然后通過循環(huán)完成對它的初始化。
這樣1來,頁表所占的空間就小很多,在本例中,32MB的內(nèi)存實(shí)際上只要32KB的頁表就夠了,所以在GDT中,這樣初始化頁表段:
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096*8⑴,DA_DRW
這樣,程序所需的內(nèi)存空間就小了許多。
; ==========================================
; pmtest7.asm
; 編譯方法:nasm pmtest7.asm -o pmtest7.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 和1些說明
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 描寫符
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW ; Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096 * 8 - 1, DA_DRW ; Page Tables
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非1致代碼段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非1致代碼段, 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 結(jié)束
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] ; 數(shù)據(jù)段
ALIGN 32
[BITS 32]
LABEL_DATA:
; 實(shí)模式下使用這些符號
; 字符串
_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
_MemChkBuf: times 256 db 0
; 保護(hù)模式下使用這些符號
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 - $$
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
; 打開地址線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句會把 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 ; 開中斷
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 ax, SelectorData
mov es, ax
mov ax, SelectorVideo
mov gs, ax ; 視頻段選擇子
mov ax, SelectorStack
mov ss, ax ; 堆棧段選擇子
mov esp, TopOfStack
; 下面顯示1個字符串
push szPMMessage
call DispStr
add esp, 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ; 顯示內(nèi)存信息
call SetupPaging ; 啟動分頁機(jī)制
; 到此停止
jmp SelectorCode16:0
; 啟動分頁機(jī)制 --------------------------------------------------------------
SetupPaging:
; 根據(jù)內(nèi)存大小計算應(yīng)初始化多少PDE和多少頁表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 1個頁表對應(yīng)的內(nèi)存大小
div ebx
mov ecx, eax ; 此時 ecx 為頁表的個數(shù),也即 PDE 應(yīng)當(dāng)?shù)膫€數(shù)
test edx, edx
jz .no_remainder
inc ecx ; 如果余數(shù)不為 0 就需增加1個頁表
.no_remainder:
push ecx ; 暫存頁表個數(shù)
; 為簡化處理, 所有線性地址對應(yīng)相等的物理地址. 并且不斟酌內(nèi)存空洞.
; 首先初始化頁目錄
mov ax, SelectorPageDir ; 此段首地址為 PageDirBase
mov es, ax
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 為了簡化, 所有頁表在內(nèi)存中是連續(xù)的.
loop .1
; 再初始化所有頁表
mov ax, SelectorPageTbl ; 此段首地址為 PageTblBase
mov es, ax
pop eax ; 頁表個數(shù)
mov ebx, 1024 ; 每一個頁表 1024 個 PTE
mul ebx
mov ecx, eax ; PTE個數(shù) = 頁表個數(shù) * 1024
xor edi, edi
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每頁指向 4K 的空間
loop .2
mov eax, PageDirBase
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分頁機(jī)制啟動終了 ----------------------------------------------------------
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到1個ARDS
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++) //每次得到1個ARDS中的成員
mov edi, ARDStruct ; {//順次顯示BaseAddrLow,BaseAddrHigh,LengthLow,
.1: ; LengthHigh,Type
push dword [esi] ;
call DispInt ; DispInt(MemChkBuf[j*4]); //顯示1個成員
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)
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" ; 庫函數(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 ; 段地址會在程序開始處被設(shè)置成正確的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
lib.inc
:
;; lib.inc
;; 顯示 AL 中的數(shù)字
DispAL:
push ecx
push edx
push edi
mov edi, [dwDispPos]
mov ah, 0Fh ; 0000b: 黑底 1111b: 白字
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .1
add al, '0'
jmp .2
.1:
sub al, 0Ah
add al, 'A'
.2:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
;add edi, 2
mov [dwDispPos], edi
pop edi
pop edx
pop ecx
ret
;; DispAL 結(jié)束
;; 顯示1個整形數(shù)
DispInt:
mov eax, [esp + 4]
shr eax, 24
call DispAL
mov eax, [esp + 4]
shr eax, 16
call DispAL
mov eax, [esp + 4]
shr eax, 8
call DispAL
mov eax, [esp + 4]
call DispAL
mov ah, 07h ; 0000b: 黑底 0111b: 灰字
mov al, 'h'
push edi
mov edi, [dwDispPos]
mov [gs:edi], ax
add edi, 4
mov [dwDispPos], edi
pop edi
ret
;; DispInt 結(jié)束
;; 顯示1個字符串
DispStr:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov esi, [ebp + 8] ; pszInfo
mov edi, [dwDispPos]
mov ah, 0Fh
.1:
lodsb
test al, al
jz .2
cmp al, 0Ah ; 是回車嗎?
jnz .3
push eax
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop eax
jmp .1
.3:
mov [gs:edi], ax
add edi, 2
jmp .1
.2:
mov [dwDispPos], edi
pop edi
pop esi
pop ebx
pop ebp
ret
;; DispStr 結(jié)束
;; 換行
DispReturn:
push szReturn
call DispStr ;printf("\n");
add esp, 4
ret
;; DispReturn 結(jié)束