4. 内存映射与MMU:内存映射概念、MMU的作用、页表配置、Cache与Buffer管理
好,咱们今天聊点硬核的——内存映射和MMU。说实话,这玩意儿在嵌入式开发里,尤其是跑Linux或者RTOS的时候,几乎是绕不开的。我当年刚接触FCC认证那会儿,就因为在MMU配置上栽了个跟头,导致射频测试一直过不去。后来才发现,是Cache没处理好,数据一致性问题搞的鬼。
4.1 内存映射概念
什么叫内存映射?说白了,就是给CPU看的地址和实际物理地址之间,搭一座桥。CPU发出的地址,叫虚拟地址。真正去读写内存条或者外设寄存器用的地址,叫物理地址。
你想想看,如果没有映射,那每个程序都得知道自己要占哪块物理内存。万一两个程序冲突了,系统直接崩。所以,现代操作系统都搞了这么一层抽象。
核心要点: 内存映射让每个进程都以为自己独占整个地址空间。比如32位系统,每个进程看到的是4GB的连续空间,但实际上物理内存可能只有512MB。
我在项目中遇到过一种情况:某款MCU只有256KB的SRAM,但我们要跑一个需要1MB内存的算法。怎么办?靠内存映射!把一部分数据映射到外部Flash或者SPI RAM上,用的时候再换进来。虽然慢点,但能跑起来。
4.2 MMU的作用
MMU,全称Memory Management Unit,内存管理单元。它是硬件里专门干映射这件事的模块。没有MMU,你只能直接操作物理地址,那叫“实模式”。有了MMU,才能进“保护模式”。
MMU主要干三件事:
- 地址转换: 把虚拟地址转成物理地址。这是基本功。
- 权限控制: 哪些内存可读、可写、可执行。防止程序乱搞。
- 隔离保护: 进程A不能随便访问进程B的内存。这是安全的基础。
嗯,这里要注意。在FCC认证测试中,如果MMU配置不当,可能会导致某些外设的寄存器被意外访问,产生电磁干扰。我曾经就因为这个,多花了两个星期排查。
个人经验: 调试MMU问题时,我习惯先关掉所有优化,直接读写物理地址。确认硬件没问题了,再一步步打开MMU功能。这样定位问题快很多。
4.3 页表配置
MMU怎么知道虚拟地址对应哪个物理地址?靠查表。这个表就叫页表。页表里每一行,叫一个页表项(PTE)。
页表的结构,不同架构不一样。ARM Cortex-A系列常用两级页表,甚至三级。x86也有类似的分页机制。咱们以ARMv7为例,简单看看:
// 一级页表项结构(简化版)
typedef struct {
uint32_t type : 2; // 00=无效, 01=页表, 10=段, 11=保留
uint32_t domain : 4; // 域编号
uint32_t sbz : 1; // 应为0
uint32_t ns : 1; // 非安全位
uint32_t base : 22; // 物理基地址(段映射时)
uint32_t xn : 1; // 禁止执行
uint32_t tex : 3; // 内存类型
uint32_t ap : 2; // 访问权限
} L1_PTE_t;
配置页表时,我建议你注意几个坑:
- 对齐要求: 页表本身必须放在物理地址对齐的位置。比如1MB段映射,页表基地址必须1MB对齐。
- 域权限: ARM的域机制很容易搞混。我刚开始时,把域配成了0x0,结果所有访问都被拒绝,系统直接挂。
- TLB刷新: 修改页表后,一定要刷新TLB(Translation Lookaside Buffer)。否则MMU用的还是旧数据。
避坑指南: 我曾经在FCC预测试时,发现设备在某个频点突然发射超标。查了两天,最后发现是页表里把射频控制器的内存区域配成了Cacheable。Cache把写操作缓存了,导致寄存器写入时序不对,射频芯片工作异常。记住:外设寄存器区域,一定要配成Strongly Ordered或者Device类型,不能Cache。
4.4 Cache与Buffer管理
Cache和Buffer,这两个东西经常被混为一谈。其实它们干的活不一样:
- Cache: 缓存CPU最近用过的数据。目的是减少访问主存的延迟。
- Buffer: 一般是写缓冲,把要写的数据先攒着,再批量写入。目的是提高写效率。
在MMU的页表项里,有专门的位来控制Cache和Buffer策略。ARM里叫TEX、C、B位。组合起来有几种模式:
| TEX[2:0] | C | B | 内存类型 | 说明 |
|---|---|---|---|---|
| 000 | 0 | 0 | Strongly Ordered | 严格顺序,不Cache,不Buffer |
| 000 | 0 | 1 | Device | 外设内存,不Cache,可Buffer |
| 000 | 1 | 0 | Normal, Write-Through | 普通内存,写通 |
| 000 | 1 | 1 | Normal, Write-Back | 普通内存,写回(性能最好) |
你想想看,如果给DMA用的内存配成了Write-Back,那CPU写数据后,数据可能还在Cache里,没到主存。DMA去读主存,读到的就是旧数据。这就是典型的数据一致性问题。
我的做法: 对于DMA缓冲区,我一般配成Normal, Non-Cacheable。或者用Write-Through模式,配合Cache清理操作。具体用哪种,看性能要求。如果数据量小,Non-Cacheable最省心。
最后,说一个实际调试技巧。当你怀疑Cache导致问题时,可以临时把整个内存区域都配成Non-Cacheable。如果问题消失了,那基本就是Cache的锅。然后再逐步优化,把性能敏感的部分改回Cacheable。
嗯,内存映射和MMU这块,内容确实不少。但只要你理解了“虚拟地址到物理地址”这条主线,再搞清楚Cache和Buffer的配置原则,大部分问题都能迎刃而解。我在FCC认证项目里,靠这套方法解决了不少疑难杂症。希望对你也有帮助。