第四讲:内存管理与MPU配置
各位同学,咱们今天聊聊TC3xx的内存管理。说实话,这块内容我第一次接触时也头大——又是Local Memory又是Global Memory,再加上MPU保护,光概念就绕得人晕。但别急,我带你一步步拆开看。
4.1 TC3xx的内存分区
TC3xx的内存布局,说白了就是一张地图。这张地图上,每个区域都有自己明确的用途。我个人习惯把整个内存空间分成三大块:
- 程序内存(PMU):存放代码的地方。包括PFlash和DLMU(Data Local Memory Unit)的一部分。
- 数据内存(DMU):存放变量、堆栈的地方。包括DLMU、SPRAM(Scratch Pad RAM)等。
- 外设内存空间:映射到各个外设模块的寄存器地址。
嗯,这里要注意一点:TC3xx的地址空间是统一编址的。什么意思?就是CPU看内存和外设,用的是一套地址体系。你读写0x60000000和读写0xF0000000,对CPU来说都是访问内存,只不过背后连的硬件不同。
核心要点:TC3xx的内存映射是固定的,但你可以通过配置寄存器来改变某些区域的访问权限。这就是MPU要做的事。
4.2 Local Memory vs Global Memory
这两个概念,我当年学的时候也纠结了很久。说白了:
- Local Memory:每个CPU核私有的内存。其他核不能直接访问。
- Global Memory:所有CPU核都能访问的共享内存。
举个例子:CPU0的Local Memory就像你家里的卧室,别人不能随便进。而Global Memory就像客厅,谁都能来坐坐。
我在项目中遇到过一个问题:两个核同时写同一个Global Memory地址,结果数据乱掉了。后来加了信号量才解决。你想想看,多核编程里,共享资源的保护有多重要。
| 特性 | Local Memory | Global Memory |
|---|---|---|
| 访问权限 | 仅限所属核 | 所有核可访问 |
| 访问速度 | 最快(无总线仲裁) | 稍慢(需总线仲裁) |
| 典型用途 | 中断栈、关键变量 | 共享数据、通信缓冲区 |
| 大小 | 通常较小(几十KB) | 较大(几百KB到MB) |
我的建议:把每个核的私有数据放在Local Memory里,共享数据才放Global Memory。这样既能保证速度,又能减少冲突。
4.3 MPU保护单元配置
MPU(Memory Protection Unit)是干嘛的?说白了就是给内存上锁。你可以规定:CPU0只能访问哪些地址,CPU1只能访问哪些地址。谁越界了,就触发异常。
为什么要这么做?我记得有一次调试,一个核的野指针写到了另一个核的代码区,结果程序跑飞了。查了两天才找到原因。从那以后,我每个项目都必配MPU。
4.3.1 MPU的基本配置步骤
- 使能MPU:通过系统控制寄存器开启MPU功能。
- 定义内存区域:设置起始地址、结束地址、访问权限。
- 分配区域编号:每个区域有一个编号,最多支持16个区域。
- 设置访问权限:读、写、执行权限可以单独控制。
- 使能区域:激活配置。
来看一个实际的配置代码:
/* 配置CPU0的MPU区域0:保护PFlash代码区 */
Ifx_Mpu_RegionConfig regionConfig;
regionConfig.region = 0; /* 区域编号 */
regionConfig.startAddress = 0x80000000; /* 起始地址 */
regionConfig.endAddress = 0x8003FFFF; /* 结束地址(256KB) */
regionConfig.readAccess = Ifx_Mpu_Access_Enabled; /* 允许读 */
regionConfig.writeAccess = Ifx_Mpu_Access_Disabled; /* 禁止写 */
regionConfig.executeAccess = Ifx_Mpu_Access_Enabled;/* 允许执行 */
Ifx_Mpu_initRegion(®ionConfig); /* 初始化区域 */
Ifx_Mpu_enableRegion(0); /* 使能区域 */
Ifx_Mpu_enableModule(); /* 使能MPU模块 */
警告:配置MPU时,一定要先使能区域,再使能模块。顺序反了会导致配置不生效。我曾经因为这个bug查了一整天。
4.3.2 多核场景下的MPU配置
在多核系统里,每个核都有自己的MPU。你可以给每个核配置不同的保护策略。比如:
- CPU0:可以读写所有Global Memory,但只能执行自己的代码区。
- CPU1:只能读写自己的Local Memory和部分Global Memory。
- CPU2:只能读写自己的Local Memory,不能访问其他区域。
这样做的好处是什么?你想想看,如果一个核的代码出问题了,它最多只能破坏自己权限范围内的内存,不会影响到其他核。这就是隔离性。
4.3.3 常见配置场景
| 场景 | 配置建议 | 说明 |
|---|---|---|
| 代码区保护 | 只读+可执行 | 防止代码被意外修改 |
| 关键数据保护 | 只读(非本核) | 其他核不能修改你的数据 |
| 堆栈溢出检测 | 在堆栈末尾设置保护区 | 堆栈溢出时触发异常 |
| 外设寄存器保护 | 仅允许特定核访问 | 防止外设配置冲突 |
避坑指南:我曾经在配置堆栈保护时,把保护区设得太小了。结果正常运行时没问题,一遇到深度函数调用就触发MPU异常。后来我把保护区扩大到堆栈大小的1/4,才稳定下来。
4.4 实战经验总结
说了这么多,我总结几条实战经验:
- 先规划,后编码:在写代码之前,先把内存分区图画出来。每个核用哪些区域,共享区域怎么保护,都要想清楚。
- MPU配置要尽早:在系统初始化阶段就配好MPU。不要等到跑起来再配,那时候可能已经出问题了。
- 测试要全面:写几个测试用例,故意越界访问,看看MPU能不能正确触发异常。我每次都会做这个测试,确保保护机制有效。
- 调试时可以先关MPU:在开发初期,可以先关闭MPU,等基本功能调通了再打开。不然MPU异常和程序bug混在一起,很难排查。
嗯,内存管理和MPU配置这块,说白了就是给系统上规矩。规矩定好了,系统就稳定。规矩没定好,出问题只是时间问题。希望今天的分享能帮你少走弯路。
下一讲,我们会聊聊中断系统与多核中断分配。到时候见。