第2章:多核启动流程与同步机制
好,咱们今天聊聊多核启动。说实话,我刚接触TC3xx的时候,也被这玩意儿绕晕过。六个核同时跑,谁先谁后?怎么保证不乱套?今天我就把这块掰开了讲清楚。
2.1 启动序列:谁先醒来?
TC3xx上电后,不是所有核一起跑的。它有个严格的顺序。我习惯把这个过程叫做「点名机制」。
具体来说:
- CPU0永远是第一个启动的。这是硬件决定的,没得商量。
- CPU0跑起来后,负责初始化系统、配置时钟、初始化内存。
- 然后CPU0去「叫醒」其他核。
你想想看,如果所有核同时跑,连堆栈指针都没设好,那不乱套了?所以这个顺序是必须的。
核心要点:CPU0是主核(Master Core),其他都是从核(Slave Core)。主核负责系统级初始化,从核等待被唤醒。
2.2 CPU0主核启动流程
CPU0启动后,它会执行以下步骤。我在项目里调试启动代码时,经常在这几个地方设断点:
- 复位向量跳转:从0xA0000020地址开始执行。
- 初始化堆栈指针:设置SP、PSW等核心寄存器。
- 初始化C运行时环境:清零BSS段、拷贝数据段。
- 配置锁相环(PLL):把系统时钟从默认的慢速时钟切换到目标频率。
- 初始化内存保护单元(MPU):划分各个核的内存区域。
- 启动从核:通过写特定寄存器触发从核启动。
嗯,这里要注意: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个核的同步。
我一般这样用:
- 在关键操作前设置一个同步点(比如访问共享内存前)。
- 所有核到达后,SPB释放它们。
- 然后大家再一起往下执行。
代码示例:
/* 同步点使用示例 */
void sync_all_cores(void)
{
/* 等待所有核到达同步点 */
IfxCpu_syncEvent(&g_syncEvent);
/* 所有核都到齐了,可以安全操作共享资源 */
shared_counter++;
}
关键点:同步点不是万能的。它只能保证「同一时刻」所有核都在这个点上,但不能保证后续执行的顺序。如果你需要严格的顺序控制,还得用信号量或互斥锁。
2.5 实际项目中的经验
我做过一个项目,6个核全用上了。启动顺序和同步点这块,我踩过不少坑。分享几个:
- 启动顺序依赖:CPU2需要CPU1初始化完某个外设才能工作。我就在CPU2的入口函数里加了一个自旋等待,等CPU1设置一个标志位。
- 同步点超时:如果某个核死机了,其他核会永远卡在同步点上。我后来加了超时机制——等太久就报错,然后重启那个核。
- 调试技巧:用逻辑分析仪抓GPIO电平。每个核在到达同步点时拉高一个引脚,离开时拉低。一眼就能看出谁快谁慢。
我的习惯:在同步点前后各加一个调试打印。打印里带上当前核的ID和时间戳。这样出问题时,翻日志就能定位到是哪个核卡住了。
2.6 小结
多核启动和同步,说白了就是「先来后到」和「集合出发」这两件事。CPU0先跑,把系统搭好,然后叫醒其他核。大家跑起来后,在关键地方用同步点碰个头。
记住三点:
- 启动顺序不能乱,CPU0永远是老大。
- 每个从核要有独立的入口函数。
- 同步点要配合超时机制,防止死锁。
下一章咱们聊聊共享资源的保护——那才是真正让人头疼的地方。到时候我会讲讲自旋锁、互斥锁这些玩意儿在实际项目中怎么用。