Lazy loaded image
🥳嵌入式Linux开发
多核异构核间通信:RPMsg 控制面与共享内存数据面设计
Words 8247Read Time 21 min
2024-9-24
2026-5-28
type
Post
date
Sep 24, 2024
slug
heterogeneous_inter_core_communication
category
🥳嵌入式Linux开发
icon
password
0. 核心结论0.1 三面分离原则0.2 阅读地图1. 背景知识:为什么异构多核通信复杂1.1 异构多核的典型分工1.2 AMP 与 SMP1.3 核间通信的本质约束2. 理论分层:RemoteProc / RPMsg / VirtIO / Mailbox / Shared Memory2.1 总体分层模型2.2 RemoteProc:生命周期管理2.2.1 RemoteProc 图解2.3 RPMsg:短消息控制面2.3.1 RPMsg 抽象图解2.4 VirtIO / Vring:共享内存队列抽象2.4.1 Vring 结构图解2.5 Mailbox / MU / IPCC:只负责通知2.5.1 MU / Mailbox 通知流程2.6 Shared Memory:真实数据承载区3. 控制面设计:RPMsg 短消息通信3.1 控制面的边界3.2 通用控制面消息类型3.3 通用控制面包头3.4 控制面设计约束3.5 Linux RPMsg API 总览3.5.1 发送 API3.5.2 Endpoint / Driver API3.5.3 API 选择规则3.5.4 最小 Linux RPMsg Driver 骨架4. 数据面设计:共享内存传大块 frame4.1 为什么大数据不能直接走 RPMsg4.2 数据面可选方案对比4.3 数据面流程4.4 共享内存布局4.5 RPMsg Descriptor4.6 Remote Core 写入侧4.7 发送 descriptor4.8 Linux 侧读取共享内存4.9 Linux mmap / char driver 最小链路4.9.1 最小驱动职责4.9.2 驱动内数据流4.9.3 mmap / read / release 骨架4.10 数据面最佳实践5. Trace 面设计:日志与调试通道隔离5.1 Trace 面原则5.2 Trace Ring5.3 Remote Core 写 trace5.4 上报策略5.5 Trace 面反模式6. 通用案例:以 NXP iMX8MP / Linux + FreeRTOS 为例6.1 平台背景与需求抽象6.2 总体运行流程6.3 Remote Core 初始化6.4 Remote Core 主循环6.5 Linux 侧 RPMsg 回调6.6 用户态消费7. 资源归属与冲突处理7.1 资源归属表7.2 通用处理流程7.3 Pinmux 冲突的通用写法8. 调试验证与指标8.1 Bring-up Checklist8.2 控制面验证8.3 数据面验证8.4 Trace 面验证8.5 最小指标集9. 常见问题与排查路径9.1 RPMsg channel 不出现9.2 Linux 发 M 核收不到9.3 M 核发 Linux 收不到9.4 共享内存读到脏数据9.5 CRC mismatch 随机出现9.6 大数据传输一段时间后卡死9.7 trace 打开后系统变慢9.8 remote core reset 后无法恢复10. 总结:最佳实践原则参考资料

0. 核心结论

🎯
RPMsg 是异构多核系统的短消息控制面,不是高吞吐数据面。 正确架构应该是:Control Plane 用 RPMsg,Data Plane 用 Shared Memory,Trace Plane 独立隔离。如果把命令、传感器大数据、调试日志全部塞进 RPMsg,系统不是“慢一点”,而是会进入不可预测状态。

0.1 三面分离原则

平面
承载内容
推荐机制
设计目标
Control Plane
启动、停止、配置、心跳、状态、错误事件
RPMsg
短消息、低延迟、可确认、可恢复
Data Plane
传感器 frame、图像块、点云块、批量日志、大块二进制数据
Reserved Shared Memory + Ring Buffer + RPMsg Descriptor
高吞吐、低拷贝、可背压
Trace Plane
调试日志、性能采样、崩溃前日志
Trace Ring / 本地落盘 / 限流事件
不饿死控制面,不干扰数据面

0.2 阅读地图

