RPMsg:協(xié)議簡介

0. 起因

之前在RPC原理與FastRPC實(shí)現(xiàn)一文中介紹過RPC的原理,簡而言之,RPC就是實(shí)現(xiàn)本地程序調(diào)用位于另一個地址空間的例程(routine)的一種技術(shù)手段,其基本架構(gòu)如圖0-1所示。

圖0-1 RPC基本架構(gòu)

由于RPC只是一種技術(shù)手段,并沒有一個統(tǒng)一的標(biāo)準(zhǔn),因此,每一種RPC框架根據(jù)其應(yīng)用場景不同,所采用的實(shí)現(xiàn)方式也不盡相同。這些差異主要集中在兩個方面:

  1. 數(shù)據(jù)的序列化和反序列化方法不同:例如可以使用JSON、XML以及谷歌推出的Protocol Buffer、Flat Buffer等格式作為數(shù)據(jù)傳遞格式,甚至是自己定義的一套格式;
  2. 數(shù)據(jù)傳遞方法不一樣:例如可以通過HTTP、TCP等網(wǎng)絡(luò)協(xié)議棧將數(shù)據(jù)發(fā)送出去。

今天主要介紹的是眾多數(shù)據(jù)傳遞方式中的一種——RPMsg。

RPMsg,全稱Remote Processor Messaging,它定義了異構(gòu)多核處理系統(tǒng)(AMP,Asymmetric Multiprocessing)中核與核之間進(jìn)行通信時所使用的標(biāo)準(zhǔn)二進(jìn)制接口。

1. AMP

現(xiàn)在的芯片非常復(fù)雜,很多都是包含多個核,特別是片上系統(tǒng)(SoC),一顆芯片上不僅包含了很多個核心,并且很多核心都是異構(gòu)的。例如手機(jī)里的芯片,就可能包含了CPU、GPU、DSP等不同的處理器單元。顯然,這些不同架構(gòu)的核心都有著他們各自的目的,例如,為了在端測實(shí)現(xiàn)高效的神經(jīng)網(wǎng)絡(luò)模型推理,現(xiàn)在的高端手機(jī)芯片基本都搭載了專門為神經(jīng)網(wǎng)絡(luò)這種密集計(jì)算的算法定制的運(yùn)算單元。既然是不同單元,我們就不能等同的對待他們。

為了最大限度的發(fā)揮他們的性能,協(xié)同完成某一任務(wù),不同的核心上面運(yùn)行的系統(tǒng)可能各不相同,有些核心上面運(yùn)行的通用系統(tǒng)例如Linux、Android等,另外一些核心上可能運(yùn)行的就是實(shí)時操作系統(tǒng)(RTOS)等。這些不同架構(gòu)的核心以及他們上面所運(yùn)行的軟件組合在一起,就成了異構(gòu)多處理系統(tǒng)(Asymmetric Multiprocessing System)。

由于一般他們存在的目的都是協(xié)同的處理事情,因此在異構(gòu)多處理系統(tǒng)中往往會形成主-從(Master-Slave)結(jié)構(gòu)。主核上的系統(tǒng)先啟動,并負(fù)責(zé)準(zhǔn)備好運(yùn)行環(huán)境,然后根據(jù)需要或者一定規(guī)則啟動從核并對其進(jìn)行管理。主-從核心上的系統(tǒng)都準(zhǔn)備好之后,他們之間就通過IPC(Inter Processor Communication)方式進(jìn)行通信,而RPMsg就是IPC中的一種。對于非通用的操作系統(tǒng),它上面很可能是沒有搭載傳統(tǒng)的TCP/IP協(xié)議棧的,因此,當(dāng)主核想要通過RPC的方式調(diào)用從核上的服務(wù)的時候,便不能使用一般的RPC框架所采用的網(wǎng)絡(luò)通信方式。這時候類似于RPMsg這種專門用于核間通信的通信協(xié)議就派上了用場。

2. RPMsg

2.1. Linux中的RPMsg

在Linux內(nèi)核代碼中,RPMsg的代碼主要位于drivers/rpmsg/下,文件之間的主要關(guān)系如圖2-1所示。一開始Linux中只使用VirtIO作為該協(xié)議傳輸層,后來又增加了Glink、SMD等,Glink和SMD主要用于高通平臺。

用戶代碼通過操縱rpmsg驅(qū)動,實(shí)現(xiàn)數(shù)據(jù)的收發(fā)操作。所有數(shù)據(jù)都在RPMsg總線上傳遞。

圖2-1 Linux中RPMsg代碼結(jié)構(gòu)

2.2. 原理

