2. ARMv7/v8 页表基础:层级结构与属性详解
在 QNX 实时操作系统中,MMU 页表配置是决定系统内存隔离、缓存策略与访问权限的核心环节。ARMv7(32 位)与 ARMv8(64 位)架构虽然指令集不同,但其页表设计遵循分级的页表遍历(Page Table Walk)思想。本章从工程视角拆解 L1/L2/L3 页表层级、页表项(PTE)格式及关键属性位,为后续 QNX 页表配置实战打下基础。
SYSPAGE 和 as_alloc() 系列接口建立进程地址空间,底层依赖的正是 ARM 页表结构。理解页表层级深度直接影响 TLB 命中率与上下文切换开销。
2.1 页表层级结构(L1 / L2 / L3)
ARMv7 采用两级页表(L1 + L2),ARMv8 采用三级页表(L1 + L2 + L3)。两者均支持段映射(Section)与页映射(Page)两种粒度。下表对比了两种架构的层级与映射粒度:
| 架构 | 页表层级 | L1 粒度 | L2 粒度 | L3 粒度 | 虚拟地址宽度 |
|---|---|---|---|---|---|
| ARMv7-A | 2 级 | 1MB (Section) | 4KB (Page) / 64KB (Large Page) | — | 32 位 |
| ARMv8-A (AArch64) | 3 级 | 1GB (Table) | 2MB (Table) | 4KB (Page) | 48 位 / 52 位 |
在 QNX 中,进程地址空间通常使用 4KB 页粒度(L2 或 L3 页表项),而内核映射(如设备寄存器、内核代码段)常使用 1MB 或 2MB 大页以减少页表占用。下图展示了 ARMv8 三级页表遍历的典型流程:
buildfile 中通过 vaddr 和 paddr 指定映射时,系统自动选择最优页粒度。若需强制使用大页,可在 as_attr 中设置 PAGESIZE_2M 或 PAGESIZE_1G 标志。
2.2 页表项(PTE)格式详解
页表项是 MMU 进行地址转换与权限检查的基本单元。ARMv7 与 ARMv8 的 PTE 格式存在差异,但核心字段高度相似。下表以 ARMv8 AArch64 的 L3 页表项(4KB 页)为例,拆解各比特位含义:
| 比特位 | 字段名 | 描述 | QNX 典型配置 |
|---|---|---|---|
| [1:0] | Descriptor Type | 00: 无效;01: 页表项;10: 块描述符;11: 保留 | 01(页表项) |
| [47:12] | Output Address | 物理页帧号(PFN),4KB 对齐 | 由 mmu_map() 自动填充 |
| [11] | nG (not Global) | 0: 全局映射(所有进程可见);1: 非全局(仅当前进程) | 用户空间置 1;内核空间置 0 |
| [10] | AF (Access Flag) | 0: 未访问;1: 已访问(MMU 自动置位) | 初始置 1,避免第一次访问触发异常 |
| [7:6] | AP (Access Permission) | 00: 仅 EL1 读写;01: EL1 读写/EL0 只读;10: EL1 只读;11: EL1/EL0 读写 | 内核段 00;用户段 11 |
| [5:4] | SH (Shareability) | 00: 非共享;01: 预留;10: 外设共享;11: 内部共享 | 多核共享内存置 11 |
| [3:2] | AttrIndx (MAIR 索引) | 指向 MAIR_EL1 寄存器中的 8 个内存属性条目之一 | 0: 正常内存(写回);1: 设备内存(nGnRE) |
ARMv7 的 L2 页表项(4KB 小页)格式类似,但缺少 nG 和 SH 字段,取而代之的是 XN(Execute Never) 和 Domain 字段。QNX 在 ARMv7 上通过 Domain 实现进程间保护,在 ARMv8 上则依赖 ASID(Address Space ID) 与 nG 位。
SYSPAGE 的 mattr 中显式指定 DEVICE 属性。
2.3 属性位详解:权限、缓存与共享
页表项中的属性位直接决定了 QNX 进程的内存访问行为。以下从三个维度展开:
2.3.1 访问权限(AP / PXN / UXN)
- AP[2:1](ARMv8):控制 EL1(内核)与 EL0(用户)的读写权限。QNX 内核段使用
AP=00(仅 EL1 读写),用户段使用AP=11(EL0/EL1 读写)。 - PXN(Privileged Execute Never):置 1 时禁止内核执行该页代码。QNX 用于保护用户栈页,防止内核漏洞利用。
- UXN(User Execute Never):置 1 时禁止用户执行。QNX 数据段与堆页均置 UXN=1。
2.3.2 缓存策略与 MAIR
ARMv8 通过 MAIR_EL1 寄存器定义 8 个内存属性索引。QNX 启动时初始化 MAIR,典型配置如下:
| AttrIndx | MAIR 编码 | 缓存策略 | QNX 用途 |
|---|---|---|---|
| 0 | 0xFF | Normal Memory, Write-Back, Read-Allocate, Write-Allocate | 进程代码段、数据段、堆栈 |
| 1 | 0x44 | Device-nGnRE (非缓存, 非聚集, 重排, 提前写确认) | MMIO 寄存器、DMA 描述符 |
| 2 | 0x04 | Normal Memory, Non-Cacheable | 共享内存(无缓存一致性需求) |
2.3.3 共享域与多核一致性
SH(Shareability) 字段控制缓存一致性域:
- 00 (Non-shareable):仅当前核访问,无需硬件一致性。QNX 用于单核专用数据结构。
- 11 (Inner Shareable):多核集群内所有核可见。QNX 用于进程间通信(IPC)缓冲区。
- 10 (Outer Shareable):跨集群共享(多芯片系统)。QNX 在 AMP 系统中使用。
下图总结了 ARMv8 页表项属性位如何映射到 QNX 内存区域:
buildfile 中为设备寄存器添加映射时,使用 map 指令并指定 device 属性:# 将 0xF0000000 处的 4KB 设备寄存器映射到虚拟地址 0xA0000000
map -d 0xA0000000 0xF0000000 0x1000
# -d 标志自动设置 AttrIndx=1 (DEVICE), AP=00, PXN=1, UXN=1
2.4 页表配置的工程考量
在 QNX 实际项目中,页表配置直接影响系统实时性与安全性:
- TLB 抖动:频繁使用 4KB 页会导致 TLB 覆盖范围小,上下文切换时 TLB 刷新增多。建议对性能关键路径使用 2MB 大页。
- 页表占用内存:三级页表在 48 位地址空间下最多占用 512 个 L1 表项 + 512 个 L2 表项 + 512 个 L3 表项(约 12KB)。QNX 通过
as_alloc()按需分配,避免浪费。 - 安全隔离:利用 PXN/UXN 和 nG 位实现内核与用户空间的严格隔离。QNX 的 ASLR 机制依赖页表随机化,需确保 nG 位正确设置。
pidin 命令查看进程内存映射,结合 io-pager 统计页错误次数,可快速定位页表配置不当导致的性能瓶颈。