章节
核心问题
建议读法
背景知识
为什么 Linux + RTOS 异构通信比普通进程通信复杂?
先理解不同 CPU、不同 OS、不同 cache、不同中断控制器带来的约束。
理论分层
RemoteProc / RPMsg / VirtIO / Mailbox / Shared Memory 各管什么?
不要把它们混成“跨核通信框架”一锅粥。
控制面
短命令、响应、心跳、状态事件如何设计?
重点看 RPMsg 边界、通用控制包头、seq / status / timeout 策略。
数据面
大块 frame / batch 数据如何传?
重点看 Shared Memory Ring + RPMsg Descriptor,而不是 RPMsg 分片硬塞。
Trace 面
日志和调试信息如何不拖死系统?
重点看 Trace Ring、限流、采样和 postmortem 设计。
资源归属
共享内存、Mailbox、Pinmux、外设到底归谁?
先做 ownership 表,再改 DTS / SDK 配置。
调试验证
怎样证明系统不是“刚好能跑”?
看指标、压测、异常恢复和长稳验证。
常见问题
channel 不出现、脏数据、CRC mismatch、卡死如何排查?
遇到工程问题时直接按症状查,不要靠玄学猜。

1. 背景知识:为什么异构多核通信复杂

1.1 异构多核的典型分工

现代 SoC 常见结构是:一个或多个 Cortex-A 核运行 Linux,另一个 Cortex-M / DSP / R 核运行 RTOS 或 Bare-metal。主核负责复杂系统能力,从核负责实时、低功耗或专用任务。
角色
常见系统
主要职责
优势
Host Core
Linux
文件系统、网络、UI、复杂业务、远端核管理
生态完整,资源管理能力强
Remote Core
RTOS / Bare-metal
实时采集、控制闭环、低延迟响应、低功耗任务
确定性更强,启动快,调度轻

1.2 AMP 与 SMP

模式
核心关系
操作系统
典型场景
SMP
多个同构核心共享一个 OS
Linux 多核
应用处理、服务器、桌面、多线程计算
AMP
不同核心运行不同系统
Linux + RTOS / Bare-metal
工业控制、传感器采集、实时任务、低功耗协处理

1.3 核间通信的本质约束

异构核间通信不是“两个程序互相发消息”这么简单,而是在以下约束下建立可靠同步:
  • 不同 CPU:指令集、地址视图、中断控制器可能不同。
  • 不同 OS:Linux 有复杂调度,RTOS 更关注确定性。
  • 不同内存视图:同一块 DDR,在两侧可能有不同的虚拟地址 / 物理地址映射。
  • 不同 cache 策略:共享内存若 cacheable,必须处理 clean / invalidate / barrier。
  • 不同实时性要求:控制命令不能被大数据和日志拖死。
  • 不同资源归属:外设、pinmux、clock、reset 如果归属不清,系统会玄学失效。

2. 理论分层:RemoteProc / RPMsg / VirtIO / Mailbox / Shared Memory

2.1 总体分层模型

2.2 RemoteProc:生命周期管理

RemoteProc 不是通信协议,而是 Linux 侧管理远端核的框架。它更像“远端核进程管理器 + 固件加载器 + 资源协调器”。
典型职责:
  • 加载 remote firmware。
  • 解析 resource table。
  • 启动 / 停止远端核。
  • 管理远端核崩溃与恢复。
  • 根据资源描述创建 VirtIO / RPMsg 相关设备。

2.2.1 RemoteProc 图解

原文里这类解释图不该完全删除。现在用可维护的 Mermaid 重新画一版,保留它的教学作用:帮助读者区分 Linux 侧生命周期管理Remote 侧固件运行环境
读图时抓三个点:
  • RemoteProc 管生命周期:加载固件、启动、停止、崩溃恢复。
  • RPMsg 管通信:RemoteProc 不是消息协议,RPMsg 才是短消息总线。
  • 资源表 / 平台配置管资源:carveout、vring、trace buffer 等必须被双方一致理解。

2.3 RPMsg:短消息控制面

RPMsg 是基于 VirtIO 的消息总线,面向 Linux Host 与 Remote Processor 之间的短消息通信。
适合:
  • 启动 / 停止 / 复位。
  • 配置读写。
  • 心跳。
  • 状态事件。
  • 错误上报。
  • 时间同步握手。
不适合:
  • 高频传感器流。
  • 图像 / 点云 / 声呐 frame。
  • 大块日志。
  • 高频 trace / printf。
一句话:RPMsg 负责“告诉对方发生了什么”,不负责搬大块数据。

2.3.1 RPMsg 抽象图解

