RTOS 异构多核通信
异构多核通信介绍
R128 所带有的 M33 主核心与 C906, HIFI5 DSP 核心是完全不同的核心,为了最大限度的发挥他们的性能,协同完成某一任务,所以在不同的核心上面运行的系统也各不相同。这些不同架构的核心以及他们上面所运行的软件组合在一起,就成了 AMP 系统 (Asymmetric Multiprocessing System, 异构多处理系统)。
为了多核心协同工作,对于需要异构多核通信框架需要满足以下功能:
- 隔离核间差异,把一部分服务部署在一个核上,另一部分服务部署在另外的核上,应用层代码只需通过标准接口来申请服务,其对底层服务具体在哪个核上实现无感知。
- 同一个核,既可作为远程服务端,也可以作为客户端。
针对异构多核系统的特性,在进行远程服务调用时,需要解决以下几个问题:
- 缓存一致性问题。缓存一致性是在异构多核系统中十分重要的问题,跨核调用者和服务提供者必须知道其使用的 buffer 是否会经过其他核的修改,是否会被其他核读取数据。若被其他核修改,那当前核使用时,需要先无效 buffer 对应的 dcache;若会被其他核读取,则当前核写完数据后,需要将 buffer 对应的 dcache 刷回下一级内存。如此一来,一旦远程服务的参数或返回值比较复杂,那么使用者和服务提供者就需要花大量的精力来理清每个 buffer 的使用关系,极大地增加了他们的负担。并且,为了避免对其他数据造成影响,冲刷 dcache 时也还需要保证其数据独占一个 cacheline,否则会将其他数据误刷。这也会增加处理缓存一致性问题的难度。
- CPU 位宽不一致问题。在 R128 项目中,HIFI5 的 CPU 位宽为 32bit,C906 的 CPU 位宽为64bit,M33 的 CPU 位宽为 32bit。对于三种不同的核,软件上指针变量、long 类型变量的数据大小并不一致,那么就会导致同一个结构体在不同位宽的核上,其内存布局并不一致,三个核若直接读取则会发生错误。
- 复杂场景处理问题。在多个核之间,需要考虑到远程服务端的并发处理能力、核间的嵌套调用关系、服务端函数的休眠、如何降低内存使用开销等。这些场景都是需要进行优化处理的,服务端的并发处理能力会影响到跨核远程服务的高效性;核间的嵌套调用以及服务函数的休眠,影响到远程调用服务的稳定性。
为了解决这些问题,提供了 Sunxi-AMP 框架提供异构通讯的接口。同时也提供了 rpdata 实现更加底层的异构通讯
Sunxi‑AMP 简介
Sunxi‑AMP 工作流程图
线程池
amp_threadpool.c
文件中实现了一个简易线程池,在系统启动时创建指定线程数量的线程池,以提升远程消息的处理效率。若处理远程消息较多,无法及时处理,则由动态增加线程池中线程的数量;若消息较少,则动态删除线程池中的线程。初始化时创建的线程数量由 amp_threadpool.h
中的 AMP_THD_POOL_MIN_NUM
宏来决定,其也表示线程池中至少存在的最小数量的处理线程。AMP_THD_POOL_MAX_NUM
表示线程池中能同时存在的处理线程的最大数量。动态新增处理线程的条件是消息接受队列中存在两个或以上的未处理消息并且当前线程池中存活线程数量小于 AMP_THD_POOL_MAX_NUM
。动态删除处理线程的条件是 (正在处理消息的线程的数量 * 2) 小于系统中存活线程数量并且当前线程池中存活线程数量大于 AMP_THD_POOL_MAX_NUM
。
远程调用实现方式
Sunxi‑AMP 目前支持 1 种远程跨核调用实现方式:
- 参数指针传递:将远程跨核调用服务参数的指针或者数据值进行直接传递。如果参数是指针类型,则直接传递该指针;如果参数是数值,则直接传递该数值。参数指针传递比较适合参数结构体简单、核间数据传递量大的场景。其缺点是处理缓存一致性比较麻烦。
直接通过指针来传递参数及返回值,数据传递过程中可以免于拷贝,消耗内存较少,性能较高。
以 setConfig(struct config *data)
函数为例,在发起 setConfig
远程调用时,会创建一个sunxi_amp_msg_args
结构体,会将 setConfig
的参数依次设置到 sunxi_amp_msg_args
的 args
数组中,然后将 sunxi_amp_msg
的 data
字段设置为 sunxi_amp_msg_args
结构体地址。通过 msgbox
将sunxi_amp_msg
发送给另外的一个核后,另外一个核重新组装 sunxi_map_msg
,即可拿到参数数据。
Sunxi‑AMP 数据结构
消息结构
sunxi_amp_msg
结构体表示通过 msgbox
传输的消息内容,Sunxi‑AMP 远程跨核调用框架基于sunxi_amp_msg
消息来完成各种功能。一个完整的 sunxi_amp_msg
,包含以下字段,总共 12字节。
typedef struct _sunxi_amp_msg_t
{
uint32_t rpcid: 16; // 远程调用 ID 值,高 8 位表示 service id,低 8 位表示 function id
uint32_t prio: 5; // 发送端任务优先级
uint32_t type: 5; // 消息类型
uint32_t src: 3; // 源地址,即表示从哪个核发出的消息
uint32_t dst: 3; // 目的地址,即表示该消息发送到哪个核上
uint32_t data; // 消息数据
uint32_t flags; // 消息标识,当前设置为线程句柄,用于在远程调用堵塞等待时唤醒该线程
} sunxi_amp_msg;
其中远程调用的 ID 分为两个部分,高 8 位表示远程服务组 ID,低 8 位表示某个远程服务组中的 function ID
,也就是最多支持 256 个远程服务组,每个远程服务组最多支持 256 个远程调用。下面以 FSYS 文件系统服务为例,介绍 rpcid
的组成及其计算方法。
#define RPCCALL_FSYS(y) (RPCNO(RPCNO_FSYS, RPCHandler_FSYS_t, y) | (RPCSERVICE_FSYS_DIR << 29) | SELF_DIRECTION << 26)
RPCCALL_FSYS(y)
宏会自动计算rpcid
以及src
,dst
三个字段的值。RPCSERVICE_FSYS_DIR
表示FSYS
服务所在的核,它用来设置dst
字段。SELF_DIRECTION
表示当前所在的核,用于设置 src 字段。RPCNO(RPCNO_FSYS
,RPCHandler_FSYS_t, y)
用于计算rpcid
。其中,RPCNO_FSYS
计算出service ID
,放置于高 8 位,RPCNO
计算出function ID
,放置于低 8 位。
消息类型
MSG_SERIAL_FUNC_CALL // 序列化远程服务调用
MSG_SERIAL_FUNC_RET // 序列化远程服务返回值
MSG_SERIAL_FREE_BUFFER // 序列化远程服务释放内存
MSG_DIRECT_FUNC_CALL // 参数指针传递远程服务调用
MSG_DIRECT_FUNC_RET // 参数指针传递远程服务返回值
指针传递远程调用时的参数结构体
sunxi_amp_msg_args
用来表示使用指针传递远程调用时的参数信息,sunxi_amp_msg_args
结构体的地址会被设置到 sunxi_amp_msg
的 data
字段进行传递。
typedef struct _sunxi_amp_msg_args_t
{
uint32_t args_num: 8;
uint32_t reserved;
uint32_t args[SUNXI_AMP_MAX_ARGS_NUM];
} sunxi_amp_msg_args;
远程调用服务的服务函数表
sunxi_amp_func_table
用来表示远程调用服务的服务函数表。args_num
表示该服务的参数个数,return_type
表示该服务的返回值类型,func
表示服务函数指针。
typedef struct _sunxi_amp_func_table
{
uint32_t args_num: 8;
uint32_t return_type: 8;
sunxi_amp_service_func func;
} sunxi_amp_func_table;
Sunxi-AMP 源码结构
├── amp_core.c # Sunxi‑AMP 核心处理代码,包含消息解析等
├── amp_msgbox.c # Sunxi‑AMP msgbox 对接封装
├── amp_service.c # 远程服务数组
├── amp_stub.c # 触发远程服务的钩子函数
├── amp_test.c # Sunxi‑AMP 单核测试文件
├── amp_threadpool.c # Sunxi‑AMP 线程池
├── amp_threadpool.h # 线程池头文件
├── Kconfig # 配置文件
├── Makefile
├── msgbuffer.c # 对接 erpc 实现的源码,已废弃
├── msgbuffer.h
├── service # 已支持的远程调用服务
│ ├── audio # 音频远程调用服务
│ ├── bt # 蓝牙远程调用服务
│ ├── demo # erpc 测试用例
│ ├── flashc # flashc驱动远程调用服务
│ ├── fsys # 文件系统远程调用服务
│ ├── misc # 杂项远程调用服务,主要用于操作命令传递之类的场景
│ ├── net # wifi 网络远程调用服务
│ ├── pm # 休眠唤醒远程调用服务
│ ├── rpcconsole # 控制台远程调用服务
│ ├── rpdata # 远程数据获取调用服务,用于屏蔽复杂操作,使开发者仅关心数据获取及发送
│ └── tfm # 安全系统远程调用服务
├── sunxi_amp.h
├── sunxi_amp_msg.h
├── sunxi_amp_status.h
├── sunxi_amp_table.h
└── tests # 多核通信压力测试
└── test_stress.c
模块配置
M33 与 C906
System components ‑‑‑>
aw components ‑‑‑>
AMP Components Support ‑‑‑>
[*] Tina RTOS AMP # 使能 Sunxi‑AMP 组件
[*] AMP Funcall Thread # 使能通过任务处理函数调用
[*] AMP Funcall ThreadPool # 使能线程池
[*] AMP Change Service Task Priority # 使能优先级传递
Sunxi-AMP 接口说明
头文件
#include <sunxi_amp.h>
远程调用服务函数结构体
typedef struct
{
sunxi_amp_func_table *RPCHandler_FSYS;
sunxi_amp_func_table *RPCHandler_NET;
sunxi_amp_func_table *RPCHandler_BT;
sunxi_amp_func_table *RPCHandler_DEMO;
sunxi_amp_func_table *RPCHandler_ARM_CONSOLE;
sunxi_amp_func_table *RPCHandler_DSP_CONSOLE;
sunxi_amp_func_table *RPCHandler_RV_CONSOLE;
sunxi_amp_func_table *RPCHandler_PMOFM33;
sunxi_amp_func_table *RPCHandler_PMOFRV;
sunxi_amp_func_table *RPCHandler_PMOFDSP;
sunxi_amp_func_table *RPCHandler_FLASHC;
sunxi_amp_func_table *RPCHandler_M33_MISC;
sunxi_amp_func_table *RPCHandler_RV_MISC;
sunxi_amp_func_table *RPCHandler_DSP_MISC;
sunxi_amp_func_table *RPCHandler_AUDIO;
sunxi_amp_func_table *RPCHandler_RPDATA;
sunxi_amp_func_table *RPCHandler_TFM;
} RPCHandler_RPCT_t;
AMP 信息结构体
typedef struct _sunxi_amp_info_t
{
QueueHandle_t send_queue; /*send to remote processor */
QueueHandle_t recv_queue; /*receive from remote processor */
TaskHandle_t sendTask; /*send to remote processor */
TaskHandle_t recvTask; /*receive from remote processor */
struct msg_endpoint sedp_arm;
struct msg_endpoint sedp_rv;
struct msg_endpoint sedp_dsp;
sunxi_amp_wait wait;
QueueHandle_t amp_msg_heap_mutex;
} sunxi_amp_info;
AMP 消息结构体
typedef struct _sunxi_amp_msg_t
{
uint32_t rpcid: 16;
uint32_t prio: 5;
uint32_t type: 5;
uint32_t src: 3;
uint32_t dst: 3;
uint32_t data;
uint32_t flags;
} __attribute__((packed)) sunxi_amp_msg;
AMP操作结构体
typedef struct _sunxi_amp_msg_ops
{
sunxi_amp_msg_func send_to_queue;
sunxi_amp_msg_func send_to_dev;
sunxi_amp_msg_func receive_from_dev;
sunxi_amp_dev_init init;
} sunxi_amp_msg_ops;
AMP 消息类型枚举
enum MSG_TYPE
{
MSG_SERIAL_FUNC_CALL = 0,
MSG_SERIAL_FUNC_RET,
MSG_SERIAL_FREE_BUFFER,
MSG_DIRECT_FUNC_CALL,
MSG_DIRECT_FUNC_RET,
MSG_TYPE_NUM,
};
AMP 消息返回值枚举
enum FUNC_RETURN_TYPE
{
RET_NULL = 0,
RET_POINTER,
RET_NUMBER_32,
RET_NUMBER_64,
};
AMP 消息发送方向枚举
enum RPC_MSG_DIRECTION
{
RPC_MSG_DIR_UNKNOWN = 0,
RPC_MSG_DIR_CM33 = 1,
RPC_MSG_DIR_RV,
RPC_MSG_DIR_DSP,
};
AMP 消息参数
typedef struct _sunxi_amp_msg_args_t
{
uint32_t args_num: 8;
uint32_t reserved;
uint32_t args[SUNXI_AMP_MAX_ARGS_NUM];
} __attribute__((packed)) sunxi_amp_msg_args;
AMP 函数表结构体
typedef struct _sunxi_amp_func_table
{
uint32_t args_num: 8;
uint32_t return_type: 8;
sunxi_amp_service_func func;
} sunxi_amp_func_table;
获取 AMP 系统信息
函数原型
sunxi_amp_info *get_amp_info(void);
参数:
- 无
返回值:
- sunxi_amp_info 结构体
AMP发送消息
函数原型
int hal_amp_msg_send(sunxi_amp_msg *msg);
参数:
- msg:消息结构体
返回值:
- 0:成功
- -1:失败
获取 AMP 操作
函数原型
sunxi_amp_msg_ops *get_msg_ops(void);
参数:
- 无
返回值:
- sunxi_amp_msg_ops:操作结构体
AMP接收消息
函数原型
int hal_amp_msg_recv(sunxi_amp_msg *msg);
参数:
- msg:消息结构体
返回值:
- 0:成功
- -1:失败
发起远程函数调用
函数原型
unsigned long func_stub(uint32_t id, int haveRet, int stub_args_num, void *stub_args[]);
参数:
- id : 远程服务函数的 ID 值
- haveRet :是否存在返回值,在实际使用中,为了保证函数调用的顺序,该值需为 1
- stub_args_num : 远程服务函数的参数个数
- stub_args : 远程服务函数的参数
返回值:
- 远程服务函数的返回值
申请 cacheline 对齐的内存
函数原型
void *amp_align_malloc(int size);
参数:
- size : 需要申请的内存大小
返回值:
- 申请的内存地址
释放 cacheline 对齐的内存
函数原型
void amp_align_free(void *ptr);
参数:
- ptr:需要释放的内存地址
返回值:
- 无
申请 AMP 内存
函数原型
void *amp_malloc(int size);
参数:
- size : 需要申请的内存大小
返回值:
- 申请的内存地址
释放 AMP 内存
函数原型
void amp_free(void *ptr);
参数:
- ptr:需要释放的内存地址
返回值:
- 无