RPMsg:协议简介

  • Post author:
  • Post category:其他



阅读目录

转自:

https://www.jianshu.com/p/c7cdad8273ed

0. 起因

之前在

RPC原理与FastRPC实现

一文中介绍过RPC的原理,简而言之,RPC就是实现本地程序调用位于另一个地址空间的例程(routine)的一种技术手段,其基本架构如图0-1所示。

图0-1 RPC基本架构


由于RPC只是一种技术手段,并没有一个统一的标准,因此,每一种RPC框架根据其应用场景不同,所采用的实现方式也不尽相同。这些差异主要集中在两个方面:

  1. 数据的序列化和反序列化方法不同:例如可以使用JSON、XML以及谷歌推出的Protocol Buffer、Flat Buffer等格式作为数据传递格式,甚至是自己定义的一套格式;
  2. 数据传递方法不一样:例如可以通过HTTP、TCP等网络协议栈将数据发送出去。

今天主要介绍的是众多数据传递方式中的一种——RPMsg。

RPMsg,全称Remote Processor Messaging,它定义了异构多核处理系统(AMP,Asymmetric Multiprocessing)中核与核之间进行通信时所使用的标准二进制接口。

1. AMP

现在的芯片非常复杂,很多都是包含多个核,特别是片上系统(SoC),一颗芯片上不仅包含了很多个核心,并且很多核心都是异构的。例如手机里的芯片,就可能包含了CPU、GPU、DSP等不同的处理器单元。显然,这些不同架构的核心都有着他们各自的目的,例如,为了在端测实现高效的神经网络模型推理,现在的高端手机芯片基本都搭载了专门为神经网络这种密集计算的算法定制的运算单元。既然是不同单元,我们就不能等同的对待他们。

为了最大限度的发挥他们的性能,协同完成某一任务,不同的核心上面运行的系统可能各不相同,有些核心上面运行的通用系统例如Linux、Android等,另外一些核心上可能运行的就是实时操作系统(RTOS)等。这些不同架构的核心以及他们上面所运行的软件组合在一起,就成了异构多处理系统(Asymmetric Multiprocessing System)。

由于一般他们存在的目的都是协同的处理事情,因此在异构多处理系统中往往会形成主-从(Master-Slave)结构。主核上的系统先启动,并负责准备好运行环境,然后根据需要或者一定规则启动从核并对其进行管理。主-从核心上的系统都准备好之后,他们之间就通过IPC(Inter Processor Communication)方式进行通信,而RPMsg就是IPC中的一种。对于非通用的操作系统,它上面很可能是没有搭载传统的TCP/IP协议栈的,因此,当主核想要通过RPC的方式调用从核上的服务的时候,便不能使用一般的RPC框架所采用的网络通信方式。这时候类似于RPMsg这种专门用于核间通信的通信协议就派上了用场。

2. RPMsg


回到顶部


2.1. Linux中的RPMsg

在Linux内核代码中,RPMsg的代码主要位于drivers/rpmsg/下,文件之间的主要关系如图2-1所示。一开始Linux中只使用VirtIO作为该协议传输层,后来又增加了Glink、SMD等,Glink和SMD主要用于高通平台。

用户代码通过操纵rpmsg驱动,实现数据的收发操作。所有数据都在RPMsg总线上传递。

图2-1 Linux中RPMsg代码结构


回到顶部


2.2. 原理

在AMP系统中,主-从核心通过共享内存的方式进行通信,如图2-2所示。内存的管理由主核负责,在每个通信方向上都有两个缓冲区,分别是USED和AVAIL,这个缓冲区可以按照RPMsg中消息的格式分成一块一块链接形成一个环,如图2-3所示。

图2-2 AMP中主从通信方式

图2-3 RPMsg中的消息缓冲区示意图

当主核需要和从核进行通信的时候可以分为四步,如图2-4所示:

  1. 主核先从USED中取得一块内存;
  2. 将消息按照消息协议填充;
  3. 将该内存链接到AVAIL换中;
  4. 触发中断,通知从核有消息处理。

图2-4 主核发消息给从核

