4. 内存映射与地址空间

在 VxWorks 中,任务看到的地址几乎都是虚拟地址,真正落到 DDR、SRAM、外设寄存器上的访问,必须经过 MMU 的转换与属性检查。理解这一层,才能解释“同一块内存为什么换个映射方式性能差一倍”“为什么外设寄存器不能随便开缓存”“为什么 DMA 缓冲区必须按缓存行对齐”。本节围绕 MMU 配置、虚实地址转换和缓存属性三个核心问题展开。

一、MMU 在 VxWorks 中到底管什么

VxWorks 的 MMU 主要承担三件事:地址转换、访问权限控制和内存属性定义。地址转换把虚拟地址(VA)映射到物理地址(PA);权限控制决定某段内存是只读、可读写还是不可访问;内存属性则告诉 CPU 这段地址是否可缓存、是否允许乱序访问、是否可共享。三者共同作用,缺一不可。

在 ARM 架构下,VxWorks 通常使用两级页表:一级页表(L1)以 1MB 段或指向二级页表的方式描述映射,二级页表(L2)以 4KB 小页或更粗粒度描述。段映射适合大块连续内存,页映射适合需要精细权限控制的区域。配置时最常打交道的接口是 vxCpuMmuLib 和 vxMmuLib 提供的映射函数,例如 vxMmuMapRegion() 或底层 vxMmuMapPage()。

二、虚拟地址到物理地址的转换过程

以 32 位 ARM 为例,一次数据访问的转换路径可以概括为:CPU 发出 VA → MMU 查 TLB → 未命中则查页表 → 得到 PA 与属性 → 访问物理内存。TLB 是转换旁路缓冲,命中时无需查表,因此 TLB 命中率直接影响访存延迟。页表项中除了物理基地址,还包含 AP(访问权限)、C/B(缓存与缓冲位)、XN(不可执行)等控制位。

下面这张图展示了一个典型的 VxWorks 地址空间布局与转换关系:左侧是虚拟地址空间中的几个关键区域,中间经过 MMU 页表转换,右侧落到实际物理地址。图中同时标出了各区域的缓存属性,便于对照理解。

VxWorks 虚拟地址空间 → MMU 页表 → 物理地址映射示意 虚拟地址空间 (VA) 0x0000_0000 起 向量表 / 异常入口 内核代码 / 数据 只读 + 可执行 任务栈 / 堆 可读写 + 可缓存 DMA 缓冲区 非缓存 / 写穿透 外设寄存器映射 强序 / 设备属性 共享内存区 可共享 + 非缓存 MMU + 页表 TLB 查找 L1 页表查询 L2 页表查询 权限 / 属性检查 物理地址空间 (PA) 0x0000_0000 Boot ROM / 向量 0x0010_0000 DDR 内核区 0x0100_0000 DDR 用户区 0x4000_0000 DMA 物理缓冲 0xE000_0000 UART / GPIO 寄存器 0xF000_0000 共享 SRAM 蓝色:普通可缓存映射 橙色:设备/非缓存映射 绿色:共享内存映射

三、缓存属性设置:性能差异的根源

MMU 页表项中的 C(Cacheable)和 B(Bufferable)位决定了该区域的缓存行为。VxWorks 中常见的属性组合有以下几种:

  • Write-Back + Write-Allocate(WBWA):最常用的普通内存属性。写操作先落在 Cache,延迟写回 DDR,读未命中时分配缓存行。适合任务栈、堆、频繁读写的普通数据。
  • Write-Through(WT):写操作同时更新 Cache 和 DDR,读可缓存。适合需要与 DMA 共享但又不希望手动维护一致性的缓冲区。
  • Non-Cacheable(NC):完全不经过 Cache,每次访问直达物理内存。适合 DMA 描述符、外设寄存器、共享内存。
  • Device / Strongly-Ordered:除不缓存外,还禁止访问合并和乱序,保证每次访问严格按程序顺序到达设备。适合 UART、GPIO 等控制寄存器。

属性选错会带来两类典型问题。第一类是性能问题:把频繁访问的普通数据设成 NC,每次读写都走总线,实测带宽可能只有 WBWA 的 1/5 到 1/10。第二类是一致性问题:DMA 写入的缓冲区如果被 CPU 缓存在 Cache 中,CPU 读到的可能是旧数据;CPU 写入的数据如果还在 Cache 里没写回,DMA 读到的也是旧数据。解决方式要么把缓冲区设为 NC,要么在 DMA 传输前后调用 cacheFlush() 和 cacheInvalidate()。

四、一个可参考的映射配置示例

下面给出一个基于 ARM 平台的映射配置片段,展示如何为不同区域设置不同属性。参数为示例值,实际使用时需根据具体 SoC 的地址手册调整。

/* 示例:VxWorks 下配置几段关键映射 */
/* 普通 DDR 用户区:WBWA,可读写,可执行 */
vxMmuMapRegion(0x01000000,   /* VA 基址 */
              0x01000000,   /* PA 基址 */
              0x08000000,   /* 大小 128MB */
              MMU_ATTR_WBWA | MMU_ATTR_RW | MMU_ATTR_X);

/* DMA 缓冲区:Non-Cacheable,可读写,不可执行 */
vxMmuMapRegion(0x40000000,
              0x40000000,
              0x00100000,   /* 1MB */
              MMU_ATTR_NC | MMU_ATTR_RW);

/* UART 寄存器:Device,可读写,不可执行 */
vxMmuMapRegion(0xE0000000,
              0xE0000000,
              0x00001000,   /* 4KB */
              MMU_ATTR_DEVICE | MMU_ATTR_RW);

/* 共享 SRAM:Non-Cacheable + Shareable */
vxMmuMapRegion(0xF0000000,
              0xF0000000,
              0x00040000,   /* 256KB */
              MMU_ATTR_NC | MMU_ATTR_SHARE | MMU_ATTR_RW);

配置完成后,可以通过读取 MMU 相关寄存器或使用 vxMmuShow() 类调试接口查看当前映射表,确认 VA、PA、大小和属性是否与预期一致。如果发现某段地址访问异常,优先检查该段是否被正确映射、权限是否允许、属性是否与访问方式匹配。

实践提示:在调试 DMA 相关问题时,先确认缓冲区映射属性。如果缓冲区是 WBWA,必须在 DMA 启动前 flush CPU 写缓存,在 DMA 完成后 invalidate CPU 读缓存。如果缓冲区是 NC,则无需这些操作,但要注意 NC 区域的访问延迟明显更高,不适合高频小数据量访问。

五、属性选择对性能的量化影响

为了建立直观感受,可以做一个简单的对比测试:在同一块 DDR 上分别以 WBWA 和 NC 属性映射,然后让 CPU 连续读写 1MB 数据,统计耗时。在典型 ARM Cortex-A9 平台上,WBWA 的连续写带宽可达数百 MB/s 到 1GB/s 量级,而 NC 可能只有几十到一百多 MB/s。这个差距在数据密集型任务中会直接体现为响应时间变长、吞吐下降。

因此,映射配置的原则可以归纳为:普通代码和数据用 WBWA;DMA 缓冲区和跨核共享内存用 NC 或 WT;外设寄存器用 Device 属性;只读常量区可以加 XN 禁止执行以提升安全性。每次修改映射后,都要重新验证功能正确性和性能表现,不能只看“能跑通”就结束。

注意:修改 MMU 映射属于底层操作,错误的属性或权限配置可能导致系统立即崩溃或出现难以复现的数据错误。建议在修改前备份当前映射表,修改后先用小范围测试验证,再逐步扩大范围。