在AMP系統(tǒng)中,主-從核心通過共享內(nèi)存的方式進(jìn)行通信,如圖2-2所示。內(nèi)存的管理由主核負(fù)責(zé),在每個通信方向上都有兩個緩沖區(qū),分別是USED和AVAIL,這個緩沖區(qū)可以按照RPMsg中消息的格式分成一塊一塊鏈接形成一個環(huán),如圖2-3所示。


圖2-2 AMP中主從通信方式
圖2-3 RPMsg中的消息緩沖區(qū)示意圖

當(dāng)主核需要和從核進(jìn)行通信的時候可以分為四步,如圖2-4所示:

  1. 主核先從USED中取得一塊內(nèi)存;
  2. 將消息按照消息協(xié)議填充;
  3. 將該內(nèi)存鏈接到AVAIL換中;
  4. 觸發(fā)中斷,通知從核有消息處理。
圖2-4 主核發(fā)消息給從核

反過來,從核需要和主核通信的時候也類似,如圖2-5所示:

  1. 主核先從AVAIL中取得一塊內(nèi)存;
  2. 將消息按照消息協(xié)議填充;
  3. 將該內(nèi)存鏈接到USED換中;
  4. 觸發(fā)中斷,通知主核有消息處理。
圖2-5 從核發(fā)消息給主核

2.3. 協(xié)議

既然是一種信息交換的協(xié)議,與TCP/IP類似,RPMsg協(xié)議也有分層,主要分為三層,分別是傳輸層、MAC層和物理層,如圖2-6所示:


圖2-6 RPMsg協(xié)議層

各層在Linux代碼的對應(yīng)情況如圖2-7所示。

圖2-7 RPMsg結(jié)構(gòu)

并且,在rpmsg 總線上的消息都具有以下結(jié)構(gòu),包含消息頭和數(shù)據(jù)兩部分。消息頭與TCP/IP協(xié)議的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并未形成相關(guān)的標(biāo)準(zhǔn)文檔,但Linux內(nèi)核中已經(jīng)有了RPMsg的實(shí)現(xiàn)并給出了相關(guān)定義,OpenAMP也參照Linux中的定義做出了自己的實(shí)現(xiàn)。因此,這里對相關(guān)的API做些簡單的介紹。可能在不久的將來,RPMsg可以從一個事實(shí)上的標(biāo)準(zhǔn)變成一個真正的標(biāo)準(zhǔn),畢竟,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 設(shè)備:
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 設(shè)備:
void rpmsg_deinit_vdev(struct rpmsg_virtio_device *rvdev)`
  1. 從RPMsg virtio設(shè)備中獲取RPMsg設(shè)備:
struct rpmsg_device *rpmsg_virtio_get_rpmsg_device(struct rpmsg_virtio_device *rvdev)
  1. 創(chuàng)建 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 終端是否已經(jīng)與遠(yuǎn)程的終端綁定,以及是夠已經(jīng)準(zhǔn)備好可以發(fā)送信息:
int is_rpmsg_ept_ready(struct rpmsg_endpoint *ept)
  1. 通過默認(rèn)的RPMsg 終端發(fā)送信息:
int rpmsg_send(struct rpmsg_endpoint *ept, const void *data, int len)
  1. 通過制定的終端以以及目的地地址發(fā)送信息:
int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len,
       uint32_t dst)
  1. 通過指定原地址和目的地址發(fā)送消息:
int rpmsg_send_offchannel(struct rpmsg_endpoint *ept,
            uint32_t src, uint32_t dst,
            const void *data, int len)
  1. 嘗試通過默認(rèn)的終端發(fā)送信息,如果當(dāng)前沒有緩存可用則返回:
int rpmsg_trysend(struct rpmsg_endpoint *ept, const void *data,
        int len)
  1. 嘗試通過制定的終端以以及目的地地址發(fā)送信息,如果當(dāng)前沒有緩存可用則返回:
int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len,
          uint32_t dst)
  1. 嘗試通過指定原地址和目的地址發(fā)送消息,如果當(dāng)前沒有緩存可用則返回:
int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept,
               uint32_t src, uint32_t dst,
               const void *data, int len)
本文首發(fā)于個人微信公眾號TensorBoy,微信掃描上方二維碼或者微信搜索TensorBoy并關(guān)注,及時獲取最新文章。C++ | Python | Linux | 原理 | 源碼,有一起玩耍的么?

4. References

[1] https://elinux.org/images/3/3b/NOVAK_CERVENKA.pdf
[2] https://github.com/OpenAMP/open-amp/wiki/AMP-Intro
[3] https://github.com/NXPmicro/rpmsg-lite
[4] https://github.com/torvalds/linux/tree/master/drivers/rpmsg

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容