4. 页表层级与粒度:L1/L2 页表结构、段映射(1MB)、大页(64KB)、小页(4KB)选择策略

在 QNX 实时操作系统中,MMU 通过多级页表将虚拟地址转换为物理地址。ARMv7-A 架构(QNX 常用平台)采用两级页表结构:L1 页表(一级页表)和 L2 页表(二级页表)。页表层级与映射粒度的选择直接影响 TLB 命中率、内存开销和实时性能。

4.1 L1/L2 两级页表结构

QNX 的页表采用两级层次结构,L1 页表将 4GB 虚拟地址空间划分为 4096 个条目(每个条目覆盖 1MB 空间),L2 页表进一步将 1MB 区域细分为更小的页。

关键概念: L1 页表常驻内存,L2 页表按需分配。这种设计在节省内存的同时,支持灵活的粒度映射。
QNX L1/L2 两级页表结构 L1 页表 4096 个条目 每个条目 4 字节 总大小 16KB 条目[0] → 1MB 区域 条目[1] → 1MB 区域 条目[2] → 1MB 区域 ... 条目[4095] 指向 L2 页表 256 个条目(4KB 小页) 或 16 个条目(64KB 大页) 每个条目 4 字节 条目[0] → 4KB 物理页 条目[1] → 4KB 物理页 条目[2] → 4KB 物理页 ... 条目[255] → 4KB 物理页 物理内存 物理页帧 0 物理页帧 1 物理页帧 2 ... 映射 L1 条目可指向 L2 页表(细粒度映射)或直接映射 1MB 段(粗粒度映射) L2 页表仅在需要细粒度映射时分配,节省内存

4.2 三种映射粒度对比

QNX 支持三种映射粒度,每种粒度对应不同的应用场景和性能特征:

映射类型 粒度 L1 条目类型 L2 条目数 TLB 覆盖范围 典型用途
段映射 1MB 段描述符 无(直接映射) 大 内核代码、大块连续内存
大页 64KB 指向 L2 页表 16 中 设备内存、帧缓冲
小页 4KB 指向 L2 页表 256 小 用户进程堆栈、数据段

4.3 段映射(1MB)

段映射是最高效的映射方式:L1 页表条目直接包含物理基地址(1MB 对齐),无需 L2 页表。一次 TLB 查询即可完成地址转换。

性能优势: 段映射仅需一次内存访问即可完成地址转换,TLB 可以覆盖 1MB 连续空间。适合映射内核代码段、大块 DMA 缓冲区。
/* QNX 段映射配置示例(伪代码) */
/* L1 段描述符格式 */
#define L1_SECTION_DESC(phys_addr, attr) \
    (((phys_addr) & 0xFFF00000) | (attr) | 0x2)  /* 0x2 = 段描述符类型 */

/* 映射 0x80000000 虚拟地址到 0x40000000 物理地址(1MB 段) */
uint32_t *l1_table = (uint32_t *)L1_TABLE_BASE;
l1_table[0x800] = L1_SECTION_DESC(0x40000000, 
    L1_ATTR_CACHEABLE | L1_ATTR_BUFFERABLE | L1_ATTR_AP_RW);

4.4 大页(64KB)

大页映射介于段映射和小页之间:L1 条目指向 L2 页表,L2 页表包含 16 个条目(每个覆盖 64KB)。适合中等大小的连续内存区域。

64KB 大页映射结构 虚拟地址分解(64KB 大页) [31:20] L1 索引 (12 bits) | [19:16] L2 索引 (4 bits) | [15:0] 页内偏移 (16 bits) L1 条目 指向 L2 页表基址 L2 页表(16 条目) 条目[0] → 64KB 物理页 条目[1..15] → 64KB 物理页 物理内存 64KB 连续区域 64KB 对齐 64KB 大页:L2 页表仅需 16 个条目(64 字节),TLB 覆盖范围是 4KB 小页的 16 倍 适合映射设备寄存器、帧缓冲、中等大小共享内存

4.5 小页(4KB)

小页是 QNX 最常用的映射粒度,提供最精细的内存管理。每个 L2 页表包含 256 个条目,每个条目映射 4KB 物理页。

注意: 小页映射虽然灵活,但 TLB 覆盖范围最小(仅 4KB/条目),频繁的 TLB 缺失会降低实时性能。对于时间关键型代码,建议使用段映射或大页。
/* QNX 小页映射配置示例 */
/* L2 小页描述符格式 */
#define L2_SMALL_PAGE_DESC(phys_addr, attr) \
    (((phys_addr) & 0xFFFFF000) | (attr) | 0x2)  /* 0x2 = 小页描述符类型 */

/* 映射 4KB 物理页到虚拟地址 */
uint32_t *l2_table = (uint32_t *)alloc_l2_page_table();
l2_table[0] = L2_SMALL_PAGE_DESC(0x40001000, 
    L2_ATTR_CACHEABLE | L2_ATTR_AP_RW);
l2_table[1] = L2_SMALL_PAGE_DESC(0x40002000, 
    L2_ATTR_CACHEABLE | L2_ATTR_AP_RW);
/* ... 继续映射 256 个条目 ... */

4.6 粒度选择策略

在实际的 QNX 系统设计中,粒度选择需要综合考虑以下因素:

场景 推荐粒度 理由
内核代码段 段映射(1MB) 代码段连续且只读,TLB 效率最高
用户进程代码段 段映射或大页 减少 TLB 缺失,提高指令获取速度
设备寄存器 大页(64KB) 设备地址空间通常连续,64KB 对齐
帧缓冲 大页(64KB) 大块连续内存,减少页表开销
进程堆栈 小页(4KB) 堆栈增长不确定,需要按需分配
进程数据段 小页(4KB) 支持按页保护,实现 COW 和按需加载
共享内存 大页或段映射 减少 TLB 压力,提高跨进程访问性能

4.7 混合映射策略

QNX 允许在同一地址空间内混合使用不同粒度,实现性能与灵活性的平衡:

混合映射策略示例 虚拟地址空间(4GB) 段映射 1MB 内核代码 大页 64KB 设备内存 小页 4KB 用户进程 L1 条目直接映射,无 L2 页表 TLB 覆盖 1MB,性能最优 L2 页表仅 16 条目,TLB 覆盖 64KB 适合中等大小连续区域 L2 页表 256 条目,支持按页保护 适合堆栈、数据段、按需加载 混合映射策略:内核段(1MB)+ 设备内存(64KB)+ 用户进程(4KB) 在同一个 L1 页表中,不同虚拟地址区域使用不同粒度,兼顾性能与灵活性

4.8 粒度选择决策流程

在实际工程中,可以按照以下决策流程选择映射粒度:

  1. 检查内存区域大小:如果区域 ≥ 1MB 且连续,优先使用段映射。
  2. 检查对齐要求:段映射要求 1MB 对齐,大页要求 64KB 对齐。
  3. 评估 TLB 压力:实时任务的关键代码段使用大粒度减少 TLB 缺失。
  4. 考虑保护需求:需要按页保护(如只读、读写、不可执行)时使用小页。
  5. 平衡页表开销:小页需要完整的 L2 页表(1KB),大页仅需 64 字节。
工程建议: 在 QNX 系统启动阶段,使用 as_region() 和 as_attr() 接口指定映射粒度。对于实时线程的代码段和数据段,尽量使用段映射或大页,避免运行时 TLB 抖动。