x86-64架构:内存分页机制

925 阅读5分钟

分页是一种内存管理机制,该机制把线性地址(虚拟地址)切分成页并映射为物理地址,且只能在保护模式下使用(CR0.PE = 1)。开启分页后,从应用程序视角,看到的是线性地址(虚拟地址)空间,而不是物理地址。分页机制涉及到线性地址转换、访问权限控制以及缓存的使用和管理。本篇文章主要介绍线性地址到物理地址的转换过程,不会涉及到访问权限控制和缓存的使用。

一、分页的开启与关闭

分页只能在保护模式(CR0.PE = 1)下使用。在保护模式下,是否开启分页,由 CR0. PG 位(位 31)决定:

  • 当 CR0.PG = 0 时,未开启分页,线性地址等同于物理地址;
  • 当 CR0.PG = 1 时,开启分页。
CR0.png

二、四种分页模式

Intel-64 处理器支持 4 种分页模式:

  • 32 位分页
  • PAEPhysical Address Extention) 分页
  • 4 级分页
  • 5 级分页

处理器当前处于哪种分页模式,由 CR4.PAE, CR4.LA57 以及 IA32_EFER.LME 联合决定:

  • 如果 CR4.PAE = 0, 使用的是 32位分页模式。
  • 如果 CR4.PAE = 1 且 IA32_EFER.LME = 0,使用的是 PAE 分页模式
  • 如果 CR4.PAE = 1, IA32_EFER.LME = 1 且 CR4.LA57 = 0,使用的是 4 级分页模式。
  • 如果 CR4.PAE = 1, IA32_EFER.LME = 1 且 CR4.LA57 = 1,使用的是 5 级分页模式。

这些标志位的说明如下所示:

CR4.png

CR4.PAE

​ 物理地址扩展标志(Physical Address Extension),CR4 寄存器第 5 位。当设置时,允许分页时产生大于 32 位的物理地址;清除时,物理地址被限制为 32 位。该控制位必须在进入 IA-32e 模式前设置。

CR4.LA57

​ 57 位线性地址标志(57-bit linear addresses),CR4 寄存器第 12 位。只用于 IA-32e 模式来区分分页模式。当设置时,处理器使用 5级分页来转换 57 位线性地址;清除时,处理器使用 4 级分页来转换 48 位线性地址。

IA32_EFER.png IA32_EFER_detail.png

不同的分页模式,其支持的线性地址宽度、物理地址宽度和页大小也是同的,其对应关系如下:

分页模式CR0.PGCR4.PAEIA32_EFER.LMECR4.LA57线性地址宽度(位)物理地址宽度(位)页大小
32位100N/A32最大 404 KB
4 MB
PAE110N/A32最大 524 KB
2 MB
4 级分页111048最大 524 KB
2 MB
1 GB
5 级分页111157最大 524 KB
2 MB
1 GB

不同型号的处理器,所支持的物理地址和线性地址宽度也不相同,处理器提供了cpuid 指令来查询 CPU 信息。Linux 系统下,有个同名的 shell 命令(需要单独安装),可用来查看当前处理器信息,包括所支持的地址宽度。在我的 Ubuntu 虚拟机上,使用 cpuid 命令,查看结果如下:

$ cpuid|grep address
      physical address extensions            = true
      maximum physical address bits         = 0x27 (39)
      maximum linear (virtual) address bits = 0x30 (48)

可以看到,该 CPU 支持最大 39 位物理地址,以及最大 48 位虚拟地址。

32 位分页和 PAE 分页只能在 32 位保护模式(IA32_EFER.LME = 0)下使用,只能转换 32 位的线性地址。 本文不会对这两种分页模式进行讨论

相对的,4 级和 5 级分页,只能在 IA-32e 模式(IA32_EFER.LME = 1)下使用。 IA-32e 模式有两种子模式:

  • 兼容模式。这种子模式下,只使用 32 位的线性地址; 4 级和 5 级分页把线性地址中的位 63:32 全部当做 0 来看待。
  • 64 位模式。这种子模式下,能够使用 64 位的线性地址。但由于 4 级分页只支持 48 位线性地址(5 级分页支持 57 位),所以 4 级分页线性地址的 63:47 位,5 级分页线性地址的 63:57 位,均未使用。在 64 位模式下,处理器要求线性地址必须是 canonical 的,即这些冗余位应该是一致的,要么全是 0,要么全是 1。

