第4章:核间通信(IPC)实战:中断(SRI)、信号量(Semaphore)、邮箱(Mailbox)机制
各位同学,咱们今天聊点硬核的——多核通信。说实话,我刚开始接触TC3xx多核开发时,最头疼的就是核间通信。你想想看,六个核各自跑各自的,怎么让它们协同工作?总不能靠喊吧?
这一章,我就把我在项目里踩过的坑、总结的经验,掰开揉碎了讲给你们听。咱们重点讲三个机制:SRI中断、信号量、邮箱。这三个东西,说白了就是多核世界的「电话、红绿灯和快递柜」。
4.1 SRI中断——核间打电话
SRI(Service Request Interrupt)是TC3xx最直接的核间通信方式。它的原理很简单:一个核往另一个核的中断控制寄存器里写个值,对方就收到中断了。
我在做ADAS项目时,主核需要通知从核「数据准备好了」,用的就是SRI。当时我犯了个低级错误——没清中断标志位,结果中断一直触发,CPU直接卡死。嗯,这个坑你们一定要记住。
核心要点:
- SRI中断是单向的:Core0可以触发Core1的中断,反之亦然
- 每个核有8个SRI中断源,优先级可配
- 中断触发后,必须手动清除标志位
来看个实际代码片段:
// Core0 触发 Core1 的 SRI 中断
IfxScuSrc_setInterrupt(IfxScuSrc_sel_cpu1, // 目标核
IfxScuSrc_pri_0, // 优先级
IfxScuSrc_sel_sri0); // SRI源
// 触发中断
IfxScuSrc_triggerInterrupt(IfxScuSrc_sel_cpu1, IfxScuSrc_sel_sri0);
// Core1 的中断服务函数
void core1_sri0_isr(void)
{
// 处理数据
// ...
// 清除中断标志——这一步千万别忘!
IfxScuSrc_clearInterrupt(IfxScuSrc_sel_cpu1, IfxScuSrc_sel_sri0);
}
我曾经踩过的坑:
SRI中断的触发是边沿敏感的。如果你连续两次触发,但第一次的中断还没处理完,第二次触发可能会丢失。我的解决方案是:在中断服务函数里用轮询方式检查是否有新请求,而不是依赖中断的再次触发。
4.2 信号量(Semaphore)——多核世界的红绿灯
信号量这东西,说白了就是一把锁。多核共享资源时,谁拿到信号量谁就能用,用完了再还回去。
TC3xx有硬件信号量模块,比软件实现的信号量快得多。我记得第一次用硬件信号量时,还纳闷为什么读写操作要分开——后来才明白,这是为了支持「读-改-写」的原子操作。
4.2.1 硬件信号量的使用
TC3xx提供了16个硬件信号量,每个信号量就是一个特殊的寄存器。你通过读取这个寄存器来获取信号量,写回0来释放。
// 获取信号量(阻塞方式)
uint32 semaphore_value;
do {
semaphore_value = IfxSemaphore_get(IfxSemaphore_0);
} while (semaphore_value != 1); // 1表示获取成功
// 访问共享资源
shared_data++;
// 释放信号量
IfxSemaphore_release(IfxSemaphore_0);
我的个人习惯:
我一般不用阻塞方式获取信号量,而是用超时机制。万一哪个核死锁了,至少还能恢复。代码大概是这样的:
uint32 timeout = 1000;
while (timeout--) {
if (IfxSemaphore_get(IfxSemaphore_0) == 1) {
// 获取成功
break;
}
}
if (timeout == 0) {
// 超时处理——我一般会触发一个错误中断
}
4.2.2 信号量的优先级反转问题
这个问题我在培训时经常被问到。说白了就是:低优先级任务拿着信号量,高优先级任务等着用,结果低优先级任务被中优先级任务抢了CPU,高优先级任务就一直等。
TC3xx的硬件信号量不支持优先级继承,所以你得自己处理。我的做法是:把临界区做得尽量短,别在持有信号量时做复杂计算。
4.3 邮箱(Mailbox)——核间快递柜
邮箱机制,说白了就是个消息队列。一个核往里放消息,另一个核取出来。TC3xx的邮箱是基于共享内存实现的,但加上了硬件保护。
我做过一个项目,需要把雷达数据从Core2传到Core4做融合处理。如果用SRI中断,每次传一个字节太浪费;用共享内存又怕冲突。最后选了邮箱,刚刚好。
4.3.1 邮箱的配置
邮箱需要配置三个东西:消息大小、邮箱深度、中断使能。消息大小一般是32位或64位,深度看你的数据量。
| 参数 | 说明 | 我的建议 |
|---|---|---|
| 消息大小 | 每条消息的字节数 | 32位够用就别用64位,省内存 |
| 邮箱深度 | 最多能存多少条消息 | 一般设成16或32,太大浪费 |
| 中断使能 | 收到消息是否触发中断 | 建议开启,别用轮询 |
// 邮箱初始化
IfxMailbox_Config config;
IfxMailbox_initConfig(&config);
config.txBufferSize = 32; // 发送缓冲区大小
config.rxBufferSize = 32; // 接收缓冲区大小
config.messageSize = 4; // 每条消息4字节
config.interruptEnable = TRUE;
IfxMailbox mailbox;
IfxMailbox_init(&mailbox, &config);
// Core2 发送消息
uint32 msg = 0x12345678;
IfxMailbox_send(&mailbox, &msg, 1); // 1表示阻塞发送
// Core4 接收消息(中断方式)
void mailbox_isr(void)
{
uint32 msg;
IfxMailbox_receive(&mailbox, &msg, 0); // 0表示非阻塞
// 处理消息
}
注意:
邮箱的发送和接收必须在不同的核上。如果你在同一个核上又发又收,那还不如直接用全局变量。另外,邮箱满了之后,阻塞发送会导致当前核卡死——我建议用非阻塞方式,配合重试机制。
4.4 三种机制的选型建议
讲了这么多,到底什么时候用哪个?我根据自己的经验,给个参考:
- SRI中断:适合传递「事件信号」,比如「数据准备好了」、「任务完成了」。数据量小,实时性要求高。
- 信号量:适合保护共享资源,比如全局变量、外设寄存器。注意临界区要短。
- 邮箱:适合传递数据块,比如传感器数据、控制指令。数据量大,需要排队处理。
实际项目中,这三种机制经常配合使用。比如:用信号量保护共享数据,用SRI中断通知对方「数据已更新」,用邮箱传递具体内容。你想想看,是不是很合理?
总结一下我的经验:
多核通信的核心不是技术,而是设计。你得想清楚:谁负责通知?谁负责保护?谁负责传输?把这些理清了,代码写起来就顺了。我见过太多人一上来就写代码,结果调试时发现核间通信乱成一锅粥。
好了,这一章就到这里。下一章咱们讲共享资源的保护策略,包括原子操作和内存屏障。到时候我会分享一个我调试了三天三夜的bug——嗯,那故事可有意思了。