学习ucore操作系统的时候在附录D. 自映射机制 · ucore_os_docs (gitbooks.io)看到关于页表自映射的介绍,但是讲的不是很详细,理解起来有些吃力,查了一些资料大概有了一个自己的理解。
x86的各种地址概念
-
逻辑地址:
segment selector : offset,如指令地址:cs : ip。
-
线性地址:
base + offset,其中base是segment selector对应到GDT里的段描述符指定的base字段
-
物理地址:
物理地址是物理内存中存储单位 (Byte) 的地址
物理地址在只有分段没有分页时等于线性地址,在开启分页后等于线性地址通过页表映射得到的地址。
通过objdump得到的地址(虚拟地址)其实是逻辑地址的offset,比如%cs对应的段的base是-0xC0000000,虚拟地址0xC0000010对应的逻辑地址是%cs : 0xC0000010,通过分段机制转化成的线性地址是 -0xC0000000+0xC0000010=0x00000010,再通过分页机制查询页表中0x00000010映射的地址就得到了物理地址。当segment selector对应的base是0时,虚拟地址等于线性地址。
寻址方式
虚拟地址(逻辑地址)通过分段机制,转换为线性地址,线性地址通过分页机制转为物理地址,CR3寄存器存的即为PDT起始的物理地址,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uiFomGjt-1660200787416)(https://raw.githubusercontent.com/mingshashan/note-images/doc/image006.png)]
自映射
先看ucore里面关于自映射的描述
如果我们这时需要按虚拟地址的地址顺序显示整个页目录表和页表的内容,则要查找页目录表的页目录表项内容,根据页目录表项内容找到页表的物理地址,再转换成对应的虚地址,然后访问页表的虚地址,搜索整个页表的每个页目录项。这样过程比较繁琐。
我们需要有一个简洁的方法来实现这个查找。ucore做了一个很巧妙的地址自映射设计,把页目录表和页表放在一个连续的4MB虚拟地址空间中,并设置页目录表自身的虚地址<–>物理地址映射关系。这样在已知页目录表起始虚地址的情况下,通过连续扫描这特定的4MB虚拟地址空间,就很容易访问每个页目录表项和页表项内容。
windows里面也使用了页表自映射,而linux里面却没有使用,可参考:linux的页表为什么没有实现自映射_dog250的博客-CSDN博客
ucore关于自映射讲的不是很清晰,我们结合下面的图和实际的示例来说明:
PDT放在PT中,而这1024*1024个PTE位于连续的4M空间,且4M对齐:
自映射:
查找PTE:
页表自映射,即把所有页目录表和页表放在一个连续的4MB虚拟地址空间中,并且要求这段空间4MB对齐,设置页目录表自身的虚地址<–>物理地址映射关系。这样,会有一张页表的内容与页目录的内容完全相同,从而节省了页目录的空间。在已知页目录表起始虚地址的情况下,通过连续扫描这特定的4MB虚拟地址空间,就很容易访问每个页目录表项和页表项内容。代价则是需要从虚拟地址空间中分配出连续的4MB对齐的4MB的空间。
根据上图可知一个合适的页目录表的虚地址的(31,22)和(21,12)是相同的,且(11,0)是0,即4KB对齐,CR3可以找到页目录项的物理地址,而根据线性地址的(31,22)位作为页目录的索引可以找到对应页目录项,页目录项找到页表的物理地址,此物理地址本应指向一个二级页表,而因为自映射机制,指向的还是页目录项的物理地址(其实也是自映射页表的物理地址),也即CR3放的页目录项的物理地址,而再根据(21,12)位作为页表的索引,找到的页表项,放的是物理页的地址而因为自映射,此地址指向的还是页目录项的物理地址(也即CR3放的页目录项的物理地址)。
举个例子,根据自映射的方式,假设假设页目录表的虚拟地址VPD,物理地址PPD。
VPD = 0xF03C000(0000111100|0000111100|0000000000 00)
因为自映射页目录表的线性地址的(31,22)和(21,12)是相同的。
PPD=physicalAddress(VPD) = m
那么根据CR3得到PPD(m)。而根据VPD的(31,22)位去索引页目录项,得到的还是PPD(m),
再根据VPD的(21,12)去索引得到的仍是PPD(m)。
而由于是自映射的空间是4MB对齐,页表项和页表又是4KB对齐,页表项则是4B对齐(每个页表项占用4B空间)。
假如平坦地址空间(flat address space)我们可以认为
0xF03C000(0000111100|0000111100|0000000000 00)->PDT_INDEX(0) ->物理地址:m+0
其对应到第0个页表,对应第0个4MB大小的物理地址(不一定连续)
0xF03C004(0000111100|0000111100|0000000001 00)->PDT_INDEX(1) ->物理地址:m+1*4
其对应到第1个页表,对应第1个4MB大小的物理地址(不一定连续)
0xF03C804(0000111100|0000111100|1000000001 00)->PDT_INDEX(513)->物理地址:m+1*513
其对应到第513个页表,对应第513个4MB大小的物理地址(不一定连续)
很显然可以看出来,对于一个地址addr,想知道其在第几个4MB大小的物理地址(不一定连续)需要addr/4MB即>>22位。
其对应页目录项,则可通过如下方式求得:
VPD + addr>> 22
再来看如何获取addr的对应的页表项。
因为页目录表的虚拟地址我们已经知道所有页目录表和页表放在一个连续的4MB虚拟地址空间中。
我们根据VPD = 0xF03C000(0000111100|0000111100|0000000000 00)的(21,12)位可知,VPD处于这1024个页表的第60(0000111100)项即VPT_60 = VPD = 0xF03C000(0000111100|0000111100|0000000000 00)。
也就是说第0项(第1个页表)的线性地址VPT_0为0xF000000(0000111100|0000000000|0000000000 00)。
对于addr,其(31,22)可以让我们知道其在第几个4MB大小的物理地址(不一定连续)里面,再根据(21,12)可以知道其在这4MB(1024个4KB)里面的第几项因而,可以将addr的(31,12)放在VPT的低22位里面,即
VPT + addr >> 12
这样就知道addr对应的页表项。
参考:
页表自映射 - RichardUSTC - 博客园 (cnblogs.com)
附录D. 自映射机制 · ucore_os_docs (gitbooks.io)
关于WIndows内核自映射方案的通俗解释 - SivilTaram - 博客园 (cnblogs.com)