关于canonical 地址的说明,请参考下文:

3.3.7.1 Canonical Addressing

In 64-bit mode, an address is considered to be in canonical form if address bits 63 through to the most-significant

implemented bit by the microarchitecture are set to either all ones or all zeros.

Intel 64 architecture defines a 64-bit linear address. Implementations can support less. The first implementation of

IA-32 processors with Intel 64 architecture supports a 48-bit linear address. This means a canonical address must

have bits 63 through 48 set to zeros or ones (depending on whether bit 47 is a zero or one).

Although implementations may not use all 64 bits of the linear address, they should check bits 63 through the

most-significant implemented bit to see if the address is in canonical form. If a linear-memory reference is not in

canonical form, the implementation should generate an exception.

简单翻译下:

​ 在 64 位模式下,虽然支持 64 位线性地址,但由于 64位线性地址太大了,允许实际使用的位数小于 64 位。其余未用到的位,要么全是 0,要么全是 1。例如,线性地址只使用了 48 位,那么从 63 到 48 位,需要是全 0 或者全 1(取决于第 47位是 0 还是 1,第 47 位符号扩展到63位)。处理器会检查线性地址是否符合这个要求,不符合要求就会报异常。

在 4 级分页的 Linux 内核中,用户空间的虚拟地址范围 0x0000000000000000 - 0x00007fffffffffff(47位),内核使用的虚拟地址范围 0xffff800000000000 - 0xffffffffffffffff(47位),都是满足 canonical 类型地址要求的。中间的的空洞部分,不满足 canonical 地址要求,未使用。

memory-layout.png

三、层级分页结构

上述 4 种分页模式,都使用了层级分页结构每个页结构的大小为 4096 字节,由多个项组成。在 32 位分页模式下,每一项大小为 4 字节(32位),每个页结构包含 1024 项;在 4 级 或 5 级分页模式下,每一项大小为 8 字节(64位),每个页结构包含 512 项。 PAE 分页模式中有个例外情况,使用了大小为 32 个字节的页结构,该页结构由 4 个 8 字节(64 位)的项组成。

从功能上来说,线性地址可分为 2 个部分。线性地址的高位部分(称为页号,page number),用来识别一系列页结构项。这些项中的最后一个,用来标识线性地址转换后的内存区域的物理地址(称为页帧page frame)。线性地址的低位部分(称为页偏移量page offset),标识了线性地址转换后的内存区域内的特定地址。

总的来说,线性地址的高位部分,决定了物理地址的高位部分;线性地址的低位部分,决定了物理地址的低位部分。而页的大小,决定了页号(page number)和页偏移量(page offset)的边界。

每一个页结构项都包含一个物理地址,该物理地址要么是另一个页结构项的地址,要么是一个页帧的地址。对于第一种情况,我们说该页结构项引用了另一个页结构项;对于后者,我们说该页结构项映射了一个页

不论哪种分页模式,第一个页结构(根页结构)的物理地址都会被保存在 CR3 寄存器中。然后,使用以下迭代过程来进行线性地址转换:使用线性地址的一部分(刚开始时使用最高位部分)定位到页结构(刚开始时使用保存在 CR3 寄存器中的地址)中的一项。如果该项又引用了另一个页结构项,那么使用被引用项和线性地址的剩余部分,继续该过程。如果该项映射到了一个页,那么转换过程完成:该项包含的物理地址即为页帧,线性地址的剩余部分就是页内偏移量。

4 级 和 5 级分页模式下(以 4KB 页为例),转换过程概述如下:

  • 在 4 级分页下,每个页结构由 512 (292^9)项组成,每次转换使用 48 位线性地址中的 9 位。位 47:39 标识了第一个页结构项,位 38:30 标识了第二个;位 29:21 标识了第三个;位 20:12 标识了第四个。注意,最后一个页结构项标识了页帧。
  • 5 级分页跟 4 级类似,只不过 5 级分页的线性地址是 57 位的。位 56:48 标识了第一个页结构项,剩余位用于 4 级分页。

