第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——嗯,那故事可有意思了。