3. 页表描述符详解:Table Descriptor 与 Page Descriptor 的结构,以及属性位(AF, AP, XN, PXN)的含义
在 ARM64 的页表体系中,描述符(Descriptor)是页表项(PTE)的核心数据结构。根据页表层级和映射粒度的不同,描述符分为 Table Descriptor(表描述符)和 Page/Block Descriptor(页/块描述符)。理解这两类描述符的位域布局,是正确配置页表和初始化 MMU 的前提。
3.1 描述符类型与层级关系
ARM64 页表支持 4 级(TCR_EL1.TxSZ 配置)或 3 级查找。每一级页表项可能指向下一级页表(Table Descriptor),也可能直接指向物理内存块(Block Descriptor)或 4KB 页(Page Descriptor)。
- 0b00:无效描述符(Fault)
- 0b01:Table Descriptor(指向下一级页表)
- 0b10:Block Descriptor(映射大块内存,如 2MB/1GB)
- 0b11:Page Descriptor(映射 4KB 页)
3.2 Table Descriptor 结构
Table Descriptor 用于非最后一级页表(L0/L1/L2),其核心作用是提供下一级页表的物理基地址。ARMv8-A 架构中,Table Descriptor 的位域布局如下:
| 位域 | 宽度 | 描述 |
|---|---|---|
| [63:48] | 16 | 下一级页表的物理地址高位(PA[47:32]),具体位数由 TCR_EL1.TxSZ 决定 |
| [47:12] | 36 | 下一级页表的物理地址低位(PA[31:12]),与高位拼接形成 4KB 对齐的基地址 |
| [11:2] | 10 | 保留位(RES0),必须为 0 |
| [1] | 1 | 类型位:1 表示 Table Descriptor |
| [0] | 1 | 有效位:1 表示有效 |
3.3 Page/Block Descriptor 结构
Page Descriptor(L3 级)和 Block Descriptor(L0/L1/L2 级)共享相同的属性位布局,区别仅在于映射粒度。以下是标准 4KB 页描述符的位域结构:
| 位域 | 宽度 | 描述 |
|---|---|---|
| [63:48] | 16 | 物理地址高位(PA[47:32]),与低位拼接 |
| [47:12] | 36 | 物理地址低位(PA[31:12]),4KB 页对齐 |
| [11] | 1 | nG:非全局位(Non-Global),TLB 条目是否全局 |
| [10] | 1 | AF:访问标志位(Access Flag) |
| [9:8] | 2 | SH:可共享性属性(Shareability) |
| [7:6] | 2 | AP:访问权限位(Access Permission) |
| [5] | 1 | NS:安全位(Non-Secure) |
| [4] | 1 | AttrIndx[0]:属性索引低位(与 MAIR 配合) |
| [3] | 1 | AttrIndx[1]:属性索引高位 |
| [2] | 1 | AttrIndx[2]:属性索引最高位(共 3 位,索引 8 个 MAIR 槽位) |
| [1] | 1 | 类型位:1 表示 Page/Block Descriptor |
| [0] | 1 | 有效位:1 表示有效 |
3.4 核心属性位详解
3.4.1 AF(Access Flag,访问标志位)
AF 位位于描述符的 bit 10。当 MMU 启用且 AF 为 0 时,首次访问该页会触发 Access Flag Fault(访问标志故障)。操作系统通常利用此机制实现页表遍历的延迟优化:
- 初始状态: AF = 0,表示该页尚未被访问。
- 硬件行为: 当 MMU 成功完成地址翻译且 AF=0 时,硬件会自动将 AF 置 1(前提是未触发其他故障)。
- 软件用途: 操作系统在页表创建时可将 AF 置 1 以避免初次访问故障,或利用 AF 实现页面置换算法(如近似 LRU)。
3.4.2 AP(Access Permission,访问权限位)
AP 位位于描述符的 bit 7:6,共 2 位,用于控制页面的读写权限以及异常等级(Exception Level)的访问限制。AP 位的编码与权限映射如下:
| AP[2:1] | EL1/EL2/EL3 权限 | EL0 权限 | 典型用途 |
|---|---|---|---|
| 0b00 | 读/写 | 无访问 | 内核私有数据 |
| 0b01 | 读/写 | 读/写 | 用户共享数据 |
| 0b10 | 只读 | 无访问 | 内核只读数据 |
| 0b11 | 只读 | 只读 | 用户只读代码/数据 |
3.4.3 XN(Execute Never,不可执行位)
XN 位位于描述符的 bit 54(在 64 位描述符的高位区域)。当 XN=1 时,禁止在该页上取指执行。这是 ARM64 实现 NX(No-Execute) 内存保护机制的核心位:
- XN=0: 允许在该页上执行指令。
- XN=1: 禁止在该页上执行指令,任何取指操作都会触发 Instruction Abort。
典型应用场景:
- 数据页(堆、栈、全局变量)必须设置 XN=1,防止代码注入攻击。
- 代码页(.text 段)设置 XN=0,允许执行。
3.4.4 PXN(Privileged Execute Never,特权不可执行位)
PXN 位位于描述符的 bit 53。与 XN 类似,但 PXN 仅限制特权级(EL1/EL2/EL3)的执行权限:
- PXN=0: 允许特权级在该页上执行指令。
- PXN=1: 禁止特权级在该页上执行指令,但 EL0 用户态仍可执行(如果 XN=0)。
PXN 提供更细粒度的权限控制,常用于:
- 用户态代码页:设置 PXN=1,防止内核意外跳转到用户空间代码执行(Kernel 到 Userspace 的防护)。
- 内核某些敏感数据页:设置 PXN=1 且 XN=1,双重保护。
3.5 属性位组合示例
在实际的 MMU 初始化代码中,页表描述符的属性位通常通过宏定义或位操作组合而成。以下是一个典型的页表项属性配置示例:
/* 页表描述符属性位宏定义 */
#define PAGE_DEFAULT_ATTRS ( \
(1UL << 10) | /* AF = 1,访问标志有效 */ \
(0UL << 7) | /* AP[2:1] = 00,EL1 读写,EL0 无访问 */ \
(1UL << 54) | /* XN = 1,不可执行 */ \
(1UL << 53) | /* PXN = 1,特权不可执行 */ \
(0UL << 4) | /* AttrIndx = 0,指向 MAIR 第 0 槽 */ \
(3UL << 0) /* 类型位 = 0b11,Page Descriptor,有效位 = 1 */ \
)
/* 内核代码段属性:可执行,特权可执行 */
#define PAGE_KERNEL_CODE_ATTRS ( \
(1UL << 10) | /* AF = 1 */ \
(0UL << 7) | /* AP = 00,EL1 读写 */ \
(0UL << 54) | /* XN = 0,允许执行 */ \
(0UL << 53) | /* PXN = 0,允许特权执行 */ \
(2UL << 2) | /* AttrIndx = 2,Normal 内存属性 */ \
(3UL << 0) /* Page Descriptor,有效 */ \
)
- Table Descriptor 用于非最后一级页表,提供下一级页表基地址;Page/Block Descriptor 用于最后一级或块映射,包含物理地址和属性位。
- AF 位控制访问标志,硬件可自动置位;AP 位控制读写权限和异常等级隔离。
- XN 位禁止所有异常等级执行,PXN 位仅禁止特权级执行,两者结合可实现完整的 W^X(Write XOR Execute)保护。
- 在 MMU 初始化阶段,必须正确设置这些属性位,否则会导致权限故障或安全漏洞。