2. ARMv7/v8 页表基础:层级结构与属性详解

在 QNX 实时操作系统中,MMU 页表配置是决定系统内存隔离、缓存策略与访问权限的核心环节。ARMv7(32 位)与 ARMv8(64 位)架构虽然指令集不同,但其页表设计遵循分级的页表遍历(Page Table Walk)思想。本章从工程视角拆解 L1/L2/L3 页表层级、页表项(PTE)格式及关键属性位,为后续 QNX 页表配置实战打下基础。

工程要点: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 三级页表遍历的典型流程:

ARMv8 三级页表遍历流程(4KB 页粒度) L1 页表 1GB 块 / 指向 L2 L2 页表 2MB 块 / 指向 L3 L3 页表 4KB 页表项 物理页 4KB / 2MB / 1GB VA[47:39] VA[38:30] VA[29:21] VA[20:0] 页内偏移 说明: • TTBR0_EL1 指向 L1 页表基地址;MMU 根据 VA[47:39] 索引 L1 表项。 • 若 L1 表项为块描述符(1GB),则直接完成映射;否则指向 L2 页表。 • L2 表项类似:2MB 块直接映射,否则指向 L3 页表;L3 表项固定为 4KB 页。 • QNX 微内核通常使用 4KB 页保证细粒度保护,驱动或大块共享内存使用 2MB/1GB 大页。
QNX 工程提示:在 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 位。

常见错误:在 QNX 中配置设备内存映射时,若忘记将 MAIR 索引设置为设备内存(nGnRE),会导致 CPU 对寄存器地址执行写回缓存操作,引发总线错误或数据不一致。务必在 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 内存区域:

QNX 内存区域与页表属性位映射 内核代码/数据 AP=00, PXN=0, UXN=0 AttrIndx=0, SH=00 用户代码段 AP=11, PXN=1, UXN=0 AttrIndx=0, SH=00 用户数据/堆栈 AP=11, PXN=1, UXN=1 AttrIndx=0, SH=00 设备 MMIO AP=00, PXN=1, UXN=1 AttrIndx=1, SH=10 IPC 共享内存 AP=11, PXN=1, UXN=1 AttrIndx=0, SH=11 DMA 缓冲区 AP=00, PXN=1, UXN=1 AttrIndx=2, SH=10 AP=00: 仅内核; AP=11: 用户+内核; PXN=1: 禁止内核执行; UXN=1: 禁止用户执行; SH=00: 单核; SH=11: 多核一致性; SH=10: 外设共享
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 位正确设置。
调试技巧:使用 QNX 的 pidin 命令查看进程内存映射,结合 io-pager 统计页错误次数,可快速定位页表配置不当导致的性能瓶颈。