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 实战中的资源保护策略

说了这么多,咱们总结一下实战中的保护策略。我个人习惯用分层保护的思路:

  1. 静态分配:在系统设计阶段,就把外设和内存区域分配给固定核。能静态解决的,就别动态处理。
  2. 硬件保护:用MPU、ACCEN这些硬件机制锁死权限。硬件比软件可靠得多。
  3. 软件同步:对于必须共享的资源,用自旋锁或信号量保护。注意锁的粒度要小,别锁住整个系统。
  4. 陷阱兜底:万一保护失效,陷阱机制能捕获异常,防止系统崩溃。

避坑指南:我曾经在一个项目中,只用了软件互斥锁保护共享资源,没配置MPU。结果一个核的代码跑飞了,直接写坏了另一个核的私有数据。嗯,从那以后,我坚持硬件保护+软件同步双重保险。

好了,共享资源访问与保护就讲到这里。记住一句话:能静态分配就别动态共享,能硬件保护就别依赖软件。下一节咱们聊聊多核调试的实战技巧,到时候见。