异构多核通信 (IPC)
概述
随着信息技术的发展与需求的提升,传统单核 SoC 不能满足需求,促进了同构多核与异构多核的发展,在异构多核框架中,整个系统中由多个不同的处理器、性能与用途格不相同的多个核心组成,各个核心发挥各自的计算优势,实现资源的最佳配置,很好的提升了整体性能,降低功耗,简化开发模式,因此多核间的通信机制,以及软件编程模型对多核性能的发挥尤为重要。本文介绍异构核间通信机制,考虑的因素有:
- 轻量级,可应用于 NOS / RTOS 的应用场景
- 简化各核心的开发模式
- 针对核间协同工作的特点,划分为:通用计算、并行计算、流式数据、帧式数据,应用于多种异构场景
- 可移植性,框架可以方便移植到 Linux,不同的 RTOS 中
异构多核处理器大多采用主从式的结构。主从式结构根据不同核的功能把处理器核分为主核和从核。主核的结构和功能一般较为复杂,负责全局资源、任务的管理和调度并完成从核的引导加载。从核主要接受主核的管理,负责运行主核分配的任务,并具有本地任务调度与管理功能。在多核处理器中,根据不同核的结构,各个核可运行相同或不同的操作系统。
异构多核实现原理
从上图可知,为实现异构多核之间的通信,芯片设计时,通过中断向另一个核发送中断请求,执行相应的中断处理程序进入地址传递,使用共享一块内存区域,实现数据传递,各核心对外设的访问,通过控制总线来配置。因此,要实现异构核间通信,各核心须有以下功能:
- 主处理器对从处理器进行管理
- 内部处理器之间的信息传输与交换
核间中断
共享内存
核间通信协议
IPC架构
Channel (通道)
Channel 实现多核之间的数据交互的通信机制,
struct channel {
slist_t next;
channel_cb cb;
void *priv;
uint32_t des_id;
void *context;
};
Event (事件)
Event 是对核间中断的抽象,定义为事件,系统中可以定义32个事件,事件是有优先级的,EventId越小优先级越高,事件0的优先级最高,随着EventId增大优先级依次递减;当多个事件被触发,优先级最高的会最先响应。
MessageQ (消息队列)
MessageQ(目前暂未实现),基于队列的消息传递,有以下特点:
实现了处理期间变长消息的传递;消息的传递都是通过操作消息队列来实现的;每个消息队列可以有多个写者,但只能有一个读者;每个任务(task)可以对多个消息队列进行读写;一个宿主在准备接收消息时,必须先创建消息队列,而在发送消息前,需要打开预定的接收消息队列。
FrameQ (帧缓冲队列)
FrameQ(目前暂未实现)是专门为传递视频帧而设计出来的组件。FrameQ的基本数据结构是可用于queue/dequeue数据的数据队列,封装了视频帧缓存指针、帧数据类型、帧宽、帧高、时间戳等信息。
对于FrameQ模块具有以下特征:支持多个读者,但写者唯一;可以分配和释放Frames;可以对指向同一块内存区多次分配和初始化成新的帧buffer;FrameQ允许有多个队列,在多通道的运用中,视频帧会根据通道号被分配到相应的帧队列中。
RingQ (环形缓冲区)
RingIO(目前暂未实现)是基于数据流的环形缓冲buffer,而且针对于音视频数据的特性做了优化。
RingIO支持一下特性:仅支持一个读者和一个写者;读写相对独立,可以在不同的进程或者处理器中同时进行读写操作。
接口定义
初始化ipc
ipc_t *ipc_get(int des_cpu_id);
初始化与目标cpu的通道,获取ipc句柄
- 参数
- des_cpu_id:目标cpu id
- 返回值:
- 成功返回ipc_t 指针,失败返回NULL
发送ipc消息
int ipc_message_send(ipc_t *ipc, message_t *msg, int timeout_ms);
IPC 消息发送,将消息通过 channel 发送到远程 IPC。当 msg 为同步消息时,该函数等待对方应答后才会返回,当 msg 异步消息发送完后直接返回。
typedef struct message_msg message_t;
struct message_msg {
uint8_t flag; /** flag for MESSAGE_SYNC and MESSAGE_ACK */
uint8_t service_id; /** service id for the service want to comtunicate */
uint16_t command; /** command id the service provide */
uint32_t seq; /** message seq */
void *req_data; /** message request data */
int req_len; /** message request data len */
aos_queue_t queue; /** queue for SYNC MESSAGE */
void *resp_data; /** message response data */
int resp_len; /** message response data len*/
};
- 参数
- ipc:ipc 句柄
- msg:ipc 消息
- timeout_ms:超时时间,单位ms
- 返回值:
- 成功返回0,失败返回-1
回复ipc消息
int ipc_message_ack(ipc_t *ipc, message_t *msg, int timeout_ms);
当 msg 为同步消息时,调用该函数
- 参数
- ipc:ipc 句柄
- msg:ipc 消息
- timeout_ms:超时时间,单位ms
- 返回值:
- 成功返回0,失败返回-1
添加服务
int ipc_add_service(ipc_t *ipc, int service_id, ipc_process_t cb, void *priv);
在ipc中增加一个服务
typedef struct ipc ipc_t;
typedef void (*ipc_process_t)(ipc_t *ipc, message_t *msg, void *priv);
- 参数
- ipc:ipc 句柄
- service_id:服务号
- cb:用户自定义ipc服务处理函数
- priv:用户自定义参数
- 返回值:
- 成功返回0,失败返回-1
lpm设置
int ipc_lpm(int cpu_id, int state);
参数:
cpu_id
: 目标cpu id。state
: 状态。
返回值:
- 0: 成功;-1: 失败。
示例代码
- AP
#define IPC_BUF_LEN (4096*2)
typedef struct {
ipc_t *ipc;
char data[IPC_BUF_LEN] __attribute__ ((aligned(64)));
} ipc_test_t;
ipc_test_t g_test[2];
#define TAG "AP"
/*ipc 同步调用示例*/
int ipc_sync(void)
{
message_t msg;
/* 设置message的command为103 */
msg.command = 103;
/* 设置同步标志 */
msg.flag = MESSAGE_SYNC;
/* 设置message的service_id为20 */
msg.service_id = 20;
msg.resp_data = NULL;
msg.resp_len = 0;
/* 设置一次发送数据量 */
int snd_len = 4096;
int offset = 0;
char *send = (char *)src_audio_music_raw;
while (offset < src_audio_music_raw_len) {
/* 设置message的请求data指针 */
msg.req_data = send + offset;
/* 设置message的请求data的长度 */
snd_len = 4096 < (src_audio_music_raw_len - offset)? 4096 : (src_audio_music_raw_len - offset);
msg.req_len = snd_len;
/* 发送message*/
ipc_message_send(g_test[0].ipc, &msg, AOS_WAIT_FOREVER);
/* 发送成功后将文件偏移发送数据量*/
offset += snd_len;
}
printf("ipc sync done\n");
return 0;
}
// async message demo
static char *s[] = {
"00000000",
"11111111",
"22222222",
"33333333",
"44444444",
"55555555",
"66666666",
"77777777",
"88888888",
"99999999",
};
/*ipc 异步调用示例*/
int ipc_async(void)
{
message_t msg;
memset(&msg, 0, sizeof(message_t));
/* 设置message的service_id为20 */
msg.service_id = 20;
/* 设置message的command为104 */
msg.command = 104;
/* 设置异步标志*/
msg.flag = MESSAGE_ASYNC;
msg.service_id = 20;
for (int i = 0; i < 100; i++) {
msg.req_data = s[i%10];
msg.req_len = strlen(s[i%10]) + 1;
/* 发送 message */
ipc_message_send(g_test[0].ipc, &msg, AOS_WAIT_FOREVER);
}
printf("ipc async done\n");
return 0;
}
int ipc_server_init(void)
{
ipc_test_t *i = &g_test[0];
/* 获取ipc (cpu_id:2)*/
i->ipc = ipc_get(2);
/* 添加ipc服务(service_id:20)*/
ipc_add_service(i->ipc, 20, NULL, NULL);
return 0;
}
- CP
#define TAG "CP"
#define IPC_BUF_LEN (1024)
typedef struct {
ipc_t *ipc;
char data[IPC_BUF_LEN] __attribute__ ((aligned(64)));
} ipc_test_t;
ipc_test_t g_test[2];
static char *s[] = {
"00000000",
"11111111",
"22222222",
"33333333",
"44444444",
"55555555",
"66666666",
"77777777",
"88888888",
"99999999",
};
static void cli_ipc_process(ipc_t *ipc, message_t *msg, void *priv)
{
switch (msg->command) {
case 104: {
/* 异步cmd处理 */
static int offset = 0;
/* 比较异步发送的数据是否正确*/
int ret = memcmp(s[offset % 10], msg->req_data, msg->req_len);
offset ++;
if (ret != 0) {
printf("ipc async err!!!\n");
}
if (offset == 100) {
printf("ipc async ok!!!\n");
offset = 0;
}
break;
}
case 103: {
char *music = (char *)src_audio_music_raw;
static int music_len = 0;
/* 比较同步发送的数据是否正确*/
int ret = memcmp(music + music_len, msg->req_data, msg->req_len);
music_len += msg->req_len;
if (ret != 0) {
printf("ipc sync err!!!\n");
}
if (music_len == src_audio_music_raw_len) {
/* 若文件发送完毕则打印成功*/
printf("music recv ok, total:(%d)\n", music_len);
music_len = 0;
}
/* 回复同步message的ack*/
ipc_message_ack(ipc, msg, AOS_WAIT_FOREVER);
}
default :
break;
}
}
int ipc_cli_init(void)
{
ipc_test_t *i = &g_test[1];
/* 获取ipc (cpu_id:0)*/
i->ipc = ipc_get(0);
/* 添加ipc服务(service_id:20)*/
ipc_add_service(i->ipc, 20, cli_ipc_process, i);
return 0;
}