RPMsg API 看起来像普通 send(),但底层不是“直接把字节塞给另一个 CPU”。更准确的路径是:共享内存承载消息,Vring 管 buffer,Mailbox / MU / IPCC 负责通知
这张图对应一个关键判断:RPMsg 的 payload 是短消息;大块 payload 应放在业务共享内存里,RPMsg 只传 descriptor。

2.4 VirtIO / Vring:共享内存队列抽象

RPMsg 底层通常通过 VirtIO / Vring 组织共享内存中的消息 buffer。
Vring 关键组成:
  • descriptor table:描述 buffer 地址、长度、flags。
  • available ring:生产者告诉消费者哪些 buffer 可用。
  • used ring:消费者归还已使用 buffer。
  • kick / interrupt:通过 Mailbox / MU / IPCC 通知对方处理 ring。

2.4.1 Vring 结构图解

Vring 的价值在于把共享内存组织成“可同步的队列”,而不是让两边随意读写同一块 RAM。
最容易忽略的是 descriptor 和 buffer 不是一回事:
  • descriptor 描述 buffer。
  • avail / used ring 描述 buffer 的所有权流转。
  • Mailbox / MU 只是告诉对方“ring 变了”,不保证数据自动一致。

2.5 Mailbox / MU / IPCC:只负责通知

Mailbox / MU / IPCC 本质是“门铃”。
它通常不搬数据,只负责告诉对方:共享内存 / vring 中有新内容需要处理。
常见误区:
  • 把 Mailbox 当数据通道。
  • 以为中断到了就代表数据一定一致。
  • 忽略中断方向、通道编号、优先级和回调绑定。

2.5.1 MU / Mailbox 通知流程

原文里 MU 图的解释价值应该保留。这里改成通用流程图,避免绑定某一张厂商图或某一个寄存器名称。
所以硬件通知层只回答一个问题:对方需要被叫醒吗?
它不回答:数据在哪里、数据是否完整、cache 是否一致、slot 是否可复用。这些必须由上层共享内存协议负责。

2.6 Shared Memory:真实数据承载区

共享内存分两类:
  1. 框架内部共享区:Vring / RPMsg buffer,由通信框架使用。
  1. 业务数据共享区:大块 frame / batch / trace ring,由业务设计使用。
业务共享内存必须明确:
  • 物理地址。
  • 大小。
  • cache 属性。
  • slot / ring 布局。
  • 生产者 / 消费者状态机。
  • Linux 与 Remote Core 的地址映射关系。

3. 控制面设计:RPMsg 短消息通信

3.1 控制面的边界

控制面只承载低频、短小、影响状态机的消息。
类型
是否适合 RPMsg
说明
启动 / 停止 / 复位
适合
短命令,要求响应。
配置读写
适合
request-response 模型。
心跳 / 状态
适合
低频事件。
大块 frame
不适合
走数据面,RPMsg 只传 descriptor。
高频 trace
不适合
走 Trace Ring。

3.2 通用控制面消息类型

类型
方向
用途
CMD
Host → Remote 或 Remote → Host
请求对方执行动作,例如启动、停止、配置写入。
RSP
请求接收方 → 请求发起方
返回执行结果,必须携带对应 seq。
EVT
任意方向
状态变化、异常、心跳、低频健康信息。

3.3 通用控制面包头

3.4 控制面设计约束

  • CMD 必须有 seq:否则 RSP 无法可靠匹配。
  • RSP 必须复用 CMD 的 seq:不要另起响应编号。
  • EVT 默认不要求 ACK:除非它影响系统状态机。
  • payload 必须小:超过合理范围时,改走数据面。
  • timeout 不放进包头:超时是本地策略,由发送方维护 pending table。
控制面做到这里就够了。再复杂的事务、重试、幂等、状态机,应该在上层协议处理,不要把 RPMsg 包头设计成“宇宙飞船仪表盘”。

3.5 Linux RPMsg API 总览

上一轮把 API 删得太狠了。API 总览应该保留,但要改成“工程决策表”,而不是逐段复制内核文档。RPMsg API 的选择核心只有三件事:目标地址怎么选、是否允许阻塞、是否需要自建 endpoint

3.5.1 发送 API

