3. 共享资源访问与保护:共享内存、外设寄存器、总线仲裁、陷阱机制
好,咱们进入第三个核心话题。共享资源,说白了就是多个核要抢着用的东西。内存、外设、总线,哪个不是?你想想看,如果两个核同时往一个地址写数据,结果会怎样?嗯,数据就乱套了。我在项目里见过太多次这种问题,调试起来真是让人头大。
3.1 共享内存的保护策略
共享内存是最常见的共享资源。TC3xx里,所有核都能访问整个地址空间,但这不代表你可以随便写。我个人习惯,先把共享内存区域规划好,再用硬件机制锁死。
关键手段:
- 内存保护单元(MPU):每个核都有自己的MPU,可以设置读写权限。我建议你把共享内存区设为“只读”,只有特定核能写。
- 对齐访问:共享变量一定要对齐到4字节或8字节边界。否则,一个核读一半,另一个核写一半,数据就撕裂了。
- 原子操作:TC3xx支持LDREX/STREX指令,可以实现无锁编程。但要注意,这只能保护单个变量,不能保护复杂数据结构。
避坑指南:我曾经在一个项目中,两个核共享一个结构体。我用了原子操作保护单个成员,但结构体整体没保护。结果一个核在修改成员A时,另一个核读了整个结构体,拿到了半新半旧的数据。嗯,从那以后,我只要涉及共享结构体,一律用互斥锁。
3.2 外设寄存器的访问保护
外设寄存器比共享内存更敏感。你想想看,如果两个核同时配置同一个定时器,会发生什么?寄存器值来回跳,硬件状态就不可控了。
TC3xx的解决方案:
- 访问使能寄存器(ACCEN):每个外设模块都有ACCEN寄存器,可以指定哪些核能访问。我习惯在初始化阶段就把外设分配给固定核,其他核碰都不让碰。
- 读写保护位:有些关键寄存器有写保护位,必须先解锁才能写。这能防止意外修改。
- 影子寄存器:部分外设有影子寄存器,写入后会在安全时刻同步。这能避免多个核同时写导致的冲突。
我的经验:对于ADC、DMA这类高速外设,我建议只让一个核控制。其他核如果需要数据,通过共享内存或消息队列来拿。这样能大大降低调试难度。
3.3 总线仲裁机制
总线仲裁,说白了就是多个核抢总线时的裁判规则。TC3xx用的是轮询优先级仲裁,每个核有固定的优先级,优先级高的先拿到总线。
仲裁规则:
| 核编号 | 默认优先级 | 说明 |
|---|---|---|
| CPU0 | 最高 | 通常用于实时控制 |
| CPU1 | 次高 | 可用于通信协议栈 |
| CPU2 | 中等 | 适合后台任务 |
| CPU3~5 | 较低 | 用于非关键任务 |
但要注意,优先级高不代表总能抢到。如果低优先级核正在执行一个长总线事务(比如DMA传输),高优先级核也得等。这就是总线锁定问题。
警告:我曾经遇到一个案例,CPU0优先级最高,但CPU2启动了一个DMA传输,占用了总线长达几十微秒。CPU0的中断响应被严重延迟,导致控制环路崩溃。解决办法是:把DMA传输拆成小块,或者给CPU0预留专用总线带宽。
3.4 陷阱(Trap)机制
陷阱是TC3xx的硬件异常处理机制。当程序访问了不该访问的资源,或者发生了总线错误,CPU就会触发陷阱。嗯,这其实是保护机制的最后一道防线。
常见的陷阱类型:
- 内存访问陷阱:访问了未映射的地址,或者MPU权限不足。
- 总线错误陷阱:总线事务超时或目标设备无响应。
- 对齐陷阱:非对齐访问触发的异常。
- 保护陷阱:访问了受保护的外设寄存器。
陷阱处理流程:
// 陷阱向量表配置示例
void Trap_Init(void)
{
// 设置陷阱处理函数
__set_TrapEntry(TRAP_CLASS_MMU, MyMMUFaultHandler);
__set_TrapEntry(TRAP_CLASS_PROT, MyProtectionHandler);
// 使能陷阱
__enable_Trap(TRAP_CLASS_MMU);
__enable_Trap(TRAP_CLASS_PROT);
}
// 内存访问陷阱处理
void MyMMUFaultHandler(uint32 trapInfo)
{
// 记录错误地址
uint32 faultAddr = __get_MMUFaultAddress();
// 记录错误类型
uint32 faultType = __get_MMUFaultType();
// 根据错误类型做处理
switch(faultType)
{
case MMU_FAULT_READ:
// 读保护违规
break;
case MMU_FAULT_WRITE:
// 写保护违规
break;
default:
// 其他错误,复位系统
SystemReset();
break;
}
}
我的建议:陷阱处理函数一定要简单快速。我见过有人把陷阱处理写成几百行,结果嵌套陷阱又触发了。嗯,正确的做法是:记录错误信息,然后要么恢复,要么安全复位。别在陷阱里做复杂操作。
3.5 实战中的资源保护策略
说了这么多,咱们总结一下实战中的保护策略。我个人习惯用分层保护的思路:
- 静态分配:在系统设计阶段,就把外设和内存区域分配给固定核。能静态解决的,就别动态处理。
- 硬件保护:用MPU、ACCEN这些硬件机制锁死权限。硬件比软件可靠得多。
- 软件同步:对于必须共享的资源,用自旋锁或信号量保护。注意锁的粒度要小,别锁住整个系统。
- 陷阱兜底:万一保护失效,陷阱机制能捕获异常,防止系统崩溃。
避坑指南:我曾经在一个项目中,只用了软件互斥锁保护共享资源,没配置MPU。结果一个核的代码跑飞了,直接写坏了另一个核的私有数据。嗯,从那以后,我坚持硬件保护+软件同步双重保险。
好了,共享资源访问与保护就讲到这里。记住一句话:能静态分配就别动态共享,能硬件保护就别依赖软件。下一节咱们聊聊多核调试的实战技巧,到时候见。