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:真实数据承载区
共享内存分两类:
- 框架内部共享区:Vring / RPMsg buffer,由通信框架使用。
- 业务数据共享区:大块 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_shm0mmap 数据区,避免 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 通用处理流程
- 先画 ownership 表:明确每个外设、pin、中断、内存段属于 A 核、M 核还是共享。
- Linux 侧 DTS 收敛:M 核独占的外设,Linux 侧应禁用对应 device node,并移除冲突 pinctrl。
- M 核 SDK 配置收敛:M 核只初始化自己拥有的外设,不要“顺手初始化板级默认 pin”。
- 共享资源单独建契约:共享内存、Mailbox、Vring 必须写清地址、大小、方向、状态机。
- 启动后验证:看 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_count、shm_overrun_count、rpmsg_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. 总结:最佳实践原则
- 先分面,再选技术:Control Plane、Data Plane、Trace Plane 不分清,后面所有 API 选择都是玄学。
- RPMsg 是控制面,不是数据面:它适合短消息、状态事件、请求响应,不适合大块连续数据。
- 大数据走共享内存,RPMsg 只传 descriptor:descriptor 描述
slot / offset / length / seq / timestamp / crc,payload 留在共享内存。
- Trace 面必须隔离:日志可以丢,控制面不能被日志拖死;高频 trace 默认不走 RPMsg。
- 共享内存必须有状态机:FREE → WRITING → READY → READING → FREE,没有状态机就没有可验证性。
- Cache 策略必须显式:non-cacheable 最简单;cacheable 必须明确 clean / invalidate / barrier。
- 实时线程不能永久阻塞:RPMsg 发送失败、buffer full、Linux 消费滞后都必须有降级策略。
- 资源归属优先于驱动调试:外设、pinmux、clock、reset、shared memory 归属不清,调 RPMsg 只是浪费生命。
- 协议保持克制:通用包头只放必要字段,复杂事务交给上层状态机。
- 没有指标就没有工程稳定性:tx/rx/drop/timeout/overrun/crc/latency 都要可观测。
最终判断:一个合格的异构通信系统,不是“Linux 和 M 核能互相发 hello”,而是控制面不会被数据和日志拖死,数据面能承受背压,Trace 面能保留诊断信息,资源归属清晰,异常后能恢复,指标能证明它长期稳定。
参考资料
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/heterogeneous_inter_core_communication
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!










