本文的封面图来源于Pixiv,原作者是リング

本文中的内容基于MIPS32架构,不同架构的页表设计可能有所不同。

对于文章中的单位,「K」等单位表示2的幂而不是10的幂。详见二进制词头以及国际单位制词头

页目录自映射的实质:采用一级页表,并将页表放置在kseg2中以减少物理内存占用。

若虚拟地址空间为4GB,页面大小为4KB,则整个虚拟地址空间对应1M个页,若每个页表项为4B,则一共要占用4MB内存。

此时,可以将页表放置在kseg2中,kseg2将经过MMU进行地址翻译,未使用的页面无需分配物理内存,达到减少物理内存占用的目的。

例如,对于如下页表(注意,为了方便计算,页表起始地址需要对齐4M边界)

虚拟内存中的页表

假设现在要将虚拟地址转化为物理地址,需要访问页表,但页表也在虚拟地址空间中,需要通过页表转化为物理地址,也就是说,我们需要第C 0C00到第C 1000个虚拟页的物理地址。

这个范围对应的页表项,显然也存在于页表中,具体地说,虚拟地址为:

  • C 0C00 --> C 0C00 * 4 + C0C0 0000 = C0F0 3000
  • C 1000 --> C 1000 * 4 + C0C0 0000 = C0F0 4000

显然,这个范围刚好对应一页。

要能获得页表项所在的物理地址,需要首先通过虚拟地址为C0F0 3000C0F0 4000的页表项内容进行转化(实践中,需要提前将该虚拟地址范围的映射关系存储到TLB中,该4KB空间刚好对应一页,占用一个TLB条目,假设对应物理地址0000 8000--0000 9000的物理页),这一页的内容称为页目录

如下图:

页表中的页目录

从本质上,我们使用的是一级页表,只是将其放到了需要进行地址转换的区域。但「将一级页表放到需要进行地址转换的区域」的结果是,在页表中恰好有一页(在预设的页大小、虚拟地址空间大小下),对应了整个页表区域从虚拟地址到物理地址的映射,这一页称为页目录

从上图可以看出,页表中页目录对应的页表项,其映射到的物理地址正是页目录这一页所在的物理地址,这正是「页目录自映射」这一名称的由来。

在进行地址转换时,过程如下(假设要进行转换的虚拟地址为0x1234 5678,页表起始虚拟地址为0xC0C0 0000页目录所在的物理页地址为0x0000 80000x0000 9000,如上图):

  1. 取出虚拟页号(页大小为4KB,虚拟地址的第12到31位为虚拟页号):0x12345678 >> 12 = 0x12345
  2. 按一级页表计算偏移量(假设每个页表项占用4字节):0x12345 * 4 = 0x48D14
  3. 加上页表基地址,得到对应的页表项所在的虚拟地址:0xC0C0 0000 + 0x48D14 = 0xC0C48D14
  4. 现在,又需要将该虚拟地址转换为物理地址,取出虚拟页号:0xC0C48D14 >> 12 = 0xC0C48
  5. 按一级页表计算偏移量:0xC0C48 * 4 = 0x303120
  6. 加上页表基地址,得到对应的页表项所在的虚拟地址:0xC0C0 0000 + 0x303120 = 0xC0F0 3120,注意,此步计算出的地址一定位于页目录中(页目录的虚拟地址范围:0xC0F0 30000xC0F0 4000
  7. 已知页目录的物理地址从0x0000 8000开始,计算偏移量可得对应的页表项存储物理地址:(0xC0F0 3120 - 0xC0F0 3000) + 0x0000 8000=0x0000 8120
  8. 从该物理地址读取页表项,可得到虚拟地址0xC0C48D14对应的物理地址,从该处读取页表项,可得到虚拟地址0x12345678对应的物理地址,转换结束。

可以看出,虽然从本质上,使用的是一级页表,但由于页表本身位于需要进行地址转换的区域,仍然发生了两次地址转化,这相当于使用了二级页表。

事实上,页目录中的每一个页表项,正好相当于二级页表中的第一级页表的页表项。

例如,以上图中的第0xC 0C00页为例,其是页目录中的第一个页表项,映射了虚拟 + 地址0xC0C0 00000xC0C0 1000,这个虚拟地址范围内存储了1K个页表项,映射的虚拟地址范围为4MB。相当于页目录中的一个页表项,最终对应4MB的虚拟内存空间。

事实上,对于上述地址转换过程,我们可以合并前几步:

  1. 第1到4步,(((0x1234 5678 >> 12) * 4 + 0XC0C0 0000) >> 12 = 0x1234 5678 >> 22 + 0XC 0C00,注意,0x1234 5678 >> 20相当于取虚拟地址的高10位,与二级页表设计中第一级页表索引相同。
    二级页表
  2. 合并第4到7步,得到页内偏移,(0x1234 5678 >> 22 + 0XC 0C00) * 4 + 0xC0C0 0000 - 0xC0F0 3000 = (0x1234 5678 >> 22) * 4 = 0x0000 0120,可以看出,其中4 * 0XC 0C00 + 0XC0C0 0000 - 0XC0F0 3000相互抵消,最终相当于根据一级页表索引计算出在一级页表内的偏移量。
  3. 加上页目录的物理地址,得到0x0000 8000 + 0x0000 0120 = 0x0000 8120,从该地址处可读取对应虚拟地址0xC0C48D14的「二级页表」的物理地址。
  4. 由二级页表索引,即可获取到物理地址。