4、内存系统:程序内存(PMU)、数据内存(LMU/DSPR/PSPR)、缓存一致性(Cache Coherency)
各位同学,咱们今天聊聊TC3xx的内存系统。说实话,这块内容我当年刚接触时也绕了不少弯路。你想想看,一个芯片里塞了那么多核,每个核都有自己的小算盘(本地内存),还得共享大仓库(全局内存),这中间要是没点规矩,数据非得乱套不可。
我个人习惯把TC3xx的内存系统分成三块来看:程序怎么存、数据怎么放、多核之间怎么保证数据不打架。咱们一个一个说。
4.1 程序内存:PMU(Program Memory Unit)
PMU,说白了就是程序代码的“大本营”。它负责管理Flash和程序RAM。我在项目中遇到过好几次,代码跑着跑着突然卡死,最后查出来是PMU的配置没搞对,访问时序出了问题。
PMU的核心职责有两点:
- Flash读取与预取:CPU取指令时,PMU会提前把后面几条指令也捞出来,这叫预取(Prefetch)。嗯,这里要注意,预取策略如果太激进,反而会浪费带宽。
- 写操作管理:Flash写入很慢,PMU会帮你缓冲一下,但别指望它能像RAM一样快。
重要概念:PMU内部有多个Bank,支持并行访问。如果你的代码能巧妙分布在不同的Bank里,取指效率能提升不少。我有个项目就是靠这个优化,把中断响应时间砍掉了30%。
4.2 数据内存:LMU、DSPR、PSPR
数据内存这块,TC3xx玩得挺花。它把数据分成了几个不同的“房间”,每个房间的用途和速度都不一样。
4.2.1 LMU(Local Memory Unit)
LMU是全局共享的数据内存,所有核都能访问。它挂在系统总线上,速度中等。我建议把那些需要多核共享的数据结构放在这里,比如通信缓冲区、全局状态标志。
4.2.2 DSPR(Data Scratch-Pad RAM)
DSPR是每个CPU核私有的“小本本”,速度极快,访问它不需要经过总线仲裁。你想想看,如果每个核都把最常用的局部变量放在自己的DSPR里,那效率能不高吗?
我曾经在一个项目中,把关键任务的栈和频繁访问的全局变量都映射到了DSPR里,结果任务切换时间直接减半。当然,前提是你得算好大小,DSPR总共也就那么几十KB。
4.2.3 PSPR(Program Scratch-Pad RAM)
PSPR是给程序用的本地RAM,作用和DSPR类似,但它是用来放代码的。有些时间敏感的代码段,比如中断服务程序,放在PSPR里跑,能避免Flash访问的延迟。
我的小技巧:把最紧急的中断处理函数放到PSPR里,把最常用的数据变量放到DSPR里。这样CPU几乎不用等内存,性能直接拉满。
4.3 缓存一致性(Cache Coherency)
好,重头戏来了。多核编程最头疼的问题之一,就是缓存一致性。为什么会这样?
每个核都有自己的缓存(Cache),它会把主存里的数据复制一份到本地。如果核A改了缓存里的数据,核B不知道,还拿着旧数据在那算,结果就全错了。
TC3xx用的是MESI协议来维护一致性。简单说,每个缓存行有四种状态:
| 状态 | 含义 | 说明 |
|---|---|---|
| M(Modified) | 已修改 | 数据被当前核改了,还没写回主存 |
| E(Exclusive) | 独占 | 只有当前核有这份数据,且和主存一致 |
| S(Shared) | 共享 | 多个核都有这份数据,且和主存一致 |
| I(Invalid) | 无效 | 数据已过时,不能再用 |
当核A要写一个数据时,它会先发信号告诉其他核:“嘿,你们手里的那份作废了!”其他核收到后,把对应的缓存行标记为I(无效)。下次核B再读,发现是无效的,就得重新从主存里拿最新的。
避坑指南:我曾经在一个项目中,两个核共享一个计数器,没做任何同步。结果核A加了1,核B读到的还是旧值。查了两天才发现是缓存一致性问题。从那以后,凡是多核共享的变量,我必加volatile关键字,必要时还会用内存屏障指令。
4.4 实际编程中的注意事项
说了这么多理论,咱们来点实际的。在多核环境下写代码,你得时刻想着“数据在哪里”。
- 局部变量:默认放在栈里,栈在DSPR里,所以访问很快。别担心。
- 全局变量:默认放在LMU里,所有核都能看到。但要注意缓存一致性问题。
- 常量:编译器可能会把它放到Flash里,通过PMU访问。如果你频繁访问某个常量,可以考虑把它拷贝到DSPR里。
嗯,最后再啰嗦一句。TC3xx的内存系统虽然复杂,但只要你理解了每个部件的用途和限制,写起代码来就能游刃有余。我刚开始做多核项目时,总觉得内存不够用,后来发现是没用好DSPR和PSPR。说白了,把对的数据放在对的地方,比单纯追求大内存更重要。
下一章咱们聊聊中断系统,那可是多核实时性的命脉。