106

这个问题旨在填补关于该主题的良好免费信息的空白。

我相信一个好的答案将适合一个大的 SO 答案或至少在几个答案中。

主要目标是为初学者提供足够的信息,以便他们可以自己阅读手册,并能够理解与分页相关的基本操作系统概念。

建议的准则:

  • 答案应该是初学者友好的:
    • 具体但可能简化的示例非常重要
    • 欢迎应用所示概念
  • 引用有用的资源是好的
  • 欢迎对操作系统如何使用分页功能进行一些题外话
  • 欢迎 PAE 和 PSE 解释
  • 对 x86_64 的小题外话是受欢迎的

相关问题以及为什么我认为他们不是骗子:

4

2 回答 2

155

此答案的版本具有不错的 TOC 和更多内容

我将更正报告的任何错误。如果您想进行较大的修改或添加缺失的方面,请根据您自己的答案进行修改以获得当之无愧的代表。可以直接合并小的编辑。

示例代码

最小示例:https ://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S

就像编程中的其他一切一样,真正理解这一点的唯一方法是使用最少的示例。

使这成为“困难”主题的原因在于,最小的示例很大,因为您需要制作自己的小型操作系统。

英特尔手册

尽管没有示例就无法理解,但请尝试尽快熟悉手册。

英特尔在英特尔手册第 3 卷系统编程指南 - 325384-056US 2015 年 9 月第 4 章“分页”中描述了分页。

特别有趣的是图 4-4“32 位分页的 CR3 格式和分页结构条目”,它给出了关键数据结构。

MMU

分页由 CPU 的内存管理单元(MMU) 部分完成。与许多其他(例如x87 协处理器APIC)一样,这在早期是由单独的芯片组成的,后来被集成到 CPU 中。但该术语仍在使用。

普遍事实

逻辑地址是“常规”用户空间代码中使用的内存地址(例如rsiin的内容mov eax, [rsi])。

首先分段将它们转换为线性地址,然后分页将线性地址转换为物理地址。

(logical) ------------------> (linear) ------------> (physical)
             segmentation                 paging

大多数时候,我们可以将物理地址视为实际 RAM 硬件内存单元的索引,但这并不是 100% 正确的,因为:

分页仅在保护模式下可用。在保护模式下使用分页是可选的。PG如果设置了寄存器的位,则分页打开cr0

分页与分段

分页和分段之间的一个主要区别是:

  • 分页将 RAM 分成大小相等的块,称为页面
  • 分段将内存分成任意大小的块

这是分页的主要优点,因为相同大小的块使事情更易于管理。

分页变得如此流行,以至于在 x86-64 中以 64 位模式(新软件的主要操作模式)中放弃了对分段的支持,它只存在于模仿 IA32 的兼容模式中。

应用

分页用于在现代操作系统上实现进程虚拟地址空间。使用虚拟地址,操作系统可以通过以下方式在单个 RAM 上容纳两个或多个并发进程:

  • 两个程序都不需要对另一个程序一无所知
  • 两个程序的内存都可以根据需要增长和缩小
  • 程序之间的切换非常快
  • 一个程序永远无法访问另一个进程的内存

历史上,分页出现在分段之后,并在很大程度上取代了它以实现现代操作系统(如 Linux)中的虚拟内存,因为它更容易管理固定大小的内存块而不是可变长度的段。

硬件实现

就像保护模式下的分段(修改段寄存器会触发 GDT 或 LDT 的加载)一样,分页硬件使用内存中的数据结构来完成其工作(页表、页目录等)。

这些数据结构的格式由硬件固定,但由操作系统正确设置和管理 RAM 上的这些数据结构,并告诉硬件在哪里找到它们(通过cr3)。

其他一些体系结构几乎完全将分页留在软件手中,因此 TLB 未命中会运行操作系统提供的函数来遍历页表并将新映射插入 TLB。这使得页表格式由操作系统选择,但使得硬件不太可能像 x86 那样将页遍历与其他指令的乱序执行重叠

示例:简化的单级分页方案

这是一个分页如何在 x86 架构的简化版本上运行以实现虚拟内存空间的示例。

页表

操作系统可以为他们提供以下页表:

操作系统为进程 1 提供的页表:

