分页是一种内存管理机制,该机制把线性地址(虚拟地址)切分成页并映射为物理地址,且只能在保护模式下使用(CR0.PE = 1)。开启分页后,从应用程序视角,看到的是线性地址(虚拟地址)空间,而不是物理地址。分页机制涉及到线性地址转换、访问权限控制以及缓存的使用和管理。本篇文章主要介绍线性地址到物理地址的转换过程,不会涉及到访问权限控制和缓存的使用。
一、分页的开启与关闭
分页只能在保护模式(CR0.PE = 1)下使用。在保护模式下,是否开启分页,由 CR0. PG 位(位 31)决定:
- 当 CR0.PG = 0 时,未开启分页,线性地址等同于物理地址;
- 当 CR0.PG = 1 时,开启分页。
二、四种分页模式
Intel-64 处理器支持 4 种分页模式:
- 32 位分页
- PAE(Physical 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.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 位线性地址。
不同的分页模式,其支持的线性地址宽度、物理地址宽度和页大小也是同的,其对应关系如下:
| 分页模式 | CR0.PG | CR4.PAE | IA32_EFER.LME | CR4.LA57 | 线性地址宽度(位) | 物理地址宽度(位) | 页大小 |
|---|---|---|---|---|---|---|---|
| 32位 | 1 | 0 | 0 | N/A | 32 | 最大 40 | 4 KB 4 MB |
| PAE | 1 | 1 | 0 | N/A | 32 | 最大 52 | 4 KB 2 MB |
| 4 级分页 | 1 | 1 | 1 | 0 | 48 | 最大 52 | 4 KB 2 MB 1 GB |
| 5 级分页 | 1 | 1 | 1 | 1 | 57 | 最大 52 | 4 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 地址要求,未使用。
三、层级分页结构
上述 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 ()项组成,每次转换使用 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 或 不同的页结构项)、线性地址中用来选择页结构项的位、该项是否以及如何映射到一个页。
四、4 级分页的线性地址转换过程
4.1 转换过程概览
4 级分页下,可以把线性地址映射到 4KB 、2MB 或者 1GB 大小的页。
不同大小的页,其转换过程图如下所示。
线性地址转换到 4KB 的页:
线性地址转换到 2MB 的页:
线性地址转换到 1GB 的页:
使用 4 级或 5 级分页时,CR3 寄存器格式如下所示,其中位 M-1:12 保存着根页结构的物理地址 :
注:M 是 MAXPHYADDR 的缩写,表示处理器支持的最大物理地址位数,该值最大为 52。
4.2 转换过程解析
PML4 和 PML4E
CR3 寄存器的 51:12 位(共 40 位),保存着 PML4 表的物理地址,该地址是 4KB 对齐的。PML4 表由 512 个 64 位( 8 字节)的项(PML4E)组成,也就是说每个 PML4 表的大小为 。
线性地址的 47:39 位(共 9 位,能够标识 512 个数字),用作查找 PML4E (4 级页目录项)的索引。
PML4E 通过以下步骤定位:
- 从 CR3 寄存器的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到 4 级页目录的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
- 以线性地址的 47:39 位,作为 PML4E 在 PML4 表中的索引
- 由于每个 PML4E 是 8 字节大小,把上一步获取到的索引值乘以 8 ,得到该 PML4E 对于 4 级页目录的偏移字节量
- 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PML4E 的物理地址。
注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。
PML4E 使用线性地址的 47:39 位来标识,它控制着线性地址的 512GB ()的空间。
计算后的物理地址格式:
- 位 51:12 来自于 CR3寄存器
- 位 11:3 来自于线性地址的 47:39 位
- 位 2:0 位全是 0
PML4E 格式
PML4E 大小为 8 字节,其格式如下所示:
各字段说明如下:
我们主要关注 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)的大小为 。
线性地址的 38:30 位(共 9 位,能够标识 512 个数字),用作查找 PDPTE (页目录指针项)的索引。
定位 PDPTE
PDPTE 通过以下步骤定位:
- 从 PML4E 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页目录指针表(PDPT)的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
- 以线性地址的 38:30 位,作为 PDPTE 在 PDPT 表中的索引
- 由于每个 PDPTE 是 8 字节大小,把第 2 步获取到的索引值乘以 8 ,得到该 PDPTE 在页目录指针表中的偏移字节量
- 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PDPTE 的物理地址。
注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。
PDPTE 使用线性地址的 38:30 位来标识,它控制着线性地址的 1GB ()的空间。
计算后的物理地址格式:
- 位 51:12 来自于 PML4E
- 位 11:3 来自于线性地址的 38:30 位
- 位 2:0 位全是 0
PDPTE 大小为 8 字节,其使用依赖于它的 PS 位(第 7 位)。
PDPTE 映射到 1GB
-
当 PS(Page Size) 位为 1 时,表示该 PDPTE 项直接映射到 1GB 的页。
此时 PDPTE 格式如下:
我们主要关注 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 映射到页,整个转换流程到达最后一步,此时可以得到线性地址转换后的物理地址,其计算过程如下:
- 从 PDPTE 的 51:30 位,获取到 22 位物理地址,左移 30 位后,得到页的基地址。该基地址低 30 位全部为 0,对齐到 1GB 的物理地址;
- 从线性地址的 29:0 位,获取到页内偏移量
- 把第 1 步得到基地址,加上第 2 步得到的偏移量,得到该线性地址转换后的物理地址。
完整流程如下图:
最终的物理地址格式:
- 位 51:30 来自于 PDPTE;
- 位 29:0 来自于原始的线性地址。
PDPTE 引用页目录(Page Directory, PD)
-
当 PS(Page Size) 位为 0 时,PDPTE 的 51:12 位保存的是页目录(Page Directory)的物理地址,该地址是 4KB 对齐的。每个页目录由 512 个 64位的项(PDE)组成。
此时,PDPTE 的格式如下:
PDE 通过以下步骤定位:
- 从 PDPTE 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页目录(PD)的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
- 以线性地址的 29:21 位,作为 PDE 在 PD 中的索引
- 由于每个 PDE 是 8 字节大小,把上一步获取到的索引值乘以 8 ,得到该 PDE 在页目录中的偏移字节量
- 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PDE 的物理地址。
注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。
PDE 使用线性地址的 29:21 位来标识,它控制着线性地址的 2MB ()的空间。
PDE 物理地址组成:
- 位 51:12 来自于 PDPTE;
- 位 11:3 来自于线性地址的 29:21 位
- 第 2:0 位全是 0
PDE 大小为 8 字节,其使用依赖于它的 PS 位(第 7 位)。
PDE 映射到 2MB 页
-
当 PS(Page Size) 位为 1 时,表示该 PDE 项直接映射到 2MB 的页。
此时,PDE 的格式如下:
我们主要关注 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 映射到页,整个转换流程到达最后一步,可以得到线性地址转换后的物理地址,其计算过程如下:
-
从 PDE 的 51:21 位,获取到 31 位物理地址,左移 21 位后,得到页的基地址。该基地址低 21 位全部为 0,对齐到 2MB 的物理地址;
-
从线性地址的 21:0 位,获取到页内偏移量
-
把第 1 步得到基地址,加上第 2 步得到的偏移量,得到该线性地址转换后的物理地址。
完整流程如下图:
最终的物理地址格式:
-
位 51:21 来自于 PDE;
-
位 20:0 来自于原始的线性地址。
PDE 引用页表(Page Table, PT)
-
当 PS(Page Size) 位为 0 时,PDE 的 51:12 位保存的是页表(Page Table)的物理地址,该地址是 4KB 对齐的。每个页表由 512 个 64位的项(Page Table Entry,PTE)组成。
此时,PDE 的格式如下:
PTE 通过以下步骤定位:
- 从 PDE 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页表(Page Table,PT)的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
- 以线性地址的 20:12 位,作为 PTE 在 PT 中的索引
- 由于每个 PTE 是 8 字节大小,把上一步获取到的索引值乘以 8 ,得到该 PTE 对于页表的偏移字节量
- 把第 1 步得到基地址,加上第 3 步得到的偏移量,得到该 PTE 的物理地址。
注:由于第 3 步计算偏移量时乘了 8,相当于左移 3 位,计算得到的地址 2:0 位全是 0。
PTE 使用线性地址的 20:12 位来标识,它控制着线性地址的 4KB ()的空间。
PTE 物理地址组成:
- 位 51:12 来自于 PDE;
- 位 11:3 来自于线性地址的 20:12 位
- 位 2:0 全是 0
PTE 映射到 4KB 页
PTE 格式如下:
我们主要关注 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 页,整个转换流程到达最后一步,可以得到线性地址转换后的物理地址,其计算过程如下:
- 从 PTE 的 51:12 位,获取到 40 位物理地址,左移 12 位后,得到页的基地址。该基地址低 12 位全部为 0,对齐到 4KB 的物理地址;
- 从线性地址的 12:0 位,获取到页内偏移量
- 把第 1 步得到基地址,加上第 2 步得到的偏移量,得到该线性地址转换后的物理地址。
完整的转换流程如下:
最终的物理地址格式:
- 位 51:12 来自于 PTE;
- 位 11:0 来自于原始的线性地址。
五、异常
正常情况下,当识别出页帧时,转换过程就完成了。但是,当转换过程遇到标识了”不存在“(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