上面的两篇文章中,我们对可用内存进行了统计,并且公道的分配了页表的大小。这节中,我们来看看分页的好处
在此之前不知道你有无注意过1个细节,如果你写1个程序(在Linux或Windows下都可),并改个名复制1份,然后同时调试,你会发现,从变量地址到寄存器的值,几近全部都是1样的!而这些“1样的”地址之间完全不会混淆起来,而是各自完成着自己的职责。这就是分页机制的功劳,下面我们就来摹拟1下这个效果。
先履行某个线性地址处的模块,然后通过改变cr3来转换地址映照关系,再履行同1个线性地址处的模块,由于地址映照已改变,所以两次得到的应当是不同的输出。
映照关系转换前的情形以下图所示:
开始,我们让ProcPagingDemo中的代码实现向LinearAddrDemo这个线性地址的转移,而LinearAddrDemo映照到物理地址空间中
的ProcFoo处。我们让ProcFoo打印出红色的字符串Foo,所以履行时我们应当可以看到红色的Foo。随后我们改变地址映照关系,变
化成下图所示的情形。
页目录表和页表的切换让LinearAddrDemo映照到ProcBar(物理地址空间)处,所以当我们再1次调用进程ProcPagingDemo时,程序将转移到ProcBar处履行,我们将看到红色的字符串Bar。
接下来看看新增的代码:
首先,我们用到了另外1套页目录表和页表,所以本来的页目录段和页表段已不再够用了。事实上,前面的程序中我们用两个段分别寄存页目录表和页表,是为了让我们浏览时更加直观和形象。在接下来的程序中,我们把它们放到同1个段中,同时把增加的1套页目录和页表也放到这个段中。
为了操作方便,我们新增加1个段,其线性地址空间为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
之所以用了两个描写符来描写这个段,是由于我们不单单要读写这段内存,而且要履行其中的代码,而这对描写符的属性要求是不1样的。这两个段的段基址都是0,长度都是4GB。
下面我们就将启动分页的代码做相应的修改,以下所示:
257 ; 启动分页机制 --------------------------------------------------------------
258 SetupPaging:
259 ; 根据内存大小计算应初始化多少PDE和多少页表
260 xor edx, edx
261 mov eax, [dwMemSize]
262 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 1个页表对应的内存大小
263 div ebx
264 mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应当的个数
265 test edx, edx
266 jz .no_remainder
267 inc ecx ; 如果余数不为 0 就需增加1个页表
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 ; 分页机制启动终了 ----------------------------------------------------------
我们原来并没有把页表个数保存起来,而现在情况产生了变化,我们不只有1个页目录和页表,为了初始化另外的页表时方便起见,在这里增加了1个变量PageTableNumber,页表的个数就存在里面。
在全部初始化页目录和页表的进程中,es始终为SelectorFlatRW。这样,想存取物理地址的时候,只需将地址赋值给edi,那末es:edi指向的就是相应物理地址。比如页目录物理地址为PageDirBase0,第276即将edi赋值为PageDirBase0,es:edi因而指向地址PageDirBase0处,赋值通过指令stosd来实现。初始化页表也是一样的道理。
这样,页目录和页表的准备工作就完成了。不过我们不再在原来的位置调用它,而是新建1个函数PagingDemo,把所有与分页有关的内容全都放进里面,这样,程序看起来结构清晰1些。
根据上面两幅图,我们可以认为在这个程序的实现中有4个要关注的要素,分别是ProcPagingDemo、LinearAddrDemo、ProcFoo和ProcBar,我们把它们称为F4。由于程序开始时LinearAddrDemo指向ProcFoo并且线性地址和物理地址是对等的,所以LinearAddrDemo应当等于ProcFoo。而ProcFoo和ProcBar应当是指定的物理地址,所以LinearAddrDemo也应当是指定的物理地址。也正由于如此,我们使用它们时应当确保使用的是FLAT段,即段选择子应当是SelectorFlatC或SelectorFlatRW。
为了将我们的代码放置在ProcFoo和ProcBar这两处地方,我们先写两个函数,在程序运行时将这两个函数的履行码复制过去就能够了。
ProcPagingDemo要调用FLAT段中的LinearAddrDemo,所以如果不想使用段间转移,我们需要把ProcPagingDemo也放进FLAT段中。我们需要写1个函数,然后把代码复制到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的函数,它复制3个进程到指定的内存地址,类似于C语言中的memcpy。但有1点不同,它假定源数据放在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 ; 计数器减1
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 结束-------------------------------------------------------------
被复制的3个进程以下:
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中的代码,这部份代码最重要的部份是4个call语句,以下:
339 call SetupPaging ; 启动分页
340
341 call SelectorFlatC:ProcPagingDemo
342 call PSwitch ; 切换页目录,改变地址映照关系
343 call SelectorFlatC:ProcPagingDemo
首先启动分页机制,然后调用ProcPagingDemo,再切换页目录,最后又调用1遍ProcPagingDemo。
现在ProcPagingDemo、ProcFoo和ProcBar的内容我们都已知道了,由于LinearAddrDemo和ProcFoo相等,并且函数SetupPaging建立起来的是对等的映照关系,所以第1次对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。<font color=”red>具体是如何改变的,不是很懂
所以,此函数调用完成以后,对ProcPagingDemo的调用就变成了后来的映照关系图。
在代码PSwitch的后半部份,我们把cr3的值改成了PageDirBase1,这个切换进程宣布完成。
程序的运行情况以下:
我们看到红色的Foo和Bar,这说明我们的页表切换起作用了。其实,我们先条件到的不同进程有相同的地址,原理跟本例是类似的,也是在任务切换时通过改变cr3的值来切换页目录,从而改变地址映照关系。
这就是分页的妙处。其实,妙处还不单单如此。由于分页机制的存在,程序使用的都是线性地址空间,而不再直接是物理地址。这好像操作系统为利用程序提供了1个不依赖于硬件(物理内存)的平台,利用程序没必要关心实际上有多少物理内存,也没必要关心正在使用的是哪1段内存,乃至没必要关心某1个地址是在物理内存里面还是在硬盘中。总之,操作系统全权负责了这其中的转换工作。
; ==========================================
; pmtest8.asm
; 编译方法:nasm pmtest8.asm -o pmtest8.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 和1些说明
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⑴, 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 结束
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
; 履行这1句会把 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
; 下面显示1个字符串
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, 1个页表对应的内存大小
div ebx
mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应当的个数
test edx, edx
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加1个页表
.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++) // 每次得到1个ARDS(Address Range Descriptor Structure)结构
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到1个ARDS中的成员,共5个成员
mov edi, ARDStruct ; { // 顺次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1: ;
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) // 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]