API
目标地址
阻塞行为
适用场景
rpmsg_send(ept, data, len)
使用 endpoint 默认 dst
TX buffer 不足时可能等待
普通控制命令、响应、低频事件。
rpmsg_sendto(ept, data, len, dst)
显式指定 dst
TX buffer 不足时可能等待
一个 endpoint 面向多个远端地址。
rpmsg_send_offchannel(ept, src, dst, data, len)
显式指定 src / dst
TX buffer 不足时可能等待
需要完全控制源地址和目的地址的特殊场景。
rpmsg_trysend(ept, data, len)
使用 endpoint 默认 dst
无 TX buffer 时立即返回
实时链路、不能阻塞的控制线程。
rpmsg_trysendto(ept, data, len, dst)
显式指定 dst
无 TX buffer 时立即返回
多目的地址 + 非阻塞发送。
rpmsg_trysend_offchannel(ept, src, dst, data, len)
显式指定 src / dst
无 TX buffer 时立即返回
完全控制地址 + 非阻塞发送。

3.5.2 Endpoint / Driver API

API
作用
注意点
rpmsg_create_ept()
创建 endpoint,并绑定 RX callback、私有数据和地址信息。
适合一个驱动内需要多个逻辑通道的场景。
rpmsg_destroy_ept()
销毁 endpoint。
释放时要确保没有并发回调仍在使用私有数据。
register_rpmsg_driver()
注册 RPMsg client driver。
通常通过 id_table 匹配 remote 侧 announce 的 service name。
unregister_rpmsg_driver()
注销 RPMsg client driver。
模块退出或驱动卸载时使用。

3.5.3 API 选择规则

  • 普通控制请求rpmsg_send() 可以用,但上层必须有 timeout 和 pending 清理。
  • 实时控制路径:优先 rpmsg_trysend(),失败后计数、降级、重试或丢弃低优先级事件。
  • 数据面 descriptor:建议非阻塞发送,不能因为 Linux 暂时不消费而卡死采集线程。
  • Trace 摘要事件:必须限流后再发;高频 trace 绝对不能逐条调用 RPMsg API。

3.5.4 最小 Linux RPMsg Driver 骨架

这部分保留 API 总览的解释价值,但把重点拉回工程判断:API 是手段,链路隔离和阻塞边界才是设计核心。

4. 数据面设计:共享内存传大块 frame

4.1 为什么大数据不能直接走 RPMsg

如果把大数据拆成多个 RPMsg 包,会引入:
  • 控制面阻塞。
  • TX buffer 耗尽。
  • 分片重组复杂度。
  • 丢包 / 超时 / 重试逻辑膨胀。
  • 传感器流、日志流、控制命令互相污染。
正确方式:Shared Memory 放 payload,RPMsg 只传 descriptor。

4.2 数据面可选方案对比

数据面没有像 RPMsg 这样“一套通吃”的银弹。工程上应该先判断数据量、实时性、Linux 用户态访问方式、remote core 映射复杂度,再选实现路径。
方案
适用场景
优点
缺点
推荐程度
reserved memory + 自定义 mmap char driver
Linux + M 核固定共享内存
最可控,地址、权限、生命周期都能由驱动收口。
需要写内核驱动和用户态接口。
最高
UIO + reserved memory
快速原型、实验验证
简单,用户态可快速 mmap。
安全性、权限控制和工程规范较弱。
DMA-BUF
Linux 多设备共享 buffer、复杂 zero-copy 链路
Linux 生态标准,适合多 consumer / producer。
remote core 物理地址映射和 cache 协同更复杂。
中高
OpenAMP + libmetal shared memory
跨平台 AMP 抽象
提供共享内存、MMIO、地址转换和同步抽象。
平台移植和调试成本较高,不适合把主线写成库教程。
中高
扩大 RPMsg buffer
小幅 payload 增大
改动少。
不适合真正大数据,容易污染控制面并放大阻塞风险。
不推荐
🧭
推荐主线:教学和工程文章优先采用 reserved-memory + ring buffer + RPMsg descriptor。它足够通用,也足够接近真实项目;libmetal / DMA-BUF 可以作为可选扩展,而不是抢走主线。

4.3 数据面流程

4.4 共享内存布局

4.5 RPMsg Descriptor

4.6 Remote Core 写入侧

4.7 发送 descriptor

4.8 Linux 侧读取共享内存

4.9 Linux mmap / char driver 最小链路

Linux 侧最好不要让用户态直接“凭感觉”访问物理地址,而是由一个小型 char driver 收口共享内存映射、descriptor 队列和 slot release。

