第2章:多核启动流程与同步机制

好,咱们今天聊聊多核启动。说实话,我刚接触TC3xx的时候,也被这玩意儿绕晕过。六个核同时跑,谁先谁后?怎么保证不乱套?今天我就把这块掰开了讲清楚。

2.1 启动序列:谁先醒来?

TC3xx上电后,不是所有核一起跑的。它有个严格的顺序。我习惯把这个过程叫做「点名机制」。

具体来说:

  • CPU0永远是第一个启动的。这是硬件决定的,没得商量。
  • CPU0跑起来后,负责初始化系统、配置时钟、初始化内存。
  • 然后CPU0去「叫醒」其他核。

你想想看,如果所有核同时跑,连堆栈指针都没设好,那不乱套了?所以这个顺序是必须的。

核心要点:CPU0是主核(Master Core),其他都是从核(Slave Core)。主核负责系统级初始化,从核等待被唤醒。

2.2 CPU0主核启动流程

CPU0启动后,它会执行以下步骤。我在项目里调试启动代码时,经常在这几个地方设断点:

  1. 复位向量跳转:从0xA0000020地址开始执行。
  2. 初始化堆栈指针:设置SP、PSW等核心寄存器。
  3. 初始化C运行时环境:清零BSS段、拷贝数据段。
  4. 配置锁相环(PLL):把系统时钟从默认的慢速时钟切换到目标频率。
  5. 初始化内存保护单元(MPU):划分各个核的内存区域。
  6. 启动从核:通过写特定寄存器触发从核启动。

嗯,这里要注意:PLL配置这块特别容易翻车。我曾经有一次忘了配置等待时间,结果系统时钟切换后直接死机。后来我养成了习惯——每次改时钟配置,先看数据手册里的「PLL Lock Time」参数。

我的小技巧:在启动代码里加一个LED闪烁函数。每完成一个初始化步骤,就闪一下。调试时一眼就能看出卡在哪一步。

2.3 从核启动机制

从核怎么被唤醒?说白了,就是CPU0往一个叫Core x Boot Register(CBR)的寄存器里写值。

每个从核都有一个对应的CBR寄存器:

从核 CBR地址 启动地址默认值
CPU1 0xF003E000 0xA0000020
CPU2 0xF003E004 0xA0000020
CPU3 0xF003E008 0xA0000020
CPU4 0xF003E00C 0xA0000020
CPU5 0xF003E010 0xA0000020

CPU0把从核的入口地址写到CBR里,然后从核就开始从那个地址执行了。我见过有人直接把所有从核的入口地址写成一样的——结果所有从核跑同一份代码,互相抢资源,那叫一个惨。

避坑指南:每个从核应该有自己的入口函数。我曾经把CPU1和CPU2的入口设成同一个函数,结果两个核同时操作同一个全局变量,数据全乱了。后来我改成每个核一个独立的启动函数,问题就解决了。

代码示例,给每个从核分配不同的入口:

/* CPU0主核启动代码 */
void core0_main(void)
{
    /* 系统初始化... */
    
    /* 启动从核 */
    IfxCpu_setCoreBootAddress(&IfxCpu_cfg_core1, (uint32)core1_entry);
    IfxCpu_setCoreBootAddress(&IfxCpu_cfg_core2, (uint32)core2_entry);
    IfxCpu_setCoreBootAddress(&IfxCpu_cfg_core3, (uint32)core3_entry);
    
    /* 触发从核启动 */
    IfxCpu_startCore(&IfxCpu_cfg_core1);
    IfxCpu_startCore(&IfxCpu_cfg_core2);
    IfxCpu_startCore(&IfxCpu_cfg_core3);
    
    /* 主核继续执行... */
}

/* CPU1从核入口 */
void core1_entry(void)
{
    /* CPU1专属初始化 */
    init_core1_peripherals();
    while(1) { /* CPU1主循环 */ }
}

/* CPU2从核入口 */
void core2_entry(void)
{
    /* CPU2专属初始化 */
    init_core2_peripherals();
    while(1) { /* CPU2主循环 */ }
}

2.4 同步点(Synchronization Point)机制

好,现在所有核都跑起来了。但问题来了——它们跑的速度不一样。为什么?因为每个核可能跑在不同的时钟域,或者执行路径不同。这时候就需要同步点了。

同步点,说白了就是一个「集合哨」。所有核跑到这个点后,必须等其他人到齐了,才能继续往下走。

TC3xx提供了硬件同步单元——SPB(Synchronization Point Block)。它支持最多6个核的同步。

我一般这样用:

  1. 在关键操作前设置一个同步点(比如访问共享内存前)。
  2. 所有核到达后,SPB释放它们。
  3. 然后大家再一起往下执行。

代码示例:

/* 同步点使用示例 */
void sync_all_cores(void)
{
    /* 等待所有核到达同步点 */
    IfxCpu_syncEvent(&g_syncEvent);
    
    /* 所有核都到齐了,可以安全操作共享资源 */
    shared_counter++;
}

关键点:同步点不是万能的。它只能保证「同一时刻」所有核都在这个点上,但不能保证后续执行的顺序。如果你需要严格的顺序控制,还得用信号量或互斥锁。

2.5 实际项目中的经验

我做过一个项目,6个核全用上了。启动顺序和同步点这块,我踩过不少坑。分享几个:

  • 启动顺序依赖:CPU2需要CPU1初始化完某个外设才能工作。我就在CPU2的入口函数里加了一个自旋等待,等CPU1设置一个标志位。
  • 同步点超时:如果某个核死机了,其他核会永远卡在同步点上。我后来加了超时机制——等太久就报错,然后重启那个核。
  • 调试技巧:用逻辑分析仪抓GPIO电平。每个核在到达同步点时拉高一个引脚,离开时拉低。一眼就能看出谁快谁慢。

我的习惯:在同步点前后各加一个调试打印。打印里带上当前核的ID和时间戳。这样出问题时,翻日志就能定位到是哪个核卡住了。

2.6 小结

多核启动和同步,说白了就是「先来后到」和「集合出发」这两件事。CPU0先跑,把系统搭好,然后叫醒其他核。大家跑起来后,在关键地方用同步点碰个头。

记住三点:

  • 启动顺序不能乱,CPU0永远是老大。
  • 每个从核要有独立的入口函数。
  • 同步点要配合超时机制,防止死锁。

下一章咱们聊聊共享资源的保护——那才是真正让人头疼的地方。到时候我会讲讲自旋锁、互斥锁这些玩意儿在实际项目中怎么用。