3、多核通信机制(下):硬件队列(HWQ)、DMA引擎在多核间的数据搬运、消息传递协议设计

好,我们接着聊多核通信。上一节我们讲了共享内存和核间中断,那是基础。但说实话,在真正的多核DSP系统里,光靠这两样东西,性能是上不去的。你想想看,每个核都在抢总线、抢内存,中断一多,CPU全花在上下文切换上了。

那怎么办?硬件来帮忙。今天要讲的三个东西——硬件队列(HWQ)、DMA引擎、消息传递协议——就是解决这个问题的关键。我在好几个项目里都吃过亏,后来才明白,通信机制的设计,直接决定了系统的吞吐量。

3.1 硬件队列(HWQ):比软件队列快一个数量级

先说说硬件队列。说白了,这就是在芯片里用寄存器或SRAM实现的一个FIFO。跟软件队列比,它不需要加锁,不需要原子操作,甚至不需要CPU参与入队出队。

核心优势:硬件队列的入队和出队操作,通常只需要一个写寄存器或读寄存器的指令。延迟在1-2个时钟周期。而软件队列,哪怕用无锁设计,也得几十个周期。

我习惯把硬件队列分成两类:

  • 生产者-消费者队列:一个核写,另一个核读。典型应用是任务分发。
  • 多生产者多消费者队列:多个核同时写,多个核同时读。这种设计更复杂,但灵活性高。

举个例子。我在做一款雷达信号处理芯片时,有4个DSP核。每个核处理完一帧数据后,要把结果发给下一个核。如果用共享内存加标志位,光同步就得花掉5%的CPU时间。后来换成硬件队列,每个核只负责往队列里丢指针,下一个核从队列里取。CPU占用率直接降到0.5%以下。

我的经验:硬件队列的深度不要设太大。16-32个条目通常就够了。太深了浪费面积,太浅了容易溢出。我曾经为了省面积,把队列深度设成8,结果在高负载下频繁丢数据。后来改成16,问题就解决了。

3.2 DMA引擎:让数据自己跑起来

接下来是DMA引擎。这个大家应该不陌生。但在多核场景下,DMA的角色远不止“搬数据”这么简单。

你想想看,如果每个核都要通过DMA从共享内存里取数据,那DMA的带宽就成了瓶颈。更麻烦的是,DMA传输完成后,怎么通知目标核?

我见过一个方案:DMA传输完成后,直接触发一个核间中断。但问题是,中断处理本身有开销。如果数据量小、传输频繁,中断的开销可能比数据搬运本身还大。

那怎么办?我建议用DMA链式传输硬件队列的组合。

具体做法是这样的:

  1. DMA配置成链式模式,一次配置多个传输描述符(descriptor)。
  2. 每个描述符指定源地址、目的地址、传输长度。
  3. 最后一个描述符完成后,DMA自动把完成状态写入硬件队列。
  4. 目标核轮询硬件队列,发现新条目后直接处理。

这样做的好处是:CPU完全不用管DMA的调度。DMA自己跑完一个链,就把结果通知给目标核。目标核也不用处理中断,轮询硬件队列的开销几乎可以忽略。

注意:DMA链式传输的描述符要放在内部SRAM里,不要放在DDR里。否则DMA每次取描述符都要走DDR总线,延迟会很大。我曾经因为这个原因,DMA吞吐量直接腰斩。后来把描述符挪到L2 SRAM里,问题才解决。

3.3 消息传递协议设计:别让协议成为瓶颈

有了硬件队列和DMA,消息传递协议的设计就变得简单了。但简单不等于随便。我见过太多团队,把协议设计得过于复杂,结果性能还不如裸写共享内存。

我个人习惯把消息分成三类:

消息类型 长度 传输方式 典型场景
控制消息 4-16字节 硬件队列直接传递 启动/停止任务、状态查询
数据消息 64字节-4KB DMA搬运 + 硬件队列通知 图像块、音频帧、FFT结果
大数据消息 4KB以上 DMA链式传输 + 硬件队列通知 整帧图像、批量数据

控制消息直接用硬件队列传。因为硬件队列本身就在寄存器里,延迟极低。数据消息和大数据消息,用DMA搬,搬完往硬件队列里丢一个描述符。目标核看到描述符,就知道数据在哪、有多大。

这里有个细节:消息头部的设计。我建议每个消息至少包含以下字段:

  • 消息ID:标识消息类型
  • 源核ID:谁发的
  • 目标核ID:发给谁
  • 数据长度:有效数据大小
  • 数据指针:指向共享内存中的数据
  • 时间戳:可选,用于调试和性能分析

为什么要加时间戳?嗯,我在调试一个多核系统时,发现数据偶尔会乱序。加了时间戳后,一眼就看出是DMA传输顺序的问题。没有时间戳,你根本不知道是哪个环节出了错。

避坑指南:我曾经设计过一个协议,消息头部里放了太多信息,结果一个控制消息要传32字节。硬件队列的条目宽度只有64位,一个消息要分4次才能传完。后来我把消息头部精简到8字节,剩下的信息通过DMA传。性能提升了一倍。

3.4 整体架构:一张图看懂

说了这么多,我们来画一张图,把这三个东西串起来。

多核通信架构:HWQ + DMA + 消息协议 DSP核0 DSP核1 DSP核2 DSP核3 硬件队列 (HWQ) 条目0: 消息头部 条目1: 消息头部 条目2: 消息头部 条目3: 消息头部 ... DMA引擎 (链式传输) 通道0: 描述符链 通道1: 描述符链 通道2: 描述符链 通道3: 描述符链 共享 内存 数据块0 数据块1 数据块2 数据块3 触发DMA 数据搬运 完成通知 轮询取消息 DSP核 硬件队列 DMA引擎 共享内存

这张图展示的就是我常用的架构。每个DSP核通过硬件队列发送消息头部。消息头部里包含数据指针和长度。DMA引擎根据消息头部的内容,自动从共享内存里搬数据。搬完后,DMA往硬件队列里写一个完成通知。目标核轮询硬件队列,拿到完成通知后,就知道数据已经准备好了。

整个过程,CPU只参与了消息头部的写入和完成通知的读取。数据搬运完全由DMA负责。这就是多核通信的“零拷贝”思想——数据只搬一次,CPU不碰数据本身。

我的建议:在设计消息传递协议时,一定要考虑流控。如果生产者核发消息太快,消费者核处理不过来,硬件队列会溢出。我常用的做法是:在消息头部里加一个“信用”字段。消费者核处理完一条消息后,通过另一个硬件队列返还一个信用。生产者核只有在有信用时才能发消息。这样就能天然地实现背压。

好了,多核通信机制就讲到这里。硬件队列、DMA引擎、消息传递协议,这三样东西组合起来,基本能覆盖90%的多核通信场景。剩下的10%,比如跨芯片通信、非对称多核架构,我们后面再聊。


专注资料整理