4. ARMv7 内存模型:内存类型、属性与序模型
内存模型这东西,听起来很抽象。但说白了,它决定了你的 CPU 怎么看待外面的世界——也就是内存和外设。我早年做第一块 Cortex-A8 板子时,就因为搞混了 Device 和 Strongly Ordered,导致 DMA 传数据死活不对。嗯,从那以后我再也不敢小看这一章了。
ARMv7 的内存模型,核心就三件事:内存类型、内存属性、内存序模型。你把这三点理清了,MMU 配置就成功了一半。
4.1 三种内存类型
ARMv7 把整个地址空间分成三大类。每种类型的行为完全不同,尤其是对缓存和访问顺序的处理。
| 内存类型 | 典型用途 | 缓存支持 | 访问序保证 |
|---|---|---|---|
| Normal | DDR、Flash、普通 RAM | 可 Cacheable + Bufferable | 弱序,可重排 |
| Device | 内存映射外设、FIFO、控制寄存器 | 不可 Cacheable,可 Bufferable | 强序,但允许合并写 |
| Strongly Ordered | 中断控制器、系统控制协处理器 | 不可 Cacheable,不可 Bufferable | 严格序,完全保序 |
4.1.1 Normal 内存
Normal 内存,就是咱们最常见的 DDR 和 Flash。它的特点是:CPU 可以随便缓存、随便乱序执行、甚至可以预取。你想想看,如果每次读内存都要等几百个周期,那性能就完蛋了。所以 Normal 内存给了 CPU 最大的优化空间。
我个人习惯把 Normal 内存再细分成两种场景:
- Normal 可缓存:代码段、数据段、堆栈。性能敏感,必须开缓存。
- Normal 不可缓存:某些调试缓冲区、共享内存。避免缓存一致性问题。
4.1.2 Device 内存
Device 内存,说白了就是外设的寄存器空间。比如 UART、GPIO、I2C 的控制寄存器。这类内存有个特点:读或写一次,可能就改变了硬件状态。你不能让 CPU 随便缓存它,否则写一次寄存器,结果被缓存了没发出去,外设就罢工了。
Device 内存的访问规则:
- 不可 Cacheable(绝对不能缓存)
- 可以 Bufferable(允许写缓冲合并)
- 访问序:同一地址的访问保持顺序,不同地址之间可能重排
4.1.3 Strongly Ordered 内存
Strongly Ordered 是最严格的内存类型。它既不能缓存,也不能缓冲。每次访问都直接打到总线上,而且严格保序。说白了,你写 A 再写 B,硬件保证 A 一定在 B 之前完成。
什么时候用?
- 中断控制器(GIC)的寄存器
- 系统控制协处理器(CP15)
- 跨核同步用的标志位
- 任何对时序极其敏感的外设
代价呢?性能极差。每次访问都要等总线事务完成,CPU 被 stall 住。所以别滥用,只在必要的地方用。
4.2 内存属性:Cacheable 与 Bufferable
这两个属性,是 MMU 页表描述符里的两个比特位。但它们决定了整个系统的行为。
| 属性 | 含义 | 影响 |
|---|---|---|
| Cacheable | 允许数据进入 CPU 缓存 | 读命中快,但存在一致性问题 |
| Bufferable | 允许写操作在写缓冲中合并 | 写性能提升,但可能延迟到达外设 |
组合起来有四种情况:
- Cacheable + Bufferable:Normal 内存的标配。性能最好。
- Cacheable + Non-bufferable:很少用。缓存了但写不缓冲,每次写都直接发出去。
- Non-cacheable + Bufferable:Device 内存的典型配置。不缓存,但允许写合并。
- Non-cacheable + Non-bufferable:Strongly Ordered 的配置。最慢,但最安全。
4.3 内存序模型
内存序,就是 CPU 访问内存的顺序。ARMv7 是弱序模型。什么意思?就是 CPU 和编译器可以自由重排内存访问,只要不影响单线程的正确性。这对性能是好事,但对多核编程和外设驱动是噩梦。
三种序模型对比:
| 模型 | 架构 | 重排自由度 | 编程难度 |
|---|---|---|---|
| 强序 | x86 | 低 | 低 |
| 弱序 | ARM、PowerPC | 高 | 高 |
| 严格序 | Strongly Ordered 区域 | 无 | 低 |
为什么会这样?ARM 的设计哲学是:把重排的自由留给硬件,把同步的责任交给软件。你想想看,如果每次内存访问都严格保序,那 CPU 的乱序执行、写缓冲、预取就全废了。性能损失至少 30%。
那怎么保证顺序?用内存屏障指令:
- DMB(数据内存屏障):保证屏障前的所有内存访问在屏障后的访问之前完成。
- DSB(数据同步屏障):比 DMB 更严格,等待所有缓存、写缓冲、TLB 操作都完成。
- ISB(指令同步屏障):刷新流水线,保证之后的指令都从内存或缓存重新取。
4.4 知识结构总览
下面这张图,把内存类型、属性和序模型的关系串起来了。你可以把它当作配置 MMU 时的速查表。
4.5 避坑指南
最后,分享几个我踩过的坑:
- 别把外设配成 Normal 内存:我见过有人把 UART 寄存器配成 Normal + Cacheable,结果写一个字符,缓存没刷,串口半天没反应。
- 共享内存慎用 Bufferable:两个核共享一个标志位,如果开了 Bufferable,写操作可能被缓冲,另一个核读到的永远是旧值。
- 中断服务程序里用 DSB:在 ISR 里访问外设寄存器前,加一条 DSB,保证之前的所有内存操作都已完成。否则可能读到脏数据。
- DMA 描述符用 Non-cacheable:或者用 cache 但手动 clean。否则 CPU 更新了描述符,DMA 看到的还是缓存里的旧数据。