RAM location        physical address   present
-----------------   -----------------  --------
PT1 + 0       * L   0x00001            1
PT1 + 1       * L   0x00000            1
PT1 + 2       * L   0x00003            1
PT1 + 3       * L                      0
...                                    ...
PT1 + 0xFFFFF * L   0x00005            1

操作系统为进程 2 提供的页表:

RAM location       physical address   present
-----------------  -----------------  --------
PT2 + 0       * L  0x0000A            1
PT2 + 1       * L  0x0000B            1
PT2 + 2       * L                     0
PT2 + 3       * L  0x00003            1
...                ...                ...
PT2 + 0xFFFFF * L  0x00004            1

在哪里:

  • PT1PT2: 表 1 和 2 在 RAM 上的初始位置。

    样本值:0x000000000x12345678等。

    决定这些值的是操作系统。

  • L: 页表条目的长度。

  • present: 表示页面存在于内存中。

页表位于 RAM 上。例如,它们可以位于:

--------------> 0xFFFFFFFF


--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1


--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2

--------------> 0x0

两个页表在 RAM 上的初始位置是任意的,由操作系统控制。由操作系统来确保它们不重叠!

每个进程都不能直接接触任何页表,尽管它可以向操作系统发出导致页表被修改的请求,例如请求更大的堆栈或堆段。

一个页面是一个 4KB(12 位)的块,由于地址有 32 位,因此标识每个页面只需要 20 位(20 + 12 = 32,因此 5 个十六进制字符)。该值由硬件固定。

页表条目

页表是……页表条目的表!

表条目的确切格式由硬件确定。

在这个简化的示例中,页表条目仅包含两个字段:

bits   function
-----  -----------------------------------------
20     physical address of the start of the page
1      present flag

所以在这个例子中,硬件设计者可以选择L = 21.

大多数真实的页表条目都有其他字段。

将事物对齐为 21 位是不切实际的,因为内存可以按字节而不是按位寻址。因此,即使在这种情况下只需要 21 位,硬件设计人员也可能会选择L = 32加快访问速度,而只保留剩余的位以供以后使用。x86 上的实际值L是 32 位。

单级方案中的地址转换

一旦操作系统设置了页表,线性地址和物理地址之间的地址转换就由硬件完成。

当操作系统想要激活进程 1 时,它会将 设置cr3PT1,即进程 1 的表的开头。

如果进程 1 想要访问线性地址0x00000001,分页硬件电路会自动为操作系统执行以下操作:

  • 将线性地址分成两部分:

    | page (20 bits) | offset (12 bits) |
    

    所以在这种情况下,我们会有:

    • 页 = 0x00000
    • 偏移量 = 0x001
  • 查看第 1 页,因为cr3指向它。

  • 看条目0x00000,因为那是页面部分。

    硬件知道该条目位于 RAM 地址PT1 + 0 * L = PT1

  • 因为它存在,所以访问是有效的

  • 通过页表,页码的位置0x000000x00001 * 4K = 0x00001000

  • 要找到最终的物理地址,我们只需要添加偏移量:

      00001 000
    + 00000 001
      -----------
      00001 001
    

    因为00001是在表上查找的页的物理地址,001是偏移量。

    顾名思义,偏移量总是简单地添加页面的物理地址。

  • 然后硬件在该物理位置获取内存。

同样,进程 1 会发生以下翻译:

linear     physical
---------  ---------
00000 002  00001 002
00000 003  00001 003
00000 FFF  00001 FFF
00001 000  00000 000
00001 001  00000 001
00001 FFF  00000 FFF
00002 000  00002 000
FFFFF 000  00005 000

例如,当访问 address 时00001000,page 部分是00001硬件知道它的页表条目位于 RAM 地址:(PT1 + 1 * L因为1page 部分),它会在那里寻找它。

当操作系统要切换到进程 2 时,它只需要cr3指向第 2 页即可。就是这么简单!

现在,进程 2 将发生以下翻译:

linear     physical
---------  ---------
00000 002  00001 002
00000 003  00001 003
00000 FFF  00001 FFF
00001 000  00000 000
00001 001  00000 001
00001 FFF  00000 FFF
00003 000  00003 000
FFFFF 000  00004 000

