国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > 服务器 > 一个操作系统的实现(8)-进一步体会分页机制

一个操作系统的实现(8)-进一步体会分页机制

来源:程序员人生   发布时间:2016-06-22 15:28:56 阅读次数:3078次

上面的两篇文章中,我们对可用内存进行了统计,并且公道的分配了页表的大小。这节中,我们来看看分页的好处

在此之前不知道你有无注意过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]
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生