1. 课程导入
各位同学,咱们今天正式开讲 RPMsg。说实话,我第一次接触这个协议的时候,也被它绕得有点晕。但搞明白了之后你会发现,它其实就是一套「让两个处理器能互相递纸条」的标准流程。
先说说背景。为什么会有 RPMsg?
现在的嵌入式系统越来越复杂。一个芯片里可能集成了 Cortex-A 跑 Linux,Cortex-M 跑 RTOS,甚至还有 DSP 或 GPU。这些核心各司其职,但数据得互通啊。比如 A 核采集了摄像头数据,要传给 M 核做实时控制。怎么传?
早期做法很粗暴:直接读写共享内存,再用 GPIO 中断通知对方。但这样问题很多——没有标准协议,没有流控,没有通道隔离,调试起来简直噩梦。我早期一个项目就吃过这个亏,两个核抢同一块内存,数据错乱,查了三天才发现是同步没做好。
RPMsg 就是来解决这个问题的。
1.1 核间通信模型
先看一张整体架构图,我习惯把 RPMsg 的通信模型画成三层:
说白了,RPMsg 就是中间那层传输协议。它不关心底层是共享内存还是硬件 Mailbox,也不关心上层传的是视频流还是控制指令。它只负责一件事:把 A 核的数据可靠地送到 B 核。
我个人习惯把这种模型叫做「三明治结构」——上下两层可以随便换,中间这层保持稳定。你在项目里换硬件平台,只要 VirtIO 层适配好了,上层应用代码基本不用动。
1.2 RPMsg 在 Linux 中的角色
在 Linux 侧,RPMsg 是以 remoteproc/rpmsg 框架的形式存在的。它不是一个独立的驱动,而是和远程处理器管理框架绑在一起的。
| 组件 | 角色 | 我遇到过的坑 |
|---|---|---|
| remoteproc | 负责加载固件、启动/停止远程核 | 固件路径配错,M 核起不来 |
| rpmsg_bus | 注册 rpmsg 设备,管理通道和端点 | 通道名称冲突导致 probe 失败 |
| virtio_rpmsg | 实现 VirtIO 传输层,操作 vring | vring 地址对齐不对,数据错位 |
| rpmsg_char | 提供用户态 /dev/rpmsgX 接口 | 多进程同时读写,没加锁 |
Linux 这边的设计思路是「把远程核当作一个外设」。你想想看,在 Linux 里操作一个 I2C 设备,和操作一个 RPMsg 通道,流程其实很像:打开设备、读写数据、关闭设备。只不过 RPMsg 的数据要跨过核间边界。
关键点:Linux 侧的 RPMsg 驱动是运行在 内核态 的。用户态程序通过字符设备接口访问,但真正的消息收发是在内核里完成的。这意味着——如果你在用户态做性能测试,每次读写都有一次系统调用开销。我在做高吞吐优化时,最后不得不写了一个内核模块来绕过这个瓶颈。
1.3 RPMsg 在 RTOS 中的角色
RTOS 这边就简单多了。没有复杂的进程管理,没有虚拟内存,一切都是裸奔状态。RPMsg 在 RTOS 里通常就是一个 库 + 一个任务。
我记得第一次在 FreeRTOS 上移植 RPMsg,最让我头疼的是内存分配。Linux 那边可以随便 kmalloc,RTOS 这边得自己管理共享内存池。稍不注意就会踩到对齐问题。
| RTOS 组件 | 说明 |
|---|---|
| rpmsg_lite | NXP 提供的轻量级 RPMsg 实现,无操作系统依赖 |
| 共享内存管理 | 通常是一段固定的物理地址,双核都能访问 |
| 中断处理 | 收到新消息时触发中断,通知 RTOS 任务去取 |
| 端点回调 | 注册回调函数,消息到来时自动调用 |
我的建议:在 RTOS 侧,尽量把 RPMsg 接收处理放在一个独立的高优先级任务里。别在中断上下文直接处理消息——中断里只做数据搬运,消息解析交给任务。否则一旦消息量上来,中断延迟会拖垮整个系统。
1.4 双核通信的典型数据流
咱们用一个实际场景来串一下:Linux 上的一个应用想给 RTOS 发一条「启动电机」的指令。
- 应用层:用户程序调用 write(/dev/rpmsg0, "start_motor", 12)
- rpmsg_char:将数据从用户态拷贝到内核缓冲区
- virtio_rpmsg:把数据打包成 rpmsg_hdr 格式,写入 vring
- 硬件触发:写 Doorbell 寄存器,通知 M 核
- M 核中断:RTOS 收到中断,从 vring 中取出数据
- rpmsg_lite:解析头部,找到对应的端点回调
- 应用回调:执行 "start_motor" 指令
整个过程,从 Linux write() 到 RTOS 回调执行,延迟通常在 几十微秒 级别。如果优化得好(比如用共享内存轮询代替中断),可以压到 个位数微秒。
注意:我曾经在一个项目里发现,Linux 侧用 printf 调试 RPMsg 时,打印本身会引入毫秒级延迟,导致 M 核那边超时复位。调试 RPMsg 时,千万别在数据路径上放 printk——用 tracepoint 或者专用日志缓冲区。
1.5 为什么选择 RPMsg?
市面上核间通信的方案不少,Mailbox、共享内存轮询、自定义协议……为什么 RPMsg 成了主流?
- 标准化:Linux 内核原生支持,不用自己造轮子
- 通道隔离:不同应用走不同 endpoint,互不干扰
- 流控机制:基于 vring 的缓冲区管理,不会丢消息
- 跨平台:同一个协议,A 核跑 Linux、M 核跑 RTOS,两边代码风格统一
说白了,RPMsg 就是核间通信界的「TCP/IP」——它不一定是最快的,但它是大家都能接受的通用标准。你在项目里用 RPMsg,至少不用担心换芯片平台后通信代码要重写。