上述示例中,最后一个页结构项映射了一个 4KB 的页,线性地址的低 12 位作为页内偏移。但情况并非总是如此,因为除了 4KB 大小的页,处理器还支持其它尺寸的页。比如,在 4 级 和 5 级分页下,支持 4KB、2MB 及 1GB 大小的页。页结构项的 PS (page size)位,决定了该项是否映射到页,同时也决定了页的大小:

  • 如果线性地址剩余的位数超过 12,则参考当前页结构项的位 7(PS — page size)。如果该位为 0,该项引用了另一个页结构;如果为 1,该项映射了页。

  • 如果线性地址只剩余 12 位,当前页结构项总是映射到一个页(位 7 被用作其它用途)。

在转换过程中,每一层页结构被赋予不同的名称。下表提供了不同页结构的名称。同时也提供了其物理地址的来源(CR3 或 不同的页结构项)、线性地址中用来选择页结构项的位、该项是否以及如何映射到一个页。

page_structure_in_diff_page_mode.png

四、4 级分页的线性地址转换过程

4.1 转换过程概览

4 级分页下,可以把线性地址映射到 4KB 、2MB 或者 1GB 大小的页。

不同大小的页,其转换过程图如下所示。

线性地址转换到 4KB 的页:

4L-4K.png

线性地址转换到 2MB 的页:

4L-2M.png

线性地址转换到 1GB 的页:

4L-1G.png

使用 4 级或 5 级分页时,CR3 寄存器格式如下所示,其中位 M-1:12 保存着根页结构的物理地址 :

CR3.png

注:M 是 MAXPHYADDR 的缩写,表示处理器支持的最大物理地址位数,该值最大为 52。

4.2 转换过程解析

PML4 和 PML4E

CR3 寄存器的 51:12 位(共 40 位),保存着 PML4 表的物理地址,该地址是 4KB 对齐的。PML4 表由 512 个 64 位( 8 字节)的项(PML4E)组成,也就是说每个 PML4 表的大小为 512×8=4KB512 \times 8 = 4KB

线性地址的 47:39 位(共 9 位,能够标识 512 个数字),用作查找 PML4E (4 级页目录项)的索引。

PML4E 通过以下步骤定位:

  1. 从 CR3 寄存器的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到 4 级页目录的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
  2. 以线性地址的 47:39 位,作为 PML4E 在 PML4 表中的索引
  3. 由于每个 PML4E 是 8 字节大小,把上一步获取到的索引值乘以 8 ,得到该 PML4E 对于 4 级页目录的偏移字节量
  4. 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PML4E 的物理地址。

注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。

PML4E 使用线性地址的 47:39 位来标识,它控制着线性地址的 512GB (2392^{39})的空间。

PML4_LA_1.png

计算后的物理地址格式:

  • 位 51:12 来自于 CR3寄存器
  • 位 11:3 来自于线性地址的 47:39 位
  • 位 2:0 位全是 0
PML4_LA_2.png

PML4E 格式

PML4E 大小为 8 字节,其格式如下所示:

PML4E_format.png

各字段说明如下:

PML4E_table.png

我们主要关注 P 位(位 0)、PS 位(位 7),物理地址位(位 M-1:12):

  • P 位(Present,存在位)标识当前页结构是否已经加载到内存;如果为 0,将触发 Page-Fault 异常。
  • PS (Page Size)位,如果为 1 表示当前项直接映射到页;如果为 0,表示该页结构项引用了另一个页结构项。在 PML4E 里,该字段必须为 0。
  • 物理地址位(位 M-1:12),M 代表 MAXPHYADDR,即当前处理器支持的最大物理地址位数,该值最大为 52。

其余字段中,大部分是用于访问权限控制的,本文不做详细介绍。

Page-Directory-Pointer Table

PML4E 的 51:12 位,保存着页目录指针表( page-directory-pointer table)的基地址,该地址是 4KB 对齐的。每个页目录指针表由 512 个 64 位(8字节)的项(PDPTE)组成,也就是说每个页目录指针表(PDPT)的大小为 512×8=4KB512 \times 8 = 4KB

线性地址的 38:30 位(共 9 位,能够标识 512 个数字),用作查找 PDPTE (页目录指针项)的索引。

定位 PDPTE