对于不同的进程,相同的线性地址转换为不同的物理地址,这仅取决于内部的值cr3

通过这种方式,每个程序都可以期望其数据从 开始0和结束FFFFFFFF,而不必担心确切的物理地址。

页面错误

如果进程 1 尝试访问不存在的页面内的地址怎么办?

硬件通过页面错误异常通知软件。

然后通常由操作系统注册一个异常处理程序来决定必须做什么。

访问不在表上的页面可能是编程错误:

int is[1];
is[2] = 1;

但在某些情况下它是可以接受的,例如在 Linux 中:

  • 程序想要增加它的堆栈。

    它只是尝试访问给定可能范围内的某个字节,如果操作系统满意,它将将该页面添加到进程地址空间。

  • 该页面已交换到磁盘。

    操作系统将需要在进程后面做一些工作才能将页面放回 RAM。

    操作系统可以根据页表条目的其余部分的内容来发现这种情况,因为如果当前标志被清除,则页表条目的其他条目完全留给操作系统去做它想要的。

    例如,在 Linux 上,当存在 = 0 时:

    • 如果页表项的所有字段都为0,则地址无效。

    • 否则,页面已被交换到磁盘,这些字段的实际值编码了页面在磁盘上的位置。

在任何情况下,操作系统都需要知道哪个地址产生了页面错误才能处理问题。cr2这就是为什么好的 IA32 开发人员在页面错误发生时将值设置为该地址的原因。然后异常处理程序可以查看cr2以获取地址。

简化

使这个例子更容易理解的对现实的简化:

  • 所有真正的寻呼电路都使用多级寻呼来节省空间,但这显示了一个简单的单级方案。

  • 页表只包含两个字段:一个 20 位地址和一个 1 位存在标志。

    实际页表总共包含 12 个字段,因此省略了其他功能。

示例:多级分页方案

单级分页方案的问题在于它会占用过多的 RAM:每个进程 4G / 4K = 1M 条目。如果每个条目是 4 字节长,那么每个进程4M ,即使对于台式计算机来说也太多了:ps -A | wc -l说我现在正在运行 244 个进程,所以这将占用我大约 1GB 的 RAM!

出于这个原因,x86 开发人员决定使用减少 RAM 使用的多级方案。

该系统的缺点是访问时间稍长。

在用于没有 PAE 的 32 位处理器的简单 3 级分页方案中,32 个地址位划分如下:

| directory (10 bits) | table (10 bits) | offset (12 bits) |

每个进程必须有一个且只有一个与之关联的页面目录,因此它将至少包含2^10 = 1K页面目录条目,这比单级方案所需的最小 1M 要好得多。

页表仅根据操作系统的需要进行分配。每个页表都有2^10 = 1K页目录项

页面目录包含...页面目录条目!页目录项与页表项相同,只是它们指向页表的 RAM 地址而不是表的物理地址。由于这些地址只有 20 位宽,因此页表必须位于 4KB 页的开头。

cr3现在指向当前进程的页目录在 RAM 上的位置,而不是页表。

页表条目与单级方案完全不同。

页表从单级方案更改,因为:

  • 每个进程最多可以有 1K 页表,每页目录条目一个。
  • 每个页表正好包含 1K 条目而不是 1M 条目。

在前两个级别上使用 10 位(而不是,比如说,12 | 8 | 12)的原因是每个页表条目都是 4 个字节长。然后页面目录和页面表的 2^10 个条目将很好地适合 4Kb 页面。这意味着为此目的分配和取消分配页面更快、更简单。

多级方案中的地址转换

操作系统给进程 1 的页面目录:

RAM location     physical address   present
---------------  -----------------  --------
PD1 + 0     * L  0x10000            1
PD1 + 1     * L                     0
PD1 + 2     * L  0x80000            1
PD1 + 3     * L                     0
...                                 ...
PD1 + 0x3FF * L                     0

PT1 = 0x10000000操作系统在( 0x10000* 4K)处为进程 1 提供的页表:

RAM location      physical address   present
---------------   -----------------  --------
PT1 + 0     * L   0x00001            1
PT1 + 1     * L                      0
PT1 + 2     * L   0x0000D            1
...                                  ...
PT1 + 0x3FF * L   0x00005            1

