在支持PAE(物理地址扩展)的X86系统上,虚拟地址可以分为几个部分,作为偏移量索引到3个表中:
a.PDPT(Page Directory Pointer Table页目录指针表)
b.PD(Page Directory页目录)
c.PT(Page Table页表)
d.PTE(Page Table Entry页表项)
解释:
PDPT是一个4个元素的数组,每个元素8个字节,指向一个PD。PD是一个有512个元素的数组,每个元素8个字节,指向一个PT。PT也是一个有512个元素的数组,每个元素8个字节,指向一个PTE。
例如我们要把虚拟地址0xBF80EE6B转换为物理地址
0xBF80EE6B
10111111 10000000 11101110 01101011
10(0x2) | 111111 100(0x1FC) | 00000 1110(0xE) | 1110 01101011(0xE6B) |
---|---|---|---|
2位 | 9位 | 9位 | 12位 |
索引到PDPT | 索引到PD | 索引到PT | 页偏移量 |
地址转换过程就围绕着这3个表和CR3寄存器。CR3寄存器保存着PDPT的物理基地址。
cr3=085c01e0(已知条件)
第一步:
cr3+2*8(读入索引为2的PDPT表项)
(每个元素8个字节,偏移为2的话,前面就有两个8字节(第0和第1)的元素)
85c01f0 内容为 0000000000 ’ 0d66e001
根据相关文档可知,PDPT表项的低12位是标志位和保留位,其余部分作为PD的物理基地址。还有第63位是作为PAE的NX标志位,需要清除。对于本例来说,我们不需要清楚的步骤,因为它本来就是0.(我们的目标是可执行的代码页)
0000000000 ’ 0d66e001
00001101 01100110 11100000 00000001
当我们清除低12位之后,我们得到
0x0d66e000
00001101 01100110 11100000 00000000
这就告诉我们PD的基地址位于物理地址
0x0d66e000
第二步:
0x0d66e000 +0x1fc*8 (读入索引为0x1FC的PD表项)
d66efe000
00000000’0964b063
还是根据文档,PD表项的低12位作为标志位和保留位,其余部分作为PT的基地址。
0964b063
00001001 01100100 10110000 01100011
清除低12位后,我们得到:
00001001 01100100 10110000 00000000
这告诉我们PT基地址是0x0964b000
第三步:
0x0964b000 +e*8 (读入索引为0xE的PT表项)
964b070
00000000’06694021
类似地,清除低12位就得到了页表项的基地址
06694021
00000110 01101001 01000000 00100001
清除低12位之后,我们得到
0x06694000
00000110 01101001 01000000 00000000
这就告诉我们页表项的基地址为0x06694000
第四步:
0x06694000+e6b (从页表项中偏移量0xe6b处读入8字节)
0x06694e6b
经过这四步后,可以得到虚拟地址 0xBF80EE6B转换后的物理地址是0x06694e6b
现代操作系统通过这种机制实现进程地址实现地址隔离。每个地址关联到不同的CR3的值,因此每个进程都有特定的虚拟地址转换。这就是每个进程看似拥有自己的地址空间背后秘密。