3. 页表描述符详解:Stage 1 页表描述符格式、属性域
在 ARMv8-A 的 MMU 架构中,Stage 1 页表负责将虚拟地址(VA)转换为物理地址(PA),并携带内存访问所需的全部权限与属性。页表中的每一个条目称为页表描述符(Page Table Descriptor),其格式和属性域直接决定了驱动能否安全、高效地访问外设内存。
3.1 Stage 1 页表描述符格式
Stage 1 页表描述符根据页表级别(Level 0 ~ Level 3)和条目类型(块/页/表)有所不同。以最常用的 Level 2 和 Level 3 为例,描述符的低位用于类型判断,高位用于物理地址和属性控制。
- Bit[1:0]:描述符类型。00 = 无效(Fault),01 = 表(Table),10 = 块(Block)或页(Page),11 = 保留。
- Bit[47:12](4KB 粒度):输出物理地址的高 36 位,低 12 位由块/页内偏移决定。
- Bit[11:2]:属性域和标志位,包括访问权限、内存类型、安全位等。
3.2 核心属性域详解
Stage 1 描述符中的属性域控制着内存的访问权限、执行权限、脏页管理以及缓存策略。对于驱动开发者,以下五个属性域最为关键:
| 属性域 | 位域位置 | 功能描述 | 驱动开发影响 |
|---|---|---|---|
| XN (Execute Never) | Bit[54] | 禁止在该内存区域执行指令 | 保护数据段不被恶意代码注入执行 |
| PXN (Privileged Execute Never) | Bit[53] | 禁止特权级(EL1/EL2)在该区域执行指令 | 防止内核漏洞利用用户空间代码 |
| AF (Access Flag) | Bit[10] | 访问标志位,指示该页是否被访问过 | 用于页面回收和脏页跟踪 |
| AP (Access Permission) | Bit[7:6] | 定义读写权限组合(EL0/EL1 分离) | 控制用户态与内核态对设备内存的访问 |
| MemAttr (Memory Attributes) | Bit[5:4] + MAIR | 指定内存类型(Normal/Device)和缓存策略 | 决定外设寄存器是否被缓存、是否合并写 |
3.3 XN 与 PXN:执行权限控制
XN 和 PXN 是 ARMv8 引入的两级执行权限控制机制,用于防御代码执行类攻击。
- XN (Execute Never):当 XN=1 时,无论当前异常级别如何,均不允许从该内存区域取指执行。驱动通常将数据缓冲区、设备 MMIO 区域标记为 XN,防止 CPU 误将数据当作指令执行。
- PXN (Privileged Execute Never):当 PXN=1 时,仅禁止特权级(EL1/EL2)执行,非特权级(EL0)不受影响。这用于防止内核态跳转到用户态代码执行(如 ret2user 攻击)。
3.4 AF:访问标志位
AF(Access Flag)位用于记录该页表描述符对应的物理页是否被访问过。当 AF=0 时,MMU 在第一次访问该页时会触发 Access Flag 故障,由操作系统软件将 AF 置 1 后重试。
- 硬件行为:MMU 在每次访问页表时检查 AF 位。若 AF=0,则产生同步异常(Data Abort),异常类型为 Access Flag Fault。
- 软件职责:内核的缺页异常处理程序需要识别该故障类型,将对应描述符的 AF 置 1,并清除 TLB 中对应的缓存条目。
- 驱动影响:驱动在初始化时应当确保所有映射的描述符 AF=1,避免首次访问时产生不必要的性能开销。对于热插拔设备或动态映射区域,建议在映射完成后立即设置 AF=1。
ioremap 或 dma_alloc_coherent 后,应检查底层页表是否已正确设置 AF 位。
3.5 AP:访问权限控制
AP(Access Permission)域由两位组成,定义该页在 EL0(用户态)和 EL1(内核态)下的读写权限组合。
| AP[7:6] | EL1 权限 | EL0 权限 | 典型用途 |
|---|---|---|---|
| 00 | 读写 | 无访问 | 内核专用数据、设备寄存器 |
| 01 | 读写 | 读写 | 用户共享内存、帧缓冲区 |
| 10 | 只读 | 无访问 | 内核只读数据(rodata) |
| 11 | 只读 | 只读 | 用户只读数据(代码段) |
驱动在映射设备内存时,通常使用 AP=00(仅内核读写),确保用户态程序无法直接操作设备寄存器。若需要将 DMA 缓冲区共享给用户态,则使用 AP=01,并配合 XN=1 防止执行。
3.6 MemAttr:内存类型与缓存策略
MemAttr 域通过 2 位索引值(Bit[5:4])指向 MAIR(Memory Attribute Indirection Register)中预定义的属性条目。MAIR 寄存器包含 8 个槽位,每个槽位定义一种内存类型和缓存策略。
对于驱动开发,最常用的两种内存类型是:
- Normal Memory:用于常规 RAM,支持缓存、写合并、预取等优化。驱动中的数据缓冲区应使用 Normal 类型以获取最佳性能。
- Device Memory:用于外设寄存器,禁止缓存、禁止写合并、禁止预取。所有 MMIO 区域必须映射为 Device 类型,否则会导致寄存器读写顺序错乱或数据丢失。
- Device-nGnRnE:最严格模式,不合并、不重排、不提前应答。适用于控制寄存器。
- Device-nGnRE:允许提前应答,适用于 FIFO 数据寄存器。
- Device-nGRE:允许合并写,适用于帧缓冲区等对顺序要求较低的场景。
3.7 属性域组合配置示例
以下是一个典型的设备驱动页表描述符配置示例,用于映射一个 4KB 的 UART 寄存器区域:
// 伪代码:配置 Stage 1 页表描述符属性域
// 目标:映射 UART 基址 0x1C090000,虚拟地址 0xFC090000
uint64_t descriptor = 0;
// 类型:页(Page),4KB 粒度
descriptor |= (0b10 << 0); // Bit[1:0] = 10
// 输出物理地址(高 36 位)
descriptor |= (0x1C090000 >> 12) << 12;
// AP:仅内核读写
descriptor |= (0b00 << 6); // AP[7:6] = 00
// MemAttr:Device-nGnRnE(索引 0)
descriptor |= (0b00 << 4); // MemAttr[5:4] = 00
// AF:已访问
descriptor |= (1 << 10); // AF = 1
// XN:禁止执行
descriptor |= (1 << 54); // XN = 1
// PXN:禁止特权执行
descriptor |= (1 << 53); // PXN = 1
// 写入页表条目
page_table[va_index] = descriptor;
- 设备 MMIO 区域:AP=00, MemAttr=Device-nGnRnE, XN=1, PXN=1, AF=1
- DMA 数据缓冲区:AP=00, MemAttr=Normal WB, XN=1, PXN=1, AF=1
- 用户共享内存:AP=01, MemAttr=Normal WB, XN=1, PXN=0, AF=1