4.9.1 最小驱动职责

  • 映射 reserved memory:从 device tree 的 reserved-memory 获取物理地址和大小,并映射到内核虚拟地址。
  • 提供 mmap:用户态通过 /dev/rpmsg_shm0 mmap 数据区,避免 read 大块 payload。
  • 承接 RPMsg callback:callback 只校验 descriptor,然后放入队列并唤醒等待的 reader。
  • 提供 read:用户态 read 的是 frame descriptor,不是 payload 本体。
  • 提供 ioctl release:用户态处理完成后释放 slot,并通过 RPMsg ACK 通知 remote core。

4.9.2 驱动内数据流

4.9.3 mmap / read / release 骨架

这不是完整驱动,但已经说明 /dev/rpmsg_shm0 的来源:RPMsg driver 负责控制面通知,char driver 接管共享内存 mmap 和 buffer 生命周期。

4.10 数据面最佳实践

  • 共享内存建议由 Linux device tree reserved-memory 明确保留。
  • M 核 linker script / memory map 必须使用同一物理地址。
  • slot 状态机必须显式。
  • RPMsg descriptor 发送失败不能阻塞采集线程。
  • drop / overrun / crc_error 必须可观测。
  • 大数据不要做 RPMsg 分片传输。

5. Trace 面设计:日志与调试通道隔离

5.1 Trace 面原则

大量日志 / trace 容易饿死控制面,所以必须从架构上隔离。
Trace 类型
推荐路径
说明
关键异常事件
RPMsg EVT
影响状态机,可以进入控制面。
低频诊断日志
限流后的 RPMsg EVT
必须采样、限频、固定长度。
高频 printf / 性能 trace
Trace Ring
禁止直接走 RPMsg。
崩溃前日志
Reserved Trace Buffer
用于 postmortem 分析。

5.2 Trace Ring

5.3 Remote Core 写 trace

5.4 上报策略

Trace ring 本身不需要每写一条都 RPMsg 通知 Linux,否则又绕回“日志饿死控制面”的老坑。
  • ERROR / FATAL:可立即发送短 EVT,payload 只带 module / level / seq / timestamp
  • WARN:按窗口合并上报,例如 1s 内最多通知一次。
  • DEBUG / INFO:不主动通知,由 Linux 周期性拉取或调试模式读取。

5.5 Trace 面反模式

  • 每条日志都 RPMsg 通知。
  • 在中断里格式化字符串。
  • 日志发送失败就阻塞等待。
  • 无级别、无模块、无 seq。
  • 只有字符串,没有结构化字段。
🧯
Trace 面可以丢,但不能拖死系统;控制面不能丢关键状态,也不能被 trace 拖死。

6. 通用案例:以 NXP iMX8MP / Linux + FreeRTOS 为例

6.1 平台背景与需求抽象

这里以 NXP iMX8MP 这类异构 SoC 为通用背景,而不是绑定某一份 BSP、某一个 DTS 行号或某一块评估板。抽象后的平台模型是:
  • Host Core:Cortex-A53 / Linux,负责复杂业务、文件系统、网络、用户态应用和 remote core 生命周期管理。
  • Remote Core:Cortex-M7 / FreeRTOS,负责实时采集、低延迟控制或低功耗任务。
  • Notify:MU / Mailbox / IPCC,负责跨核“敲门”。
  • Control Plane:RPMsg / RPMsg-Lite,负责短命令、响应和状态事件。
  • Data Plane:reserved shared memory,负责 frame / batch / 大块 payload。
  • Trace Plane:独立 trace ring 或限流事件,避免日志污染控制面。
需求
推荐通道
说明
启动 / 停止采集
Control Plane / RPMsg CMD
短命令,要求可确认。
参数配置
Control Plane / RPMsg CMD + RSP
需要 seq、status、错误码。
心跳 / 状态
Control Plane / RPMsg EVT
低频、短 payload。
传感器 frame / batch
Data Plane / Shared Memory
RPMsg 只传 descriptor,不传数据本体。
高频日志 / trace
Trace Plane / Trace Ring
允许丢弃和覆盖,不能拖死控制面。

6.2 总体运行流程

6.3 Remote Core 初始化

6.4 Remote Core 主循环

6.5 Linux 侧 RPMsg 回调

6.6 用户态消费

7. 资源归属与冲突处理

7.1 资源归属表