PT2 = 0x80000000操作系统在( 0x80000* 4K)处为进程 1 提供的页表:

RAM location      physical address   present
---------------   -----------------  --------
PT2 + 0     * L   0x0000A            1
PT2 + 1     * L   0x0000C            1
PT2 + 2     * L                      0
...                                  ...
PT2 + 0x3FF * L   0x00003            1

在哪里:

  • PD1: 进程 1 的页目录在 RAM 上的初始位置。
  • PT1PT2: RAM 上进程 1 的页表 1 和页表 2 的初始位置。

所以在这个例子中,页面目录和页表可以存储在 RAM 中,如下所示:

----------------> 0xFFFFFFFF


----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2

----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1


----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1

----------------> 0x0

0x00801004让我们一步一步翻译线性地址。

我们假设cr3 = PD1,即它指向刚才描述的页面目录。

在二进制中,线性地址是:

0    0    8    0    1    0    0    4
0000 0000 1000 0000 0001 0000 0000 0100

分组为10 | 10 | 12

0000000010 0000000001 000000000100
0x2        0x1        0x4

这使:

  • 页面目录条目 = 0x2
  • 页表条目 = 0x1
  • 偏移量 = 0x4

因此硬件会查找页面目录的条目 2。

页目录表表示页表位于0x80000 * 4K = 0x80000000. 这是进程的第一次 RAM 访问。

由于页表条目是0x1,硬件查看页表的条目 1 at 0x80000000,它告诉它物理页位于地址0x0000C * 4K = 0x0000C000。这是该进程的第二次 RAM 访问。

最后,分页硬件加上偏移量,最终地址为0x0000C004

翻译地址的其他示例是:

linear    10 10 12 split   physical
--------  ---------------  ----------
00000001  000 000 001      00001001
00001001  000 001 001      page fault
003FF001  000 3FF 001      00005001
00400000  001 000 000      page fault
00800001  002 000 001      0000A001
00801008  002 001 008      0000C008
00802008  002 002 008      page fault
00B00001  003 000 000      page fault

如果页目录项或页表项不存在,则会发生页错误。

如果操作系统想要同时运行另一个进程,它会给第二个进程一个单独的页目录,并将该目录链接到单独的页表。

64 位架构

对于当前的 RAM 大小来说,64 位仍然是太多的地址,因此大多数架构将使用更少的位。

x86_64 使用 48 位 (256 TiB),而传统模式的 PAE 已经允许 52 位地址 (4 PiB)。

这 48 位中的 12 位已经为偏移量保留,剩下 36 位。

如果采用 2 级方法,最好的分割是两个 18 位级别。

但这意味着页面目录将包含2^18 = 256K条目,这将占用太多 RAM:接近 32 位体系结构的单级分页!

因此,64 位架构会创建更多的页面级别,通常为 3 或 4。

x86_64 在一个9 | 9 | 9 | 12方案中使用了 4 个级别,因此上层只占用2^9更高级别的条目。

PAE

物理地址扩展。

使用 32 位,只能寻址 4GB RAM。

这开始成为大型服务器的限制,因此英特尔将 PAE 机制引入 Pentium Pro。

为了缓解这个问题,Intel 增加了 4 条新的地址线,这样 64GB 就可以被寻址了。

如果 PAE 打开,页表结构也会改变。更改它的确切方式取决于 PSE 是打开还是关闭。

PAEPAE 通过 的位打开和关闭cr4

即使总可寻址内存为 64GB,单个进程仍然最多只能使用 4GB。然而,操作系统可以将不同的进程放在不同的 4GB 块上。

PSE

页面大小扩展。

允许页面长度为 4M(如果 PAE 开启,则为 2M)而不是 4K。

PAEPSE 通过 的位打开和关闭cr4

PAE 和 PSE 页表方案

