【问题标题】:Process Page Tables进程页表
【发布时间】:2025-12-25 00:45:11
【问题描述】:

我有兴趣深入了解虚拟内存和页面机制,特别是对于 Windows x86 系统。根据我从各种在线资源(包括 SO 上发布的其他问题)中收集到的信息,

1) 每个进程的单独页表位于同一进程的内核地址空间内。

2) 每个进程只有一个页表,包含虚拟页到物理页(或帧)的映射。

3) 与给定虚拟地址对应的物理地址由内存管理单元 (MMU) 计算,本质上是使用提供的虚拟地址的前 20 位作为页表的索引,使用该索引来检索物理帧的起始地址,然后根据虚拟地址的剩余 12 位对该地址应用一些偏移量。

这三个说法正确吗?还是我误解了信息?

【问题讨论】:

    标签: c windows assembly x86 kernel


    【解决方案1】:

    所以,首先让我们澄清一些事情:

    1. 在 x86 架构的情况下,决定分页策略的不是操作系统,而是 CPU(更具体地说是 MMU)。操作系统如何看待分页系统与其实现方式无关。正如评论者正确指出的那样,分页模型有一个特定于操作系统的组件。这从属于硬件的做事方式。
    2. 32 位和 64 位 x86 处理器具有不同的分页方案,因此如果不指定处理器的字长,就无法真正谈论 x86 分页模型。

    接下来是 32 位 x86 分页模型的大规模压缩版本,使用它的最简单版本。有许多额外的调整是可能的,我知道各种操作系统都在使用它们。我不会讨论这些,因为我并不真正熟悉大多数操作系统的内部结构,而且在你掌握了更简单的东西之前,你真的不应该深入了解这些。如果您想了解 x86 分页模型的所有奇妙怪癖,可以访问英特尔文档:Intel System Programming Guide

    在最简单的分页模型中,内存空间被划分为称为页的 4KB 块。其中 1024 个连续块映射到页表(大小也是 4KB)。对于更进一步的间接级别,所有 1024 个页表都映射到一个 4KB 的页目录,并且该目录的基址位于处理器中的一个特殊寄存器 %cr3 中。这两个级别的结构是到位的,因为操作系统中的大多数内存空间都是稀疏,这意味着其中大部分是未使用的。您不想保留一堆页表以供未触及的内存使用。

    当你得到一个内存地址时,最重要的 10 位索引到页目录,它为你提供了页表的基础。接下来的 10 位索引该页表,为您提供物理页的基础(也称为物理帧)。最后,最后 12 位索引到帧中。假设您已将 %cr3 设置为正确的值,MMU 会为您完成所有这些工作。

    64 位系统具有 4 级 分页系统,因为它们的内存空间更加稀疏。此外,页面大小可能不是 4KB。

    真正解决您的问题:

    1. 所有这些分页信息(表、目录等)都位于内核内存中。请注意,内核内存是一个大问题,没有为单个进程提供内核内存的概念。
    2. 每个进程只有一页目录。这是因为页目录定义了一个内存空间,每个进程只有一个内存空间。
    3. 上面的最后一段为您提供了地址被分割的方式。

    编辑:清理和小修改。

    【讨论】:

    • 如您所说,分页方案与操作系统无关。例如,Linux 处理分页的方式与普通 unix 不同,它不是 10:10:12 模型。
    • 非常感谢您的见解。虚拟地址是 32 位的,对吗?不是您在第三(最后)段中所述的 32 个字节。或者这只是一个错字?
    • 我想说的是,操作系统使用的分页模型和硬件使用的模型通常是不同的概念。 Linux 在内部使用统一的分页模型,但这是在硬件的分页模型之上分层的,并且需要特定于体系结构的 hack 才能使其工作。硬件模型决定了地址转换的实际发生方式(因为是 MMU 执行此操作)。 Linux 只是在上面添加了一层间接。在它的内心深处,它仍然使用 10:10:12 模型。
    • 很遗憾,您提供的链接不起作用。虽然经过一点努力,我认为我在这里找到了正确的系统信息库:developer.intel.com/products/processor/manuals/index.htm
    • 请注意,在 32 位 x86 上也可以使用大页面:在大页面的情况下,页面目录入口直接指向页面框架。这意味着虚拟地址的最后 22 位是页框内的偏移量 - x86 巨页因此大小为 4M。
    【解决方案2】:

    总的来说,这是非常正确的。

    如果没记错的话,有些细节有点不对劲:

    1. 内核内存的分页不会因每个进程而改变,因此内核始终可以看到所有页表。
    2. 理论上,还有一个基于句段的翻译步骤。然而,大多数实际系统(例如,*BSD、Linux、Windows、OS/X)使用的段的基数设置为 0,限制设置为地址空间限制,因此这一步本质上是一个 NOP。

    【讨论】:

    • 回应你的第一点:你的意思是说每个进程的内核地址空间总是包含相同的信息吗?因此,每个进程的所有页表都存储在每个虚拟地址空间中(但每个进程的内核地址空间映射到相同的物理帧)?
    • @Jimmy:不,页表不一定在每个虚拟地址空间中。不过,它们将 all 对内核直接可见。要切换到特定的地址空间,该地址空间的页表必须对内核可见。
    • 感谢您的澄清;尽管我看不出这如何使我原来的三个陈述中的任何一个无效。我只是没有提到内核可以看到每个单独的地址空间这一事实。
    • 在 OpenWall Linux 上使用(是?)段限制来提供不可执行的堆栈(证明规则的例外,如果你愿意的话)。