资源
归属规则
常见问题
处理原则
共享内存
两侧都访问,但必须有唯一布局定义
地址不一致、cache 不一致、Linux 分配覆盖
Linux 侧 reserved-memory 保留;M 核 linker / memory map 使用同一物理地址。
Vring / RPMsg buffer
通信框架专用
buffer size、数量、alignment 两侧不一致
Host 与 Remote 使用同一组参数,禁止业务代码复用 vring 区域。
MU / Mailbox / IPCC
跨核通知专用
中断方向错、通道号冲突、通知到了但对方没处理
明确 TX/RX 方向、通道编号、中断优先级和回调绑定。
UART / I2C / SPI / GPIO
同一时刻只能稳定归属一侧
Linux driver 和 M 核同时初始化外设
谁使用,谁拥有;另一侧必须禁用设备节点或不初始化。
Pinmux
必须与外设归属一致
A 核 DTS 与 M 核 SDK pin 配置冲突
统一维护 pin ownership 表,DTS 与 M 核 pin config 同步审查。
Clock / Reset / Power Domain
通常由 Linux 管,但可能影响 M 核外设
Linux runtime PM 关掉 M 核正在用的时钟
对 M 核独占外设,Linux 侧避免绑定驱动或关闭自动电源管理影响。

7.2 通用处理流程

  1. 先画 ownership 表:明确每个外设、pin、中断、内存段属于 A 核、M 核还是共享。
  1. Linux 侧 DTS 收敛:M 核独占的外设,Linux 侧应禁用对应 device node,并移除冲突 pinctrl。
  1. M 核 SDK 配置收敛:M 核只初始化自己拥有的外设,不要“顺手初始化板级默认 pin”。
  1. 共享资源单独建契约:共享内存、Mailbox、Vring 必须写清地址、大小、方向、状态机。
  1. 启动后验证:看 dmesg、remote log、中断计数、GPIO 电平、总线波形,不要靠“感觉应该没问题”。

7.3 Pinmux 冲突的通用写法

这部分不是“改设备树教程”,而是资源治理问题:外设归属不清,RPMsg 再通也救不了系统。

8. 调试验证与指标

8.1 Bring-up Checklist

  • Remote firmware 是否被 Linux 正确加载并启动?
  • RemoteProc 状态是否为 running?
  • RPMsg channel 是否出现?
  • endpoint name / id_table 是否匹配?
  • Mailbox / MU 中断计数是否增长?
  • 共享内存 magic / version 是否可从两侧读到?
  • cache clean / invalidate 是否实际生效?
  • M 核重启后 Linux 侧能否重新恢复通信?

8.2 控制面验证

测试项
验证目标
通过标准
CMD/RSP
请求响应闭环
seq 匹配,status 正确,超时可回收。
EVT
异步事件上报
事件不阻塞业务线程,丢失策略明确。
TX buffer full
背压处理
不会永久阻塞实时线程。
Remote reset
异常恢复
重启后 channel 可重建,pending 请求可清理。

8.3 数据面验证

测试项
验证目标
通过标准
frame 写入
共享内存 slot 状态机
FREE → WRITING → READY → READING → FREE 顺序稳定。
CRC 校验
数据完整性
长时间运行无随机 crc mismatch。
消费滞后
背压与丢帧策略
drop_count 增长可观测,系统不死锁。
大帧压力
吞吐能力
连续传输不污染控制面延迟。

8.4 Trace 面验证

测试项
验证目标
通过标准
高频日志
验证 Trace 不污染控制面
DEBUG / INFO 打开后,控制面延迟不明显上升。
ERROR / FATAL 事件
验证关键异常可上报
短 EVT 可见,payload 固定长度,不携带大段字符串。
trace ring 覆盖
验证日志允许丢弃且可观测
trace_drop_count / overwrite_count 增长可解释,系统不阻塞。

8.5 最小指标集

9. 常见问题与排查路径

9.1 RPMsg channel 不出现

  • 检查 remote firmware 是否已经被 remoteproc 成功加载并启动。
  • 检查 resource table 是否声明了 vring、carveout 和 RPMsg 相关资源。
  • 检查 remote side announce 的 service name 是否与 Linux rpmsg_driver.id_table 匹配。
  • 检查 MU / Mailbox 中断方向和通道编号是否正确。

9.2 Linux 发 M 核收不到

  • 确认 endpoint 地址、src / dst 是否匹配。
  • 检查 remote side 是否已经创建 endpoint 并进入接收循环。
  • 检查 RPMsg buffer 是否耗尽,发送 API 是否阻塞或失败。
  • 用最小 hello 包验证链路,再引入业务包头。

