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 页表按需分配。这种设计在节省内存的同时,支持灵活的粒度映射。
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)。适合中等大小的连续内存区域。
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 允许在同一地址空间内混合使用不同粒度,实现性能与灵活性的平衡:
4.8 粒度选择决策流程
在实际工程中,可以按照以下决策流程选择映射粒度:
- 检查内存区域大小:如果区域 ≥ 1MB 且连续,优先使用段映射。
- 检查对齐要求:段映射要求 1MB 对齐,大页要求 64KB 对齐。
- 评估 TLB 压力:实时任务的关键代码段使用大粒度减少 TLB 缺失。
- 考虑保护需求:需要按页保护(如只读、读写、不可执行)时使用小页。
- 平衡页表开销:小页需要完整的 L2 页表(1KB),大页仅需 64 字节。
工程建议: 在 QNX 系统启动阶段,使用
as_region() 和 as_attr() 接口指定映射粒度。对于实时线程的代码段和数据段,尽量使用段映射或大页,避免运行时 TLB 抖动。