PDPTE 通过以下步骤定位:

  1. 从 PML4E 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页目录指针表(PDPT)的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
  2. 以线性地址的 38:30 位,作为 PDPTE 在 PDPT 表中的索引
  3. 由于每个 PDPTE 是 8 字节大小,把第 2 步获取到的索引值乘以 8 ,得到该 PDPTE 在页目录指针表中的偏移字节量
  4. 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PDPTE 的物理地址。

注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。

PDPTE 使用线性地址的 38:30 位来标识,它控制着线性地址的 1GB (2302^{30})的空间。

PML4_LA_3.png

计算后的物理地址格式:

  • 位 51:12 来自于 PML4E
  • 位 11:3 来自于线性地址的 38:30 位
  • 位 2:0 位全是 0
PML4_LA_4.png

PDPTE 大小为 8 字节,其使用依赖于它的 PS 位(第 7 位)。

PDPTE 映射到 1GB

  • 当 PS(Page Size) 位为 1 时,表示该 PDPTE 项直接映射到 1GB 的页。

    此时 PDPTE 格式如下: PDPTE_for_1G_format.png

    我们主要关注 P 位(位 0)、D 位(位 6)、PS 位(位 7),物理地址位(位 M-1:30):

    • P 位(Present,存在位)标识该项映射的页是否已经加载到内存;如果为 0,将触发 Page-Fault 异常。
    • D 位(Dirty,脏页位),标识该项映射的 1GB 页是否写入了数据。
    • PS (Page Size)位,当映射到页时,此位必需为 1。
    • 物理地址位(位 M-1:30),M 代表 MAXPHYADDR,即当前处理器支持的最大物理地址位数。

    其余字段中,大部分是用于访问权限控制的,本文不做详细介绍。

    因为 PDPTE 映射到页,整个转换流程到达最后一步,此时可以得到线性地址转换后的物理地址,其计算过程如下:

    1. 从 PDPTE 的 51:30 位,获取到 22 位物理地址,左移 30 位后,得到页的基地址。该基地址低 30 位全部为 0,对齐到 1GB 的物理地址;
    2. 从线性地址的 29:0 位,获取到页内偏移量
    3. 把第 1 步得到基地址,加上第 2 步得到的偏移量,得到该线性地址转换后的物理地址。

    完整流程如下图:

PML4_LA_5.png

最终的物理地址格式:

  • 位 51:30 来自于 PDPTE;
  • 位 29:0 来自于原始的线性地址。
PML4_LA_6.png

PDPTE 引用页目录(Page Directory, PD)

  • 当 PS(Page Size) 位为 0 时,PDPTE 的 51:12 位保存的是页目录(Page Directory)的物理地址,该地址是 4KB 对齐的。每个页目录由 512 个 64位的项(PDE)组成。

    此时,PDPTE 的格式如下:

PDPTE_for_PD_format.png

PDE 通过以下步骤定位:

  1. 从 PDPTE 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页目录(PD)的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
  2. 以线性地址的 29:21 位,作为 PDE 在 PD 中的索引
  3. 由于每个 PDE 是 8 字节大小,把上一步获取到的索引值乘以 8 ,得到该 PDE 在页目录中的偏移字节量
  4. 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PDE 的物理地址。

注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。

PDE 使用线性地址的 29:21 位来标识,它控制着线性地址的 2MB (2212^{21})的空间。

PML4_LA_7.png

PDE 物理地址组成:

  • 位 51:12 来自于 PDPTE;
  • 位 11:3 来自于线性地址的 29:21 位
  • 第 2:0 位全是 0
PML4_LA_8.png

PDE 大小为 8 字节,其使用依赖于它的 PS 位(第 7 位)。

PDE 映射到 2MB 页

  • 当 PS(Page Size) 位为 1 时,表示该 PDE 项直接映射到 2MB 的页。

    此时,PDE 的格式如下:

PDE_for_2M_format.png

我们主要关注 P 位(位 0)、D 位(位 6)、PS 位(位 7),物理地址位(位 M-1:21):

  • P 位(Present,存在位)标识该项映射的页是否已经加载到内存;如果为 0,将触发 Page-Fault 异常。
  • D 位(Dirty,脏页位),标识该项映射的 2MB 页是否写入了数据。
  • PS (Page Size)位,当映射到页时,此位必需为 1。
  • 物理地址位(位 M-1:21),M 代表 MAXPHYADDR,即当前处理器支持的最大物理地址位数。