反过来,从核需要和主核通信的时候也类似,如图2-5所示:

  1. 主核先从AVAIL中取得一块内存;
  2. 将消息按照消息协议填充;
  3. 将该内存链接到USED换中;
  4. 触发中断,通知主核有消息处理。

图2-5 从核发消息给主核


回到顶部


2.3. 协议

既然是一种信息交换的协议,与TCP/IP类似,RPMsg协议也有分层,主要分为三层,分别是传输层、MAC层和物理层,如图2-6所示:

图2-6 RPMsg协议层

各层在Linux代码的对应情况如图2-7所示。

图2-7 RPMsg结构

并且,在rpmsg 总线上的消息都具有以下结构,包含消息头和数据两部分。消息头与TCP/IP协议的UDP包非常像,并且是固定的,如图2-8所示。

图2-8 RPMsg消息格式

该消息格式的定义位于

drivers/rpmsg/virtio_rpmsg_bus.c

中,具体定义如下。

struct rpmsg_hdr {
    u32 src;
    u32 dst;
    u32 reserved;
    u16 len;
    u16 flags;
    u8 data[];
} __packed;

3. API

虽然目前RPMsg并未形成相关的标准文档,但Linux内核中已经有了RPMsg的实现并给出了相关定义,OpenAMP也参照Linux中的定义做出了自己的实现。因此,这里对相关的API做些简单的介绍。可能在不久的将来,RPMsg可以从一个事实上的标准变成一个真正的标准,毕竟,TCP/IP 也是这么过来的嘛。

  1. RPMsg virtio主核初始化共享缓冲池(RPMsg virtio 从核不需要用到这个API):
void rpmsg_virtio_init_shm_pool(struct rpmsg_virtio_shm_pool *shpool,
              void *shbuf, size_t size)
  1. 初始化RPMsg virtio 设备:
int rpmsg_init_vdev(struct rpmsg_virtio_device *rvdev,
          struct virtio_device *vdev,
          rpmsg_ns_bind_cb ns_bind_cb,
          struct metal_io_region *shm_io,
          struct rpmsg_virtio_shm_pool *shpool)
  1. 销毁 RPMsg virtio 设备:
void rpmsg_deinit_vdev(struct rpmsg_virtio_device *rvdev)`
  1. 从RPMsg virtio设备中获取RPMsg设备:
struct rpmsg_device *rpmsg_virtio_get_rpmsg_device(struct rpmsg_virtio_device *rvdev)
  1. 创建 RPMsg 终端:
int rpmsg_create_ept(struct rpmsg_endpoint *ept,
           struct rpmsg_device *rdev,
           const char *name, uint32_t src, uint32_t dest,
           rpmsg_ept_cb cb, rpmsg_ns_unbind_cb ns_unbind_cb)
  1. 销毁 RPMsg 终端:
void rpmsg_destroy_ept(struct rpsmg_endpoint *ept)
  1. 检查本地RPMsg 终端是否已经与远程的终端绑定,以及是够已经准备好可以发送信息:
int is_rpmsg_ept_ready(struct rpmsg_endpoint *ept)
  1. 通过默认的RPMsg 终端发送信息:
int rpmsg_send(struct rpmsg_endpoint *ept, const void *data, int len)
  1. 通过制定的终端以以及目的地地址发送信息:
int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len,
       uint32_t dst)
  1. 通过指定原地址和目的地址发送消息:
int rpmsg_send_offchannel(struct rpmsg_endpoint *ept,
            uint32_t src, uint32_t dst,
            const void *data, int len)
  1. 尝试通过默认的终端发送信息,如果当前没有缓存可用则返回:
int rpmsg_trysend(struct rpmsg_endpoint *ept, const void *data,
        int len)
  1. 尝试通过制定的终端以以及目的地地址发送信息,如果当前没有缓存可用则返回:
int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len,
          uint32_t dst)
  1. 尝试通过指定原地址和目的地址发送消息,如果当前没有缓存可用则返回:
int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept,
               uint32_t src, uint32_t dst,
               const void *data, int len)