标签:
上面的两篇文章中,我们对可用内存进行了统计,并且合理的分配了页表的大小。这节中,我们来看看分页的好处
在此之前不知道你有没有注意过一个细节,如果你写一个程序(在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。<font color=”red>具体是如何改变的,不是很懂
所以,此函数调用完成之后,对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]
标签:
原文地址:http://blog.csdn.net/friendley/article/details/51533273