如果 PAE 和 PSE 中的任何一个处于活动状态,则使用不同的寻呼级别方案:

  • 没有 PAE 和没有 PSE:10 | 10 | 12

  • 没有 PAE 和 PSE 10 | 22:。

    22 是 4Mb 页面内的偏移量,因为 22 位地址为 4Mb。

  • PAE 和无 PSE:2 | 9 | 9 | 12

    使用 9 而不是 10 的设计原因是现在条目不能再放入 32 位,这些都被 20 个地址位和 12 个有意义或保留的标志位填充。

    原因是 20 位不再足以表示页表的地址:现在需要 24 位,因为向处理器添加了 4 条额外的线。

    因此,设计者决定将条目大小增加到 64 位,为了使它们适合单个页表,有必要将条目数减少到 2^9 而不是 2^10。

    开头的 2 是一个新的 Page 级别,称为 Page Directory Pointer Table (PDPT),因为它指向页目录并填写 32 位线性地址。PDPT 也是 64 位宽。

    cr3现在指向 PDPT,它必须在前四个 4GB 内存上并在 32 位倍数上对齐以提高寻址效率。这意味着现在cr3有 27 个有效位而不是 20:32 个倍数的 2^5 * 2^27 来完成前 4GB 的 2^32。

  • PAE 和 PSE:2 | 9 | 21

    设计师决定保留一个 9 位宽的字段以使其适合单个页面。

    这留下了 23 位。为 PDPT 留下 2 以保持与没有 PSE 的 PAE 情况一致,留下 21 用于偏移,这意味着页面是 2M 宽而不是 4M。

TLB

Translation Lookahead Buffer (TLB) 是用于分页地址的高速缓存。

由于它是一个缓存,它共享许多 CPU 缓存的设计问题,例如关联性级别。

本节将描述一个具有 4 个单地址条目的简化的全关联 TLB。请注意,与其他缓存一样,真正的 TLB 通常不是完全关联的。

基本操作

在线性地址和物理地址之间发生转换后,它被存储在 TLB 上。例如,一个 4 条目的 TLB 以以下状态开始:

  valid   linear   physical
  ------  -------  ---------
> 0       00000    00000
  0       00000    00000
  0       00000    00000
  0       00000    00000

>指示要替换的当前条目。

在页面线性地址00003转换为物理地址00005之后,TLB变为:

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
> 0       00000    00000
  0       00000    00000
  0       00000    00000

在对其进行第二次翻译后00007变为00009

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
  1       00007    00009
> 0       00000    00000
  0       00000    00000

现在如果00003需要再次翻译,硬件首先查找 TLB 并通过单个 RAM 访问找到它的地址00003 --> 00005

当然,00000它不在 TLB 上,因为没有有效的条目包含00000作为键。

更换政策

当 TLB 被填满时,旧地址将被覆盖。就像 CPU 缓存一样,替换策略是一个潜在的复杂操作,但一个简单而合理的启发式方法是删除最近最少使用的条目 (LRU)。

使用 LRU,从状态开始:

  valid   linear   physical
  ------  -------  ---------
> 1       00003    00005
  1       00007    00009
  1       00009    00001
  1       0000B    00003

添加0000D -> 0000A将给出:

  valid   linear   physical
  ------  -------  ---------
  1       0000D    0000A
> 1       00007    00009
  1       00009    00001
  1       0000B    00003

凸轮

使用 TLB 可以加快翻译速度,因为初始翻译每个 TLB 级别需要一次访问,这意味着在简单的 32 位方案上为 2,但在 64 位架构上为 3 或 4。

TLB 通常作为一种昂贵的 RAM 实现,称为内容可寻址存储器 (CAM)。CAM 在硬件上实现了关联映射,即一个给定键(线性地址)的结构,检索一个值。

映射也可以在 RAM 地址上实现,但 CAM 映射可能需要比 RAM 映射少得多的条目。

例如,一张地图,其中:

  • 键和值都有 20 位(简单分页方案的情况)
  • 每次最多需要存储4个值

可以存储在具有 4 个条目的 TLB 中:

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
FFFFF    00000

但是,要使用 RAM 实现这一点,需要 2^20 个地址

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
... (from 00011 to FFFFE)
FFFFF    00000

这将比使用 TLB 更昂贵。

无效条目

cr3更改时,所有 TLB 条目都将失效,因为将使用新进程的新页表,因此任何旧条目都不太可能有任何意义。

x86 还提供了invlpg显式使单个 TLB 条目无效的指令。其他架构为无效的 TLB 条目提供了更多指令,例如使给定范围内的所有条目无效。

