第四讲:内存管理与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的基本配置步骤

  1. 使能MPU:通过系统控制寄存器开启MPU功能。
  2. 定义内存区域:设置起始地址、结束地址、访问权限。
  3. 分配区域编号:每个区域有一个编号,最多支持16个区域。
  4. 设置访问权限:读、写、执行权限可以单独控制。
  5. 使能区域:激活配置。

来看一个实际的配置代码:

/* 配置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(&regionConfig);          /* 初始化区域 */
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配置这块,说白了就是给系统上规矩。规矩定好了,系统就稳定。规矩没定好,出问题只是时间问题。希望今天的分享能帮你少走弯路。

下一讲,我们会聊聊中断系统与多核中断分配。到时候见。