多级分页中第 table 页的详细信息

details about page table walks in multi-level paging

为了简单起见,我将用一个具体的例子来解释我的问题。请注意,我对基本概念很感兴趣。

考虑一个 2 级分页方案,即我们有一个页面目录 table 和多个页面 table。为了将虚拟地址转换为物理地址,我们首先使用虚拟地址的 n 个最高有效位来索引页目录 table。从那里我们获得页面地址 table。使用我们虚拟地址的剩余位,我们索引此页面 table 以找到我们数据的物理地址。

现在我想知道以下问题:Does/Should 页目录 table 存储页 table 的虚拟地址或物理地址?

如果它存储页面 tables 的物理地址,那么这意味着我们不能将任何页面 tables 页面调出到磁盘。但是为了使用多级分页来节省存储空间,我们需要能够将页面 tables 分页到磁盘。那么我们就使用页目录中的虚拟地址table。但这意味着在索引页面目录 table 之后,我们首先必须转换获得的虚拟地址,这需要另一个页面 table 遍历(并且在那个页面 table 遍历中我们必须再次转换一个虚拟地址等)。所以这似乎也不起作用。

显然我错过了一些重要的事情。我有兴趣了解这背后的概念以及实际实施。或者让我知道在哪里可以找到该信息。

现代分层分页设计中的页面目录包含页面 table 的物理地址。例如,https://wiki.osdev.org/Paging 表示 32 位 x86 的格式:PDE 的高 20 位是页面对齐的物理地址,低 12 位包括有效条目的标志。与指向最终物理页面的PTE条目基本相同。

但是当 Present 位被清除时,条目的其余部分可用于保存 OS 想要的任何内容,例如交换 space 中的虚拟地址或偏移量。页面遍历硬件在看到它不存在后就停止了,因此页面遍历失败。 (TLB 未命中成为 #PF 页面错误 OS 以某种方式处理,如果它不是错误推测的结果。)

可以设计一个分页架构,其中页面目录的一些条目(但不是全部)包含页面 table 的虚拟地址。例如,中的分页可以看作是两级的,其中包含页table的地址和长度的三个寄存器对可以统称为一个页目录。其中两个条目包含虚拟地址,第三个条目包含物理地址。可以使用物理地址页 table.

解析虚拟地址页 tables

包含物理地址的条目保存虚拟地址的内核分区的翻译space,并且只能在内核模式下访问。包含虚拟地址的条目是每个进程的,需要在每次上下文切换时更改。这与 x86 分页设计形成对比,在 x86 分页设计中,每个进程都有自己的页面目录,并且必须在每个进程中复制内核映射。也就是说,只有一个寄存器 (CR3) 必须在 x86 中的每个上下文切换时更改。另外,VAX中的tiny"page directory"使得pagetable的内存开销优于flat page时间,但仍然不如x86中使用的balanced hierarchical pagetable。另一个折衷是 32 位 x86 中的所有虚拟地址都必须经过两级内存中转换 4KB 页面,而 VAX 中的内核虚拟地址只需要一个级别的内存中转换,因为第一级被保留在少数寄存器中。这对于x86中比较大的页目录是行不通的。

But in order to use multi-level paging to save storage we need to be able to page-out page tables to disk.

一般来说,使页面 table 可分页对于减少分页结构的内存开销不是很重要。虚拟内存分配器应该(并且确实)尝试从页面的最低可用级别 table 开始尽可能密集地分配内存,而更高级别页面目录中的许多条目根本不是 "present",而不是指向整个 table 的 1024 个条目 (4kiB),它们的 Present 位已被清除。尽管经过长时间的分配和释放,页面 table 可能会在最内层变得稀疏,但在其他层变得密集。这是调出页面 table 的地方,尤其是在可用或剩余物理内存总量不足的平台上。在 Windows 上,页面 table 可以在除顶部之外的所有级别上分页。 Linux 页 table 不可分页。

(或者使用大页面:页面目录条目实际上是将 4MiB 的虚拟内存转换为连续的 4MiB 的物理内存。所以你再次避免了 4kiB 的页面 table space 但是该虚拟内存映射而不是不存在。)


分页 tables 可以在 x86 上完成,使用 PDE 上的 Present 位并将其其余部分用作元数据来区分不应该映射和分页。

在 x86 上,您必须让页面错误处理程序手动遍历页面 tables(或在其他结构中查找)以查明它是否真的是无效页面错误,或者如果你到达了一个逻辑上应该存在的不存在的页面目录条目,并且需要在页面的一部分进行分页 table 并让任务重试其内存访问。

(然后这可能会导致另一个页面错误从 4k 页面调入实际数据。)请注意,硬件页面遍历比页面 faults 便宜得多,并且可以在乱序执行期间推测性地发生 "in the background"。


将分配分组到与现有分配相同的 4M 区域(或在 x86-64、2M 或 1G 上)避免需要新的 4k 页 PTE,而只是将一些现有 PTE 更改为 Present。

(x86-64 使用 4 级页面 tables,每级 9 位 4*9 + 12 = 48 位虚拟。经典 32 位 x86 使用 2 级,每级 10 位: 2*10 + 12 = 32 位虚拟。)


多级页面table的引入是为了减轻单级页面table的内存开销,它随着虚拟地址space的大小线性增长(乘以物理地址大小的对数 space 因为每个页面 table 条目存储页面的物理地址)。在单级页table设计中,根据定义,单页table不能部分存在于物理内存中。即使使用多级页面 table,分配也可能最终稀疏地分布在页面的最内层 table。在这种情况下,页面table消耗的物理内存总量可能占处理器(甚至整个系统)消耗的所有物理内存的很大一部分。这就是使页面 table 可分页很有用的原因。

在所有进程中相同的内核映射可以为其页面 table 共享相同的物理页面,由每个页面目录中 PDE 的高半部分指向。 (它们还将设置全局位,因此在更改 CR3 时它们不会失效,因此您甚至可能不需要为它们进行页面遍历。但是如果需要页面遍历来解决 TLB 未命中,那么同一组PTE 可以在数据缓存中保持热。)