9.3 M 核发 Linux 收不到

  • 确认 remote side 是否完成 nameservice announce。
  • 检查 Linux driver 的 probe 是否被触发。
  • 检查 callback 是否快速返回,避免在回调里做重业务。
  • 检查 Mailbox interrupt count 是否增长。

9.4 共享内存读到脏数据

  • 优先检查 cache clean / invalidate 是否在正确位置执行。
  • 检查 memory barrier 是否保证了 payload、descriptor、state 的写入顺序。
  • 检查 Linux 和 M 核看到的物理地址是否一致。
  • 检查共享内存是否被 Linux buddy allocator 或其他驱动覆盖。

9.5 CRC mismatch 随机出现

  • 大概率是 cache 一致性、slot 生命周期或越界写问题。
  • 检查是否在 SLOT_WRITING 未完成时就把 state 置为 SLOT_READY
  • 检查 Linux 是否在未 invalidate cache 的情况下读取 payload。
  • 检查 frame length、offset、slot_id 是否越界。

9.6 大数据传输一段时间后卡死

  • 检查 slot 是否被 Linux 用户态处理后正确 release。
  • 检查 ACK 是否丢失,以及 remote side 是否有超时回收策略。
  • 检查 backpressure 策略:消费慢时应该丢低优先级帧、降采样或限速,而不是永久阻塞实时线程。
  • 观察 shm_drop_countshm_overrun_countrpmsg_tx_fail 是否增长。

9.7 trace 打开后系统变慢

  • 检查是否把每条日志都通过 RPMsg 发出。
  • 高频 DEBUG / INFO 默认应写 trace ring,而不是污染控制面。
  • ERROR / FATAL 可以发短事件,但 WARN 以下应采样、聚合或周期拉取。
  • 观察 trace_drop_count 和控制面延迟是否相关。

9.8 remote core reset 后无法恢复

  • 清理 Linux 侧 pending request,避免旧 seq 卡住新会话。
  • 重新初始化 shared memory magic、version、write_idx、read_idx。
  • 重新建立 RPMsg channel / endpoint。
  • 明确 reset 后 slot 状态是全部回收,还是按 generation id 判定旧数据无效。

10. 总结:最佳实践原则

  1. 先分面,再选技术:Control Plane、Data Plane、Trace Plane 不分清,后面所有 API 选择都是玄学。
  1. RPMsg 是控制面,不是数据面:它适合短消息、状态事件、请求响应,不适合大块连续数据。
  1. 大数据走共享内存,RPMsg 只传 descriptor:descriptor 描述 slot / offset / length / seq / timestamp / crc,payload 留在共享内存。
  1. Trace 面必须隔离:日志可以丢,控制面不能被日志拖死;高频 trace 默认不走 RPMsg。
  1. 共享内存必须有状态机:FREE → WRITING → READY → READING → FREE,没有状态机就没有可验证性。
  1. Cache 策略必须显式:non-cacheable 最简单;cacheable 必须明确 clean / invalidate / barrier。
  1. 实时线程不能永久阻塞:RPMsg 发送失败、buffer full、Linux 消费滞后都必须有降级策略。
  1. 资源归属优先于驱动调试:外设、pinmux、clock、reset、shared memory 归属不清,调 RPMsg 只是浪费生命。
  1. 协议保持克制:通用包头只放必要字段,复杂事务交给上层状态机。
  1. 没有指标就没有工程稳定性:tx/rx/drop/timeout/overrun/crc/latency 都要可观测。
🧯
最终判断:一个合格的异构通信系统,不是“Linux 和 M 核能互相发 hello”,而是控制面不会被数据和日志拖死,数据面能承受背压,Trace 面能保留诊断信息,资源归属清晰,异常后能恢复,指标能证明它长期稳定。

参考资料

  1. Remote Processor Messaging (rpmsg) Framework — The Linux Kernel documentation
  1. Remote Processor Framework — The Linux Kernel documentation
  1. Virtio on Linux — The Linux Kernel documentation
  1. OpenAMP RPMsg Protocol
  1. RPMsg-Lite — NXP MCUXpresso
 
上一篇
多线程、并发与线程同步
下一篇
嵌入式硬件基础

Comments
Loading...
Catalog