多核异构通信框架(RPMsg-Lite)

描述

概要

随着科技的飞速发展,计算需求日益复杂和多样化,传统的单核处理器已难以满足所有应用场景的需求。在这样的背景下,异构多核系统应运而生,成为推动计算领域进步的重要力量。异构多核系统不仅提高了计算效率,还优化了能耗,为众多领域带来了革命性的变革。

异构多核系统是指在一个芯片上集成多种不同类型的处理器核心,这些核心可能采用不同的指令集架构(ISA),具备不同的性能特性和功耗要求。这些核心可以是高性能的通用处理器核心,也可以是专为特定任务设计的专用核心,如图形处理单元(GPU)、数字信号处理器(DSP)或神经网络处理器(NPU)等。

异构多核系统的特点主要体现在以下几个方面:

性能提升:通过结合不同类型的处理器核心,异构多核系统能够充分发挥各核心的优势,实现计算性能的大幅提升。例如,高性能核心可以处理复杂的计算任务,而专用核心则可以加速特定类型的数据处理。

能效优化:异构多核系统能够根据任务需求动态调整核心的使用,避免资源浪费和不必要的功耗。对于计算密集型任务,可以使用高性能核心;而对于数据密集型任务,则可以利用专用核心进行高效的数据处理,从而实现能效比的最大化。

灵活性:异构多核系统能够适应多样化的应用场景,通过灵活的任务调度和核心分配,满足不同任务的需求。这使得系统能够同时处理多种类型的任务,提高整体计算效率。

并行处理:不同类型的核心可以并行工作,实现任务级别的并行处理。这种并行性可以进一步提高系统的整体性能,缩短计算时间。

多核通信

市面目前多核异构芯片形态:

形态 型号 核心组成 方案
ARM MCU系列 STM32H747XIH6U ARM Cortex-M7 + ARM Cortex-M4 RTOS(裸机) + RTOS(裸机)
ARM MPU系列 RK3568 四核 ARM Cortex-A55 1. 核心0:Linux + 核心1~3:RTOS(裸机)
2. 核心0~2(SMP):Linux + 核心3:RTOS(裸机)
ARM MPU系列 + ARM MCU系列 STM32MP157 双核ARM Cortex-A7 + ARM Cortex-M4 双核ARM Cortex-A7:Linux(RTOS) + ARM Cortex-M4:RTOS(裸机)
ARM MPU系列 + RISC-V系列 V853 ARM Cortex-A7 + RISC-V ARM Cortex-A7:Linux(RTOS) + RISC-V:RTOS(裸机)
RISC-V系列 + DSP系列 + ARM MCU系列 R128 RISC-V + HiFi5 DSP + ARM M33 RISC-V:RTOS + HiFi5 DSP:裸机 + ARM M33:RTOS

由于异构多核系统中集成了多种不同类型的处理器核心,这些核心之间需要进行高效的数据通信和协同工作,以确保整体系统的性能和稳定性。因此,通信机制在异构多核系统中扮演着至关重要的角色。为了确保核心间的顺畅通信,异构多核系统采用了多种通信协议和接口技术,如共享内存、消息传递接口(MPI)、高级可扩展接口(AEI)等。这些通信机制使得不同核心之间能够快速地传输数据、共享资源和协同执行任务,从而实现整体系统的高效运行。

异构多处理系统中往往会形成主-从(Master-Remote)结构。主核上的系统先启动,并负责准备好运行环境,然后根据需要或者一定规则启动从核并对其进行管理。主-从核心上的系统都准备好之后,他们之间就通过 IPC(Inter Processor Communication)方式进行通信,而 RPMsg 就是 IPC 中的一种。RPMsg,全称 Remote Processor Messaging,它定义了异构多核处理系统(AMP,Asymmetric Multiprocessing)中核与核之间进行通信时所使用的标准二进制接口。

芯片

常见的多核通信框架:OpenAMP, RPMsg,rpmsg-lite等,本片文章的主角是:rpmsg-lite

RPMsg-Lite介绍

