概述
好不容易搞明白了保护模式下的操作,兴致勃勃的进入分页机制这一章节,正好最近操作系统的课程上面也讲到了分页机制的内容。在操作系统上看了一遍原理,与真正用汇编实现与启动分页机制,这当中百种滋味,难以言表。
纸上得来终觉浅,绝知此事要躬行
一些前言
在80386当中,寄存器的位数为32位, 也就说明了寻址空间已经大到4GB,在32位保护模式下,通过分段模式+偏移地址的方式,我们写的程序中的逻辑地址通过分段机制直接转换为逻辑地址。 然而在虚拟化,或者进程进出的技术的需要,分页技术需要人们将一块内存空间作为一个单元使用,即”页”。在80386内,页的大小是被固定为4KB。
首先重点1) 页的大小为4KB ,即12位。
分页机制
在分页机制里面,我们汇编程序内的逻辑地址,通过分段机制转换为线性地址,然后这32位的线性地址再通过分页机制,转换为真正的物理地址。
简单的表示为:
分段机制 分页机制
逻辑地址 --------------> 线性地址 -----------------> 物理地址
那么什么是分页机制?
分页机制的实质其实就是一个两级索引表。
第一级表 PDE表
PDE即 Page Direct Entry. 一个PDE是4B,PDE表即一组连续的PDE。PDE表最多有1024个PDE。每一个PDE的其中20位指向了一个PTE表
第二级表
PTE 即 Page Table Entry. PTE同样也是4B大小,PTE表中有1024个PTE。每个PTE的20位作为每个物理页的基址。
从线性地址到物理地址
从上面看出,某个物理页表,是通过PTE表中的某个PTE所指向的,那么要得到一个完整的物理地址,就还缺少偏移地址。而PTE表呢,又是由某个PDE所指向的。
所以我们要将线性地址转换为物理地址,首先要得到PDE表的偏移地址1,得到一个PDE表。再用一个偏移地址2确定某个PTE表中的PTE,再从这个PTE得到物理页表,最后再用偏移地址3和物理页表确定真正的物理地址。
而我们的cr3寄存器就是用来存放偏移地址1,偏移地址2,偏移地址3的。
首先,PDE表中最多有1024个PDE,所以我们的偏移地址1有10位,PTE表中最多有1024个PTE,所以偏移地址2有10位,然后我们最终的物理页表有12位,所以偏移地址3需要12位,
三个偏移地址的长度加起来正好32位,依次从低到高的存放在了cr3寄存器中。
整个过程用图表示为:
那么现在假设我们有了线性地址,那么我们只要知道页表的地址,然后将PDE表和PTE表设置完成后即可。
所以在IA32中,我们将PDE的基地址存放在cr0寄存器的高20位中,(可以想一想为何是20位)
,然后在设置完PDE表和PTE表后,将cr0的最高位即PG位置1,即分页机制生效
实现
在这里我们用关键代码, 实现f(线性地址) = 物理地址 = 线性地址的例子
...
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M+4K
...
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
...
; 启动分页机制 --------------------------------------------------------------
SetupPaging:
; 为简化处理, 所有线性地址对应相等的物理地址.
; 首先初始化页目录
mov ax, SelectorPageDir ; 此段首地址为 PageDirBase
mov es, ax
mov ecx, 1024 ; 共 1K 个表项
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表 (1K 个, 4M 内存空间)
mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
mov es, ax
mov ecx, 1024 * 1024 ; 共 1M 个页表项, 也即有 1M 个页
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
; 分页机制启动完毕 ----------------------------------------------------------
总结
可以看到,我们在代码中用了4kb+4MB的代价来寻址4GB的空间,然而在早期的内存中也许是只有几MB的,那么为了合理有效的利用分页机制,我们必须根据内存的可用空间来设置我们的PDE表和PTE表,这也是我接下来要学习的内容