一些 x86 CPU 超出了 x86 规范的要求,并且在修改页表条目和使用它之间提供了比它所保证的更多的一致性,当它尚未缓存在 TLB 中时。显然,Windows 9x 依靠它来确保正确性,但现代 AMD CPU 不提供连贯的页面遍历。英特尔 CPU 会这样做,即使它们必须检测到错误推测才能这样做。利用这一点可能是个坏主意,因为可能不会有太多收获,而且很有可能导致难以调试的微妙时序敏感问题。

Linux内核使用

Linux 内核广泛使用 x86 的分页功能,以允许快速的进程切换和小数据碎片。

v4.2中,看下arch/x86/

  • include/asm/pgtable*
  • include/asm/page*
  • mm/pgtable*
  • mm/page*

似乎没有定义结构来表示页面,只有宏:include/asm/page_types.h特别有趣。摘抄:

#define _PAGE_BIT_PRESENT   0   /* is present */
#define _PAGE_BIT_RW        1   /* writeable */
#define _PAGE_BIT_USER      2   /* userspace addressable */
#define _PAGE_BIT_PWT       3   /* page write through */

arch/x86/include/uapi/asm/processor-flags.h定义CR0,特别是PG位位置:

#define X86_CR0_PG_BIT      31 /* Paging */

参考书目

自由的:

  • rutgers-pxk-416章节“内存管理:讲义”

    对旧操作系统使用的内存组织技术的良好历史回顾。

非免费:

  • bovet05章节“内存寻址”

    x86 内存寻址的合理介绍。缺少一些好的和简单的例子。

于 2013-08-25T16:42:02.967 回答
26

这是一个非常简短的高级答案:

x86 处理器以几种可能的模式之一运行(大致:真实、受保护、64 位)。每种模式都可以使用几种可能的内存寻址模型中的一种(但不是每种模式都可以使用每种模型),即:实模式寻址、分段寻址和平面线性寻址。

在现代世界中,只有受保护模式或 64 位模式下的平面线性寻址是相关的,这两种模式本质上是相同的,主要区别在于机器字的大小以及可寻址的内存量。

现在,内存寻址模式为机器指令的内存操作数赋予了意义(例如mov DWORD PTR [eax], 25,它将值 25 的 32 位(又名dword)整数存储到其地址存储在eax32 位寄存器中的内存中)。在平面线性寻址中,这个数字eax允许在一个连续的范围内运行,从零到最大值(在我们的例子中是 2 32  - 1)。

但是,平面线性寻址可以是分页的,也可以是不分页的。没有分页,地址直接指物理内存。通过分页,处理器的内存管理单元(或 MMU)透明地将所需地址(现在称为虚拟地址)输入查找机制,即所谓的页表,并获得一个新值,该值被解释为物理地址。原始操作现在在物理内存中这个新的、已转换的地址上运行,即使用户只看到虚拟地址。

分页的主要好处是页表由操作系统管理。因此操作系统可以任意修改和替换页表,例如在“切换任务”时。它可以保留一个完整的页表集合,每个“进程”一个,并且每当它决定一个特定进程将在给定的 CPU 上运行时,它将进程的页表加载到该 CPU 的 MMU 中(每个 CPU 都有自己的页表集)。结果是每个进程看到自己的虚拟地址空间,无论操作系统必须为其分配内存时哪些物理页面是空闲的,它看起来都是一样的。它永远不知道任何其他进程的内存,因为它不能直接访问物理内存。

页表是存储在普通内存中的嵌套树状数据结构,由操作系统写入但由硬件直接读取,因此格式是固定的。通过设置一个特殊的 CPU 控制寄存器指向顶级表,它们被“加载”到 MMU 中。CPU 使用称为 TLB 的缓存来记住查找,因此对相同的几个页面的重复访问比分散访问要快得多,因为 TLB 未命中原因以及通常的数据缓存原因。经常看到术语“TLB 条目”用于指代页表条目,即使它们没有缓存在 TLB 中。

如果您担心某个进程可能只是禁用分页或尝试修改页表:这是不允许的,因为 x86 实现了特权级别(称为“环”),并且用户代码以太低的特权级别执行,不允许它来修改CPU的页表。

于 2013-08-25T18:29:56.107 回答