在一次操作系统课程上听老师说了这么一个有意思的东西,windows的自映射方案居然达到了把4K的页目录的线性地址“藏”在4M页表里的效果,感觉甚是奇特,于是乎就想着说怎么去算。光会算之后仍旧不满足,我又感觉对我而言有两个问题是很模糊的:
- 为什么说0xC0300000是可行的页目录线性地址起始处?
- 在以0xC0000000为页表的线性地址的条件下,页目录的线性地址的起始处必须是0xC0300000吗?
为了解决这两个问题,我查看了很多个博客,但是大多数博客或文章对原理进行了充分的解释,但是并没有例子来说明,个人觉得这样会显得十分空洞,所以下面来跟大家探讨一下有关问题的通俗一点的解释,如果有错误的地方还望指正。
Q1:0xC0300000为何可行
首先我们有如下断言:知道了一个页表的4M空间的线性地址,就可以确定是页目录项的第几项中存放着页目录的物理地址。(在页表线性地址起始处为0xC0000000时,是第768项,其2进制表示为11 0000 0000)
首先值得反复强调的一点就是:
- 页表项(PTE)中存放的是页面的物理地址
- 页目录项(PDE)中存放的是页表物理地址
- CR3寄存器中存放的则是页目录的物理地址
还有一点也很重要:页目录常驻内存,内核的页目录是处于非交换分页池中的。不可以被换出去。且其在物理内存中占有一片连续的4K空间。
所以有一个很显然但是经常被我们所忽视的结论:页目录的线性地址与其物理地址大小相同,全都连续的,这意味着线性地址空间加上某个偏移量可以和物理地址空间重叠!
下面上图表示一下:
(1块的大小为1KB)
所以实际上我们直接知道物理地址只有页目录的物理地址(在CR3寄存器中),内存中的页目录项里放着每个页表的物理地址,所以我们想知道页表的物理地址时只能通过间接的方式来访问。
通过哪种方式呢,首先是通过CR3寄存器中的物理地址找到页目录,再通过我们线性地址高10位作为索引(注意我们这里说的是索引而不是地址偏移量)找到线性地址对应的页表的物理地址。那么我们就能找到线性地址对应的页表的物理地址了。
我们来举个例子说明这个过程。比如我给定一个线性地址为0x88020A68(这个地址没有任何特殊含义...),我们想找到它对应的物理地址,那么我们怎么做?
首先我们通过CR3寄存器中的物理地址找到了页目录,线性地址的二进制格式是这样:1000 1000 0000 0010 0000 1010 0110 1000
我们把它分为三段: 1000100000 | 0000100000 | 101001101000
♠ 高10位:二进制为 1000100000 这个是页目录的索引号,是用于寻找页目录项的。
所以我们要找的页目录项的实际物理地址应该是:1000100000<<2 + CR3
这里我们有必要对←移2位做出解释。我们知道,物理地址是按照字节编址的,就是说每1个字节即8位占用一个物理地址,而我们的页目录项中存着的页表的物理地址实际上有32位(其实本来只要20位就够了,为什么是20位?因为一个页表也占一个页的大小,所以其大小为4KB,所以页表的起始地址一定是XXXXX000h的形式,所以实际上我们并不需要后12位,但是在实际中为了对齐和加入一些FLAG,我们又加到了32位),这就意味着我们的一个页目录项占用了4个物理地址。索引号是连续的,但偏移量是不连续的。索引号分别为1,2,3,4的时候(假设0开始),偏移量分别为4,8,12,16。
通过算得的页目录项的实际物理地址去找那个页目录项,我们可以从里面取到 某页表 的物理地址,设为A。
不少人会在这里产生疑问,因为可能会有很多人之前理解到的是:页目录项的物理地址 和 页表的物理地址都存在页目录项中。但其实不然,我们可以把 页目录项的物理地址 想象成为 门牌 ,页目录项 想象为 家, 页表的物理地址 想象为 家具。我们通过门牌找到你家,从里面拿出了家具。
♣ 中间10位:二进制位 0000100000 (十进制为32)这个是页表的索引号,适用于寻找页表项的。
我们刚刚从页目录项中Get到了 某页表的物理地址,而这个物理地址是指页表的No.1页表项的物理地址,我们想要找第32个页表项,那么通过计算得到第32个页表项的物理地址为 100000<<2 + A。我们通过物理地址访问到了第32个页表项,从里面取出了页的物理地址,设为B。
♣ 低12位: 二进制位 101001101000
这个其实就比较简单了,低12位实际上就是页内偏移,所以我们最后得到线性地址所对应的物理地址应该为:
101001101000 + B。
可能有些同学会觉得有些奇怪,为什么这里我们的页内偏移不需要左移两位了呢?实际上是因为我们在页目录和页表中,索引号不等于偏移量,因为索引号的最小单位是页表项(或页目录项)中的内容,即32位,而偏移量的最小单位是8位。但是在页内偏移中,索引号=偏移量。
我们清晰明了地分析了一个线性地址到实际的物理地址的过程,我们可以从中看到,第一步取高10位线性地址时,我们取不同的高10位线性地址,得到的是不同的页目录项,从而里面的内容不同。但是取同一个高10位线性地址时,我们得到的是同一个页表的物理地址,这是由windows的映射规则所决定的。
所以说,我们要访问线性地址0h~3FFFFFh时,我们总是去找 页目录项1 ,我们要访问线性地址400000h~7FFFFFh时, 总是去找 页目录项2,其实也就是说,我们通过高10位线性地址作为索引找到的页目录项里的内容,是映射到我们这片线性地址的页表的物理地址。
页表1 映射到 线性地址:0h~3FFFFFh;
页表2 映射到 线性地址:400000h~7FFFFFh;
... 了解到这个之后,那么我们就探究一下下面这个很有意思的问题:
我们如何通过页表的线性地址找到页表的物理地址?
我们以页表的线性地址起始为0xC0000000为例。
页表的虚拟地址是从0xC0000000至0xC03FFFFFh。我们观察到一个很有意思的事实:这些地址的高10位均是:1100 0000 00(768d)
那么问题来了,我们之前 想找到不同的页表的物理地址(在查页目录时) 需要有不同的高10位线性地址,不同的页目录项对吧?
但现在我去找 不同的页表的物理地址的时候,访问到的却是同一个高10位线性地址,同一个页目录项?那不是矛盾了吗?
这个问题实际上一点都不矛盾!因为什么?因为你原来在查页目录时你最后要找的是某个页的物理地址,所以你第一步找到的是能知晓这一页物理地址的页表的物理地址对吧? 但是你现在要找的是 某个页表的物理地址,那么你第一步找到的是能知晓这一页表物理地址的页表的物理地址对吧?这里实际上是把 页表 当作了 普通的页 ,把页目录 当作了 普通的页表。
页表的页表的物理地址是什么?那就是页目录的物理地址啊!这下我们终于明白了,原来在页目录的第768项(1100 0000 00)里放着的居然是页目录起始项的物理地址!
那我们回到开始去看我们的断言,发现它是多么地正确啊~!
那我们现在知道了:第768项页目录项里存放着页目录的起始物理地址,然而这并没有解决我们的问题。我们的问题是:为什么0xC0300000是可行的页目录线性地址?
我们知道每一个页表可以映射4M的线性空间,那么一定有一个页表是映射页表区的4M的线性空间的,那么我们可以通过比例计算出这个页表项的位置。0xC0000000 = 3G,所以映射页表区的这个页表起始线性地址在页表区中应该是在3/4的位置,那么即3G又3M的位置,即是0xC030 0000处。我们想...这个位置好像是可以映射到我们的4M线性空间的哦。不妨我们来动手一算:
我们如果根据0xC0300C00,我们根据推论呢可以看出来这个好像某个页目录项的线性地址,我们现在想根据页目录项的线性地址去找到其物理地址,有两种方法:
1、既然已知其是页目录项,那么我们可以算出其在线性地址中的偏移量,再加上CR3寄存器里的起始物理地址,就得到了它的物理地址。
2、如果当成一个正常的虚拟地址来找,可以先查页目录表,再查页表,最后得到其物理地址。
如果这个0xC0300000作为起始地址是可行的,那么其一定满足自洽性,即用两种方法算出来的物理地址一定相同。
那么我们开始计算,为了便于观察,我们假定:CR3寄存器中的物理地址为:0x54321000。
第1种方法:0xC0300C00在虚拟地址中的偏移量为C00,那么其对应的物理地址应该也是CR3+0xC00 = 0x54321C00
第2种方法:
事实上我们本来在物理地址里面存入20位就够了(实际中的操作系统会在后12位添加标志信息)。
1、 高10位虚拟地址,由于对于所有页表的虚拟地址来说高10位在这里都是1100 0000 00所以说我们在0x54321000+ 1100 0000 00b<<2 得到的地址是哪个呢?没错,就是CR3+3072。即0x54321C00。那么我们现在找到存在0x543E10的页目录项中的东西,其中隐藏着的是CR3!即是0x54321000。(这里用到我们刚才的结论:在第768项中存入的是页目录的物理地址)
2、 中10位虚拟地址+0x54321000。所以就又是这样的感觉咯,那么则有0x54321000+1100 0000 00b<<2 = 0x54321C00实际上,我们轮回了一把,我们访问到的依旧是0x54321000。
3、 别着急,还有低12位呢,那么我们现在来看低12位,这是页内偏移,但是别忘了,页目录也是页!那么我们找吧,按照C00 = 1100 0000 0000来看的话。实际上要找的物理地址应该是:0x54321000+1100 0000 0000b=0x54321C00
我们发现了什么?这个结论是多么地美妙啊~!通过第1种算法和第2种算法得到的物理地址确实是一样的~!那么我们现在可以说0xC0300000确实是个可行的线性地址了。
Conclusion:
根据上面这个图,其实我们知道了这样一点:
告知我们一个线性地址,不论它是对应实际进程的线性地址,还是对应页表、页目录的线性地址,我们都能准确地给出它的物理地址。这是系统自洽所必须的。
Q2:为什么一定是0xC0300000?
我们再来看个例子,看下面这个例子:
假设页目录的线性起始地址为0xC0200000,请用查表的方式找出虚拟地址为0xC0200000所对应的物理地址。
我们还是用上面的例子,假设CR3寄存器里的物理地址为0x54321000.
1、 高10位虚拟地址,由于对于所有页表的虚拟地址来说高10位在这里都是1100 0000 00所以说我们在0x54321000+ 1100 0000 00<<2 得到的地址即0x54321C00。(在第768项中存入的是页目录的物理地址)我们的0x54321C00中存放的应该是页目录的地址0x54321000。
2、 中10位虚拟地址+0x54321000。那么则有0x54321000+1000 0000 00<<2 = 0x54321800实际上,我们并没有发现0x54321800对应的页目录项的内容有什么特殊之处。所以它会指向一个非页目录物理地址的页表的物理地址,而那个页表的物理地址我们不知道是在哪里。
那可能你会说,那我就在0x54321800这里放一个页目录物理地址不可以吗?当然可以,那我们在0x54321800这里放了一个地址为:0x54321000。
那么问题来了,我们究竟是在0x54321800对应的页目录项里放0x54321000呢,还是在0x54321C00中放0x54321000呢?
所以我们可以看到,页目录的起始地址如果变成其他的线性地址比如0xC0200000,我们就必须有2个页目录项中存放CR3这个地址,实际上意味着,我们有一页页表的物理地址缺失了!那一页页表将永远无法找到,那么体现在线性空间中,我们有4M的线性空间无法访问到,或者说访问到的物理地址是错的!这才是问题的症结所在,所以我们说实际上在windows的自映射方案中,这一点才是决定的核心:映射到整个4G线性空间。