轉自:https://www.jianshu.com/p/c7cdad8273ed
0. 起因
之前在RPC原理與FastRPC實現一文中介紹過RPC的原理,簡而言之,RPC就是實現本地程序調用位於另一個地址空間的例程(routine)的一種技術手段,其基本架構如圖0-1所示。

由於RPC只是一種技術手段,並沒有一個統一的標准,因此,每一種RPC框架根據其應用場景不同,所采用的實現方式也不盡相同。這些差異主要集中在兩個方面:
- 數據的序列化和反序列化方法不同:例如可以使用JSON、XML以及谷歌推出的Protocol Buffer、Flat Buffer等格式作為數據傳遞格式,甚至是自己定義的一套格式;
- 數據傳遞方法不一樣:例如可以通過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.2. 原理
在AMP系統中,主-從核心通過共享內存的方式進行通信,如圖2-2所示。內存的管理由主核負責,在每個通信方向上都有兩個緩沖區,分別是USED和AVAIL,這個緩沖區可以按照RPMsg中消息的格式分成一塊一塊鏈接形成一個環,如圖2-3所示。


當主核需要和從核進行通信的時候可以分為四步,如圖2-4所示:
- 主核先從USED中取得一塊內存;
- 將消息按照消息協議填充;
- 將該內存鏈接到AVAIL換中;
- 觸發中斷,通知從核有消息處理。

反過來,從核需要和主核通信的時候也類似,如圖2-5所示:
- 主核先從AVAIL中取得一塊內存;
- 將消息按照消息協議填充;
- 將該內存鏈接到USED換中;
- 觸發中斷,通知主核有消息處理。

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

各層在Linux代碼的對應情況如圖2-7所示。

並且,在rpmsg 總線上的消息都具有以下結構,包含消息頭和數據兩部分。消息頭與TCP/IP協議的UDP包非常像,並且是固定的,如圖2-8所示。

該消息格式的定義位於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 也是這么過來的嘛。
- RPMsg virtio主核初始化共享緩沖池(RPMsg virtio 從核不需要用到這個API):
void rpmsg_virtio_init_shm_pool(struct rpmsg_virtio_shm_pool *shpool, void *shbuf, size_t size)
- 初始化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)
- 銷毀 RPMsg virtio 設備:
void rpmsg_deinit_vdev(struct rpmsg_virtio_device *rvdev)`
- 從RPMsg virtio設備中獲取RPMsg設備:
struct rpmsg_device *rpmsg_virtio_get_rpmsg_device(struct rpmsg_virtio_device *rvdev)
- 創建 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)
- 銷毀 RPMsg 終端:
void rpmsg_destroy_ept(struct rpsmg_endpoint *ept)
- 檢查本地RPMsg 終端是否已經與遠程的終端綁定,以及是夠已經准備好可以發送信息:
int is_rpmsg_ept_ready(struct rpmsg_endpoint *ept)
- 通過默認的RPMsg 終端發送信息:
int rpmsg_send(struct rpmsg_endpoint *ept, const void *data, int len)
- 通過制定的終端以以及目的地地址發送信息:
int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, uint32_t dst)
- 通過指定原地址和目的地址發送消息:
int rpmsg_send_offchannel(struct rpmsg_endpoint *ept, uint32_t src, uint32_t dst, const void *data, int len)
- 嘗試通過默認的終端發送信息,如果當前沒有緩存可用則返回:
int rpmsg_trysend(struct rpmsg_endpoint *ept, const void *data, int len)
- 嘗試通過制定的終端以以及目的地地址發送信息,如果當前沒有緩存可用則返回:
int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, uint32_t dst)
- 嘗試通過指定原地址和目的地址發送消息,如果當前沒有緩存可用則返回:
int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, uint32_t src, uint32_t dst, const void *data, int len)

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
作者:SunnyZhou1024
鏈接:https://www.jianshu.com/p/c7cdad8273ed
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。