其余字段中,大部分是用于访问权限控制的,本文不做详细介绍。

因为 PDE 映射到页,整个转换流程到达最后一步,可以得到线性地址转换后的物理地址,其计算过程如下:

  1. 从 PDE 的 51:21 位,获取到 31 位物理地址,左移 21 位后,得到页的基地址。该基地址低 21 位全部为 0,对齐到 2MB 的物理地址;

  2. 从线性地址的 21:0 位,获取到页内偏移量

  3. 把第 1 步得到基地址,加上第 2 步得到的偏移量,得到该线性地址转换后的物理地址。

    完整流程如下图:

PML4_LA_9.png

最终的物理地址格式:

  • 位 51:21 来自于 PDE;

  • 位 20:0 来自于原始的线性地址。

PML4_LA_10.png

PDE 引用页表(Page Table, PT)

  • 当 PS(Page Size) 位为 0 时,PDE 的 51:12 位保存的是页表(Page Table)的物理地址,该地址是 4KB 对齐的。每个页表由 512 个 64位的项(Page Table Entry,PTE)组成。

    此时,PDE 的格式如下:

PDE_for_PT_format.png

PTE 通过以下步骤定位:

  1. 从 PDE 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页表(Page Table,PT)的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
  2. 以线性地址的 20:12 位,作为 PTE 在 PT 中的索引
  3. 由于每个 PTE 是 8 字节大小,把上一步获取到的索引值乘以 8 ,得到该 PTE 对于页表的偏移字节量
  4. 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PTE 的物理地址。

注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。

PTE 使用线性地址的 20:12 位来标识,它控制着线性地址的 4KB (2122^{12})的空间。

PML4_LA_11.png

PTE 物理地址组成:

  • 位 51:12 来自于 PDE;
  • 位 11:3 来自于线性地址的 20:12 位
  • 位 2:0 全是 0
PML4_LA_12.png

PTE 映射到 4KB 页

PTE 格式如下:

PTE_format.png

我们主要关注 P 位(位 0)、D 位(位 6),物理地址位(位 M-1:12):

  • P 位(Present,存在位)标识该项映射的页是否已经加载到内存;如果为 0,将触发 Page-Fault 异常。
  • D 位(Dirty,脏页位),标识该项映射的 4KB 页是否写入了数据。
  • PS (Page Size)位,当映射到页时,此位必需为 1。
  • 物理地址位(位 M-1:12),表示该项映射到的 4KB 页的物理地址。M 是 MAXPHYADDR 的缩写,表示当前处理器支持的最大物理地址位数。

因为 PTE 映射到 4KB 页,整个转换流程到达最后一步,可以得到线性地址转换后的物理地址,其计算过程如下:

  1. 从 PTE 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
  2. 从线性地址的 12:0 位,获取到页内偏移量
  3. 把第 1 步得到基地址,加上第 2 步得到的偏移量,得到该线性地址转换后的物理地址。

完整的转换流程如下:

PML4_LA_13.png

最终的物理地址格式:

  • 位 51:12 来自于 PTE;
  • 位 11:0 来自于原始的线性地址。

PML4_LA_14.png

五、异常

正常情况下,当识别出页帧时,转换过程就完成了。但是,当转换过程遇到标识了”不存在“(P 位为 0)的页结构时,或者修改了保留位,转换过程就会提前中止,并触发 page-fault 异常。

4 级和 5级页表中的保留位如下所示:

  • 位 51:MAXPHYADDR 被保留
  • PML5E 或 PML4E 中的 PS 标志位被保留
  • 如果处理器不支持 1-GByte 的页,PDPTE 中的 PS 标志位被保留
  • 如果 PDPTE 中的 PS 标志为 1,该项中的 29:13 位被保留
  • 如果 PDE 中的 PS 标志为 1,该项中的 20:13 位被保留
  • 如果 IA32_EFER.NXE = 0,XD 标志位(第 63 位)被保留

六、参考资料

1、Intel 开发者手册:Intel 64 and IA-32 Architectures Software Developer Manuals Volume 3A, Chapter 4 Paging