2. RPMsg核心数据结构剖析:rpmsg_device、rpmsg_endpoint、virtqueue与vring的深度解析
做RPMsg调试,说白了就是跟四个核心数据结构打交道:rpmsg_device、rpmsg_endpoint、virtqueue和vring。我见过不少工程师,上来就翻源码找bug,结果连这些结构体里存了什么都没搞清楚——那定位问题基本靠猜。
这一章,咱们把这四个家伙彻底扒开看看。
2.1 rpmsg_device:通信通道的抽象
rpmsg_device代表一个逻辑上的通信通道。你可以把它理解成“一根管子”——数据从这头进去,从那头出来。
核心字段速览
rpdev:指向底层virtio设备的指针,说白了就是告诉你在哪条总线上跑ept:默认端点,很多简单场景直接用这个,不用自己创建src:源地址,每个设备在总线上有个唯一IDdst:目的地址,你要跟谁说话ops:回调函数集,数据到了怎么办,由这里决定
我在项目中遇到过一个问题:两个核之间通信时,rpmsg_device创建成功了,但数据就是发不过去。查了半天,发现是dst地址配错了——你想想看,地址都错了,数据能到才怪。
2.2 rpmsg_endpoint:通信的入口与出口
rpmsg_endpoint是真正的收发接口。每个端点绑定一个回调函数,数据到了,内核自动调用它。
struct rpmsg_endpoint {
struct rpmsg_device *rpdev; // 所属设备
struct rpmsg_channel_info chinfo; // 通道信息
rpmsg_rx_cb_t cb; // 接收回调
void *priv; // 私有数据
u32 addr; // 端点地址
};
这里有个关键点:一个设备可以有多个端点。比如你跑OpenAMP,一个核上可能同时跑好几个服务,每个服务一个端点,互不干扰。
我的调试习惯:遇到数据收不到的情况,我第一件事就是检查cb回调有没有注册成功。曾经有一次,回调函数指针被意外覆盖了,数据到了没人处理,系统还以为是正常情况。
2.3 virtqueue:虚拟队列的核心
virtqueue是virtio规范里的核心概念。RPMsg用它来传递消息缓冲区。说白了,就是一块共享内存,加上一套生产-消费机制。
| 队列类型 | 方向 | 典型用途 |
|---|---|---|
| 接收队列(RX) | 远程→本地 | 远端发来的消息 |
| 发送队列(TX) | 本地→远程 | 本端发出的消息 |
每个virtqueue内部维护一个可用环(avail ring)和已用环(used ring)。生产者往avail里放buffer,消费者从used里取buffer。嗯,这个模型很经典,但坑也不少。
注意:virtqueue的深度是有限的。我见过一个案例,发送端疯狂发消息,接收端处理不过来,队列满了之后新消息直接丢弃——没有任何错误提示。所以,一定要监控队列使用率。
2.4 vring:共享内存的布局
vring是virtqueue在内存中的实际布局。它由三部分组成:
- 描述符表(descriptor table):每个描述符指向一个buffer,包含地址、长度、标志位
- 可用环(avail ring):存放待处理的描述符索引
- 已用环(used ring):存放已处理的描述符索引
这三个部分在内存中是连续存放的。布局顺序是:描述符表 → 可用环 → 已用环。
避坑指南:我曾经调试过一个核间通信卡死的问题。现象是发送端一直等ack,接收端说没收到。最后用printk打印vring的avail_idx和used_idx,发现used_idx根本没更新——接收端的中断没触发。原因是中断号在设备树里配错了。
2.5 四者关系全景图
光说概念不够直观,我画了一张图,把这四个结构体的关系串起来:
2.6 数据流全景:从发送到接收
理解了四个结构体,咱们看看数据怎么走:
- 发送端:应用层调用
rpmsg_send()→ 找到对应rpmsg_endpoint→ 把buffer放入virtqueue的avail ring → 更新vring的avail_idx→ 触发中断通知远端 - 接收端:中断触发 → 从
vring的used ring取出buffer → 调用rpmsg_endpoint的cb回调 → 应用层处理数据
调试切入点:如果数据丢了,优先检查vring的avail_idx和used_idx是否一致。不一致说明有buffer还在队列里没处理。我曾经用这个办法,五分钟就定位了一个缓冲区泄漏问题。
2.7 常见故障模式速查表
| 现象 | 可能原因 | 检查点 |
|---|---|---|
| 数据发不出去 | virtqueue满 / 地址错误 | 检查vq->num_free和dst地址 |
| 数据收不到 | 回调未注册 / 中断未触发 | 检查ept->cb和中断号 |
| 数据乱序 | vring描述符被覆盖 | 检查vring.desc的flags字段 |
| 系统卡死 | 死锁 / 队列操作未加锁 | 检查spin_lock使用情况 |
嗯,这四个结构体搞清楚了,RPMsg的骨架你就拿下了。后面遇到问题,脑子里先过一遍这张图——数据从哪来、经过谁、到哪去,一目了然。