4. 核间通信机制(IPC):MessageQ、Notify、SharedRegion原理与配置

多核DSP系统里,核间通信是个绕不开的坎儿。说白了,就是让多个核心能“说上话”,能“递东西”。我刚开始接触多核开发时,总觉得把任务分给几个核跑就完事了,结果发现数据对不上、同步乱套,调试起来那叫一个头大。

后来我慢慢摸清了门道。TI的IPC(Inter-Processor Communication)组件,提供了三样核心工具:MessageQNotifySharedRegion。今天咱们就把这三样东西掰开揉碎,讲清楚它们是什么、怎么配、怎么用。

核心观点:IPC不是银弹,选对机制比写对代码更重要。MessageQ适合大数据块传输,Notify适合轻量级信号通知,SharedRegion则是底层共享内存的基石。

4.1 先画张图:IPC整体架构

我习惯先看整体,再抠细节。下面这张图,是我自己总结的IPC三层架构:

IPC 核间通信三层架构 应用层 MessageQ(消息队列) | Notify(硬件通知) 传输层 Transport(底层驱动封装) | 多核中断管理 共享资源层 SharedRegion(共享内存管理) | 硬件信号量 每个核心通过共享内存 + 硬件中断实现数据交换与同步

你看,最上层是应用开发者直接打交道的API,中间层封装了底层硬件差异,最下层是实实在在的共享内存和中断资源。嗯,这里要注意:SharedRegion是所有通信的基础,没有它,MessageQ和Notify都玩不转。

4.2 SharedRegion:共享内存的“房东”

SharedRegion说白了就是一块被多个核心都能访问的内存区域。我把它比作“合租房的客厅”——谁都能用,但得守规矩。

4.2.1 原理

每个SharedRegion有一个唯一的ID,从0开始编号。系统启动时,由主核(通常是Core0)负责初始化,把物理地址映射到各个核心的虚拟地址空间。其他核心通过ID就能拿到这块内存的指针。

我的经验:SharedRegion的地址对齐很关键。我曾经因为没做64字节对齐,导致Cache一致性出了问题,数据读出来全是乱的。后来我养成了习惯:共享内存的起始地址和大小,都按Cache Line大小(通常是64字节或128字节)对齐。

4.2.2 配置示例

在BIOS的配置文件(.cfg)里,SharedRegion的配置长这样:

// 在.cfg文件中配置SharedRegion
var SharedRegion = xdc.useModule('ti.sdo.ipc.SharedRegion');

// 配置Region 0:用于核间大数据交换
SharedRegion.setEntryMeta(0, {
    base: 0x80000000,        // 物理基地址
    len: 0x00100000,         // 1MB大小
    ownerProcId: 0,          // 由Core0管理
    isValid: true,
    name: "DATA_REGION",
    cacheEnable: true        // 启用Cache
});

// 配置Region 1:用于控制信息
SharedRegion.setEntryMeta(1, {
    base: 0x80100000,
    len: 0x00010000,         // 64KB
    ownerProcId: 0,
    isValid: true,
    name: "CTRL_REGION",
    cacheEnable: false       // 控制信息不用Cache
});

避坑指南:我曾经把控制区和数据区混在一起,结果调试时发现控制信息被数据冲刷掉了。建议把控制信息和数据信息放在不同的SharedRegion里,控制区甚至可以关闭Cache,避免一致性问题的干扰。

4.3 MessageQ:核间“快递系统”

MessageQ是IPC里最常用的通信方式。它提供了一种消息队列机制,一个核往队列里“投递”消息,另一个核从队列里“收取”消息。你想想看,这不就是快递系统吗?

4.3.1 原理

MessageQ的核心概念有三个:

  • 队列(Queue):每个队列有一个唯一的名称,接收方负责创建,发送方通过名称打开。
  • 消息(Message):消息体存放在SharedRegion里,发送方分配,接收方释放。
  • 传输(Transport):底层通过硬件中断触发接收方从队列中取消息。

我习惯用一张表来对比MessageQ和普通队列的区别:

特性 MessageQ 普通队列
跨核能力 原生支持,通过SharedRegion 仅限单核
消息传递 传递指针,数据不拷贝 通常拷贝数据
阻塞/非阻塞 支持阻塞等待和轮询 通常非阻塞
优先级 支持消息优先级 一般FIFO

4.3.2 代码示例:发送与接收

下面是一个典型的MessageQ使用流程。我在项目中经常用这个模板:

// 接收端(Core1):创建队列并等待消息
MessageQ_Params params;
MessageQ_Params_init(&params);
MessageQ_Handle queue = MessageQ_create("CORE1_QUEUE", &params);
if (queue == NULL) {
    // 处理错误
}