RPMsg-Lite组件,它是远程处理器消息传递 (RPMsg) 协议的轻量级实现。RPMsg 协议定义了一个标准化的二进制接口,用于在异构多核系统中的多个核之间进行通信。与开放非对称多处理 (OpenAMP) 框架(https://github.com/OpenAMP/open-amp)的 RPMsg 实现相比,RPMsg-Lite 减少了代码大小、简化了 API 并改进了模块化。在较小的基于 Cortex-M0+ 的系统上,建议使用 RPMsg-Lite。RPMsg-Lite 是由 NXP Semiconductors 开发的开源组件,并在 BSD 兼容许可下发布。

RPMsg-Lite官方仓库:https://github.com/nxp-mcuxpresso/rpmsg-lite

RPMsg-Lite官方文档:https://nxp-mcuxpresso.github.io/rpmsg-lite

RPMsg-Lite源码目录:

 

.
├── common
│   └── llist.c
├── include
│   ├── environment
│   │   └── rt-thread
│   │       └── rpmsg_env_specific.h
│   ├── llist.h
│   ├── platform
│   │   └── RK3568
│   │       ├── rpmsg_config.h
│   │       └── rpmsg_platform.h
│   ├── rpmsg_compiler.h
│   ├── rpmsg_default_config.h
│   ├── rpmsg_env.h
│   ├── rpmsg_lite.h
│   ├── rpmsg_ns.h
│   ├── rpmsg_queue.h
│   ├── virtio_ring.h
│   └── virtqueue.h
├── rpmsg_lite
│   ├── porting
│   │   ├── environment
│   │   │   └── rpmsg_env_threadx.c
│   │   └── platform
│   │       └── RK3568
│   │           └── rpmsg_platform.c
│   ├── rpmsg_lite.c
│   ├── rpmsg_ns.c
│   └── rpmsg_queue.c
└── virtio
    └── virtqueue.c

 

创建目的

开发RPMsg-Lite的原因有多种:①是需要与RPMsg协议兼容的通信组件占用空间小;②是OpenAMP RPMsg实现的广泛API的简化。RPMsg协议没有记录,其唯一定义是由Linux内核和旧版OpenAMP实现给出的。这已经随着基于无锁共享内存的多核通信协议的出现而改变,它是一个标准化协议,允许多种不同的实现共存并且仍然相互兼容。

基于小型MC 的系统通常不实现动态内存分配。RPMsg-Lite中静态API的创建进一步减少了资源使用。动态分配不仅会额外增加5KB的代码大小,而且通信速度会变慢且确定性较差,这是动态内存引入的一个特性。下表显示了OpenAMP RPMsg实现和新RPMsg-Lite实现之间的一些粗略比较数据:

组件/配置 Flash[B] RAM[B]
OpenAMP RPMsg / Release (reference) 5547 456 + dynamic
RPMsg-Lite / Dynamic API, Release 3462 56 + dynamic
Relative Difference [%] ~62.4% ~12.3%
RPMsg-Lite / Static API (no malloc), Release 2926 352
Relative Difference [%] ~52.7% ~77.2%

框架说明

RPMsg-Lite的实现可以分为三个子组件。核心组件位于rpmsg_lite.c中。其中rpmsg_ns.c和rpmsg_queue.c是可选的,两个可选组件用于实现阻塞接收API(在rpmsg_queue.c中和动态“命名”端点创建和删除公告服务(在rpmsg_ns.c中)。

实际的“媒体访问”层在virtqueue.c中实现,它是与 OpenAMP 实现共享的少数文件之一。该层主要定义了共享内存模型,内部定义了vring或者virtqueue等用到的组件。

移植层分为两个子层:环境层和平台层。第一个子层将针对每个环境单独实现。(裸机环境已经存在并在rpmsg_env_bm.c中实现,FreeRTOS 环境在rpmsg_env_freertos.c等中实现)只有与所使用的环境匹配的源文件才会包含在目标应用程序项目中。第二个子层在rpmsg_platform.c中实现,主要定义中断启用、禁用和触发的低级函数。情况如下图描述:

芯片

核心子组件

该子组件实现了阻塞发送 API 和基于回调的接收 API。RPMsg 协议是传输层的一部分。这是通过使用所谓的端点来实现的。每个端点可以分配不同的接收回调函数。然而,需要注意的是,在当前的设计中,回调是在中断环境中执行的。因此,不鼓励在回调中执行某些操作(例如内存分配)。下图显示了 RPMsg 在类 ISO/OSI 分层模型中的作用:

芯片

队列子组件(可选):该子组件是可选的,需要在环境移植层中实现env_*_queue()函数。它使用阻塞接收API,这在RTOS环境中很常见。它支持复制和非复制阻塞接收功能。

名称服务子组件(可选):该子组件是RPMsg的Linux内核实现中存在的名称服务的最小实现。它允许通信节点发送有关“命名”端点(即通道)创建或删除的公告,并在应用程序回调中采取任何用户定义的操作来接收这些公告。用于接收名称服务公告的端点地址被任意固定为53(0x35)。

应用

应用程序应将 /rpmsg_lite/lib/include 目录放入包含路径,并在应用程序中包含rpmsg_lite.h头文件,或者选择包含rpmsg_queue.h和/或rpmsg_ns.h文件。RPMsg-Lite提供两个移植子层,但如果计划使用其他的RTOS,您需要实现其他RTOS的环境层(例如,rpmsg_env_xxxrtos.c)并将其包含在项目构建中。

堆栈的初始化是通过调用主端的rpmsg_lite_master_init()和远程端的rpmsg_lite_remote_init()来完成的。该初始化函数必须在任何RPMsg-Lite API调用之前调用。在init之后,需要创建一个通信端点,否则通信是不可能的。通过调用rpmsg_lite_create_ept()函数来完成。可以选择接受最后一个参数,在该参数中创建端点的内部上下文,以防RL_USE_STATIC_API选项设置为1。如果不是,堆栈将在内部调用env_alloc()为其分配动态内存。如果要使用基于回调的接收,则使用用户定义的回调数据指针将ISR回调注册到每个新端点。如果需要阻塞接收(在 RTOS 环境的情况下),则必须在调用rpmsg_lite_create_ept()之前调用rpmsg_queue_create()函数。队列句柄作为回调数据参数传递给端点创建函数,并且回调函数设置为rpmsg_queue_rx_cb()。然后可以使用 rpmsg_queue_receive() 函数在队列对象上侦听传入消息。rpmsg_lite_send()函数用于向对方发送消息。

RPMsg-Lite 还为发送和接收操作实现无复制机制。这些方法需要在应用程序中使用时必须考虑的细节。

无复制发送机制:该机制允许发送消息,而无需将数据从应用程序缓冲区复制到共享内存中的 RPMsg/virtio 缓冲区。要执行的无复制发送步骤的顺序如下:

调用rpmsg_lite_alloc_tx_buffer()函数获取virtio缓冲区并将缓冲区指针提供给应用程序。

将要发送的数据填充到预先分配的virtio缓冲区中。确保填充的数据不超过缓冲区大小(作为rpmsg_lite_alloc_tx_buffer()大小输出参数提供)。

调用rpmsg_lite_send_nocopy()函数将消息发送到目标端点。考虑缓存功能和virtio缓冲区对齐。

no-copy-receive机制:该机制允许读取消息,而无需将数据从共享内存中的virtio缓冲区复制到应用程序缓冲区。要执行的无复制接收步骤的顺序如下:

调用rpmsg_queue_recv_nocopy()函数获取指向接收数据的virtio缓冲区指针。

直接从共享内存中读取接收到的数据。

调用rpmsg_queue_nocopy_free()函数释放virtio缓冲区并使其可用于下一次数据传输。

用户有责任在取消初始化时销毁他创建的任何RPMsg-Lite对象。为此,函数rpmsg_queue_destroy()用于销毁队列,rpmsg_lite_destroy_ept()用于销毁端点,最后,rpmsg_lite_deinit()用于取消初始化RPMsg-Lite核间通信堆栈。在取消初始化队列之前,使用队列取消初始化所有端点。否则,您将主动使已使用的队列句柄失效,这是不允许的。RPMsg-Lite不会在内部检查这一点,因为它的主要目标是轻量级。

芯片

配置选项

RPMsg-Lite可以在编译时进行配置。默认配置在rpmsg_default_config.h头文件中定义。用户可以通过包含具有自定义设置的rpmsg_config.h文件来自定义此配置。下表总结了所有可能的 RPMsg-Lite 配置选项。

配置选项 默认值 用法
RL_MS_PER_INTERVAL (1) 用于轮询的非阻塞 API 函数中使用的延迟(以毫秒为单位)。
RL_BUFFER_PAYLOAD_SIZE (496) 缓冲区有效负载的大小,它必须等于 (240, 496, 1008, ...) [2^n - 16]
RL_BUFFER_COUNT (2) 缓冲区的数量,必须是 2 的幂 (2, 4, ...)
RL_API_HAS_ZEROCOPY (1) 启用/禁用零复制 API 函数。
RL_USE_STATIC_API (0) 启用/禁用静态 API 函数(无动态分配)。
RL_CLEAR_USED_BUFFERS (0) 在返回到启用/禁用的空闲缓冲区池之前清除已使用的缓冲区。
RL_USE_MCMGR_IPC_ISR_HANDLER (0) 当启用时,IPC 中断由多核管理器(IPC 中断路由器)管理;当禁用时,RPMsg-Lite 自行管理 IPC 中断。
RL_USE_ENVIRONMENT_CONTEXT (0) 启用后,环境层使用自己的上下文。某些环境需要 (QNX)。默认值为 0(无上下文,节省一些 RAM)。
RL_DEBUG_CHECK_BUFFERS (0) 启用后,将检查传递给rpmsg_lite_send_nocopy()和rpmsg_lite_release_rx_buffer()函数(由 RL_API_HAS_ZEROCOPY 配置启用)的缓冲区指针,以避免传递无效的缓冲区指针。默认值为 0(禁用)。请勿在 RPMsg-Lite 到 Linux 配置中使用。
RL_ALLOW_CONSUMED_BUFFERS_NOTIFICATION (0) 启用后,每次接收到的缓冲区被消耗并放入可用缓冲区队列时,都会通知对方。在 RPMsg-Lite 到 Linux 配置中启用此选项,以允许解除 Linux 阻塞发送的阻塞。默认值为 0(RPMsg-Lite 到 RPMsg-Lite 通信)。
RL_ALLOW_CUSTOM_SHMEM_CONFIG (0) 它允许定义自定义共享内存配置并替换 rpmsg_config.h 中与共享内存相关的全局设置。当多个实例并行运行但需要不同的共享内存排列(vring 大小和对齐、缓冲区大小和计数)时,这非常有用。默认值为 0(所有 RPMsg_Lite 实例使用由公共配置宏定义的相同共享内存排列)。
RL_ASSERT 请参阅rpmsg_default_config.h 断言实施。

   审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分