2. 页表结构详解:多级页表(L1/L2/L3)、页表项格式、属性位(RWX/User/Kernel)
页表是虚拟内存系统的核心数据结构,负责将处理器发出的虚拟地址(VA)转换为物理地址(PA)。在 ARMv8-A 64 位架构中,采用 4KB 页面 + 4 级页表(或 64KB 页面 + 3 级页表)的经典设计。本章聚焦于最常用的 4KB 页面、4 级页表 配置,深入剖析 L0/L1/L2/L3 各级页表的职责、页表项(PTE)格式以及关键的属性位控制。
2.1 多级页表层级结构(L0/L1/L2/L3)
多级页表的核心优势在于 按需分配:只有实际映射的虚拟地址范围才需要分配页表,极大地节省了内存。对于 48 位虚拟地址空间(VA[47:0]),4 级页表将地址翻译过程分解为 4 次查表。
2.2 页表项(PTE)格式详解
每个页表项(PTE)是一个 64 位的数据结构,包含下一级页表的物理地址或最终物理页面的地址,以及控制访问权限和内存属性的标志位。以 ARMv8-A 的 L3 页表项(4KB 页面) 为例,其格式如下:
| 位段 | 宽度(位) | 名称 | 描述 |
|---|---|---|---|
| [63:48] | 16 | PBHA / RES0 | 页表属性提示(Page Based Hardware Attributes)或保留位 |
| [47:12] | 36 | Output Address | 物理页面基地址(PA[47:12]),4KB 对齐 |
| [11:10] | 2 | nG / AF | 非全局位(nG)和访问标志位(Access Flag) |
| [9:8] | 2 | SH | 可共享性属性(Shareability):00-非共享,01-保留,10-外部共享,11-内部共享 |
| [7:6] | 2 | AP | 访问权限(Access Permission):控制读写权限和特权级 |
| [5] | 1 | NS | 非安全位(Non-Secure),用于 TrustZone 安全扩展 |
| [4] | 1 | AttrIndx | 内存属性索引(Attribute Index),指向 MAIR_EL1 寄存器中的属性配置 |
| [3:2] | 2 | Attr | 内存属性(Memory Attributes):00-普通内存,01-设备内存,10-不可缓存,11-保留 |
| [1] | 1 | nT | 非表项位(Non-Table),用于区分块映射和页表项 |
| [0] | 1 | Valid | 有效位:1-有效,0-无效(产生缺页异常) |
2.3 属性位详解:RWX / User / Kernel
页表项中的 AP(Access Permission) 位和 PXN/UXN 位共同决定了页面的访问权限和执行权限。这些属性位是操作系统实现进程隔离和内核保护的基础。
2.3.1 访问权限(AP)位
AP 位 [7:6] 控制页面的读写权限以及访问所需的特权级:
| AP[7:6] | EL1(内核)访问 | EL0(用户)访问 | 典型用途 |
|---|---|---|---|
| 00 | 读写 | 无权限 | 内核私有数据 |
| 01 | 读写 | 读写 | 用户共享内存 |
| 10 | 只读 | 无权限 | 内核代码段 |
| 11 | 只读 | 只读 | 用户代码段 / 只读数据 |
2.3.2 执行权限(PXN / UXN)位
ARMv8-A 引入了 PXN(Privileged Execute Never) 和 UXN(User Execute Never) 位,分别控制内核态和用户态的执行权限:
- PXN(位[54]): 当置 1 时,禁止内核(EL1)在该页面上执行代码。用于防止内核执行用户空间的数据页(如栈、堆),增强安全性。
- UXN(位[53]): 当置 1 时,禁止用户(EL0)在该页面上执行代码。用于标记数据页,防止用户态执行任意代码。
2.3.3 其他关键属性位
- nG(Non-Global,位[11]): 0 表示全局页面(所有进程共享,如内核空间),1 表示非全局页面(仅当前进程,TLB 中需携带 ASID)。
- AF(Access Flag,位[10]): 0 表示页面未被访问,首次访问时 MMU 会触发访问标志异常,由软件置 1。用于页面置换算法。
- SH(Shareability,位[9:8]): 控制多核系统中的缓存一致性域。内核空间通常设为内部共享(11),用户空间设为非共享(00)。
- AttrIndx(位[4]): 指向 MAIR_EL1 寄存器中的 8 个内存属性条目之一,用于配置缓存策略(如写回、直写、不可缓存)。
2.4 页表初始化中的属性配置策略
在系统启动阶段,页表初始化代码需要为每个映射区域正确设置属性位。以下是一个典型的属性配置策略示例:
/* 页表项属性宏定义(ARMv8-A) */
#define PTE_VALID (1UL << 0)
#define PTE_TABLE (1UL << 1) /* L0/L1/L2 表项 */
#define PTE_ATTR_INDEX (2UL << 2) /* 指向 MAIR 索引 2(Normal Write-Back) */
#define PTE_NS (1UL << 5) /* Non-Secure */
#define PTE_AP_RW_EL1 (0UL << 6) /* AP=00: EL1 读写,EL0 无权限 */
#define PTE_AP_RO_EL1 (2UL << 6) /* AP=10: EL1 只读,EL0 无权限 */
#define PTE_AP_RW_ALL (1UL << 6) /* AP=01: EL1/EL0 读写 */
#define PTE_AP_RO_ALL (3UL << 6) /* AP=11: EL1/EL0 只读 */
#define PTE_SH_INNER (3UL << 8) /* 内部共享 */
#define PTE_AF (1UL << 10) /* 访问标志置位 */
#define PTE_NG (1UL << 11) /* 非全局 */
#define PTE_UXN (1UL << 53) /* 用户不可执行 */
#define PTE_PXN (1UL << 54) /* 特权不可执行 */
/* 内核代码段:EL1 只读,不可执行(PXN=0, UXN=1) */
#define ATTR_KERN_CODE (PTE_VALID | PTE_AF | PTE_SH_INNER | \
PTE_AP_RO_EL1 | PTE_UXN)
/* 内核数据段:EL1 读写,不可执行 */
#define ATTR_KERN_DATA (PTE_VALID | PTE_AF | PTE_SH_INNER | \
PTE_AP_RW_EL1 | PTE_UXN | PTE_PXN)
/* 用户代码段:EL0 只读,可执行 */
#define ATTR_USER_CODE (PTE_VALID | PTE_AF | PTE_NG | \
PTE_AP_RO_ALL)
/* 用户数据段:EL0 读写,不可执行 */
#define ATTR_USER_DATA (PTE_VALID | PTE_AF | PTE_NG | \
PTE_AP_RW_ALL | PTE_UXN | PTE_PXN)
- AF 位预置: 在初始化阶段直接将 AF 位置 1,避免后续访问时产生访问标志异常,减少 TLB 缺失开销。
- SH 位选择: 内核空间使用内部共享(SH=11),确保多核缓存一致性;用户空间使用非共享(SH=00),减少不必要的缓存一致性广播。
- AttrIndx 配置: 将 MAIR_EL1 索引 0 配置为不可缓存(用于设备内存),索引 2 配置为写回(用于普通内存),索引 4 配置为直写(用于帧缓冲)。
2.5 块映射(Block Mapping)与性能
多级页表支持 块映射,允许 L1 或 L2 页表项直接映射大块连续内存,跳过后续级别的页表查找。这能显著减少 TLB 压力并提升性能:
| 页表级别 | 块大小 | 虚拟地址位段 | 适用场景 |
|---|---|---|---|
| L1 块映射 | 1 GB | VA[47:30] | 大块设备内存映射、GPU 缓冲区 |
| L2 块映射 | 2 MB | VA[47:21] | 内核线性映射区域、大页(HugeTLB) |
| L3 页映射 | 4 KB | VA[47:12] | 标准页面、进程私有映射 |
通过合理配置多级页表的层级结构、精确设置页表项属性位以及利用块映射特性,嵌入式系统可以在启动阶段构建一个高效、安全且可扩展的虚拟内存映射,为后续的 MMU 使能和操作系统运行奠定坚实基础。