// 循环接收消息
while (1) {
    MessageQ_Msg msg;
    // 阻塞等待消息,超时设为BIOS_WAIT_FOREVER
    Int status = MessageQ_get(queue, &msg, BIOS_WAIT_FOREVER);
    if (status == MessageQ_S_SUCCESS) {
        // 处理消息
        processMessage(msg);
        // 释放消息内存
        MessageQ_free(msg);
    }
}

// 发送端(Core0):打开队列并发送消息
MessageQ_Handle remoteQueue;
String queueName = "CORE1_QUEUE";
remoteQueue = MessageQ_open(queueName);
if (remoteQueue == NULL) {
    // 处理错误
}

// 分配消息内存(从SharedRegion分配)
MessageQ_Msg msg = (MessageQ_Msg)Memory_alloc(
    NULL, 
    sizeof(MyMessage), 
    0, 
    NULL
);
// 填充消息内容
msg->dstId = 1;
msg->dataLen = 256;
// 发送
MessageQ_put(remoteQueue, msg);

我的习惯:发送消息时,我会在消息头里加一个“消息类型”字段。这样接收端收到后,根据类型做不同的处理,不用每次都解析整个消息体。这个习惯帮我省了不少调试时间。

4.4 Notify:轻量级的“敲门砖”

Notify跟MessageQ不一样。它不传数据,只发一个“信号”。说白了就是敲敲门告诉对方:“嘿,有活儿干了!”

4.4.1 原理

Notify基于硬件中断实现。每个核心可以注册多个事件ID(Event ID),发送方触发某个事件ID,接收方对应的回调函数就会被执行。整个过程非常快,延迟通常在微秒级。

我一般这样用Notify:

  • 事件ID 0-3:留给系统级同步,比如启动、停止、复位。
  • 事件ID 4-7:用于数据就绪通知,配合MessageQ使用。
  • 事件ID 8-15:留给应用自定义,比如状态变化、错误报警。

4.4.2 配置与使用

// 接收端(Core1):注册Notify回调
#include <ti/sdo/ipc/Notify.h>

Void myNotifyFxn(UInt16 procId, UInt16 lineId, UInt32 eventId, 
                 UArg arg, UArg payload) {
    // 收到通知后的处理
    if (eventId == 4) {
        // 数据就绪,去MessageQ取数据
        processDataReady();
    }
}

// 注册事件ID 4,绑定回调函数
Notify_registerEvent(0, 0, 4, myNotifyFxn, NULL);

// 发送端(Core0):触发通知
// 参数:目标核心ID=1,线路ID=0,事件ID=4
Notify_sendEvent(1, 0, 4, 0, TRUE);

避坑指南:我曾经在Notify回调函数里做了太多事情,导致中断响应时间过长,影响了系统实时性。记住:Notify回调里只做“标记”,不做“处理”。把实际工作放到任务(Task)里去做,用信号量或事件标志来触发。

4.5 三者的配合使用

在实际项目中,我很少只用一种机制。最常见的组合是:

  1. Notify做“敲门”:Core0处理完数据后,发一个Notify给Core1,告诉它“数据准备好了”。
  2. MessageQ做“递货”:Core1收到通知后,从MessageQ里取出数据指针,直接处理。
  3. SharedRegion做“仓库”:数据本身存放在SharedRegion里,两个核通过指针访问,零拷贝。

这个组合的好处是:Notify的延迟极低,MessageQ的队列管理可靠,SharedRegion的零拷贝效率高。我在一个4核DSP的项目里用这个方案,核间通信延迟控制在5微秒以内,吞吐量达到了800Mbps。

4.6 配置要点总结

最后,我把配置时的几个关键点列出来,供你参考:

组件 关键配置项 我的建议
SharedRegion base, len, ownerProcId, cacheEnable 控制区关Cache,数据区开Cache;地址64字节对齐
MessageQ 队列名称,消息池大小,超时时间 消息池大小设为最大并发消息数的2倍,避免内存碎片
Notify 事件ID分配,回调函数注册 事件ID做好规划,回调函数保持轻量

嗯,IPC这部分内容就讲到这里。记住一句话:选对机制,配好参数,多核通信其实没那么难。我在后续章节里还会结合具体案例,带你一步步搭建完整的多核通信系统。

核心回顾:

  • SharedRegion是共享内存的基础,配置时注意对齐和Cache策略。
  • MessageQ适合大数据块传输,支持阻塞/非阻塞模式。
  • Notify基于硬件中断,延迟极低,适合做信号通知。
  • 实际项目中,三者配合使用效果最佳。
专注资料整理