在非对称多处理器系统中,不同核心最常见的协作方式是使用基于共享内存的通信。有许多自定义实现,这意味着所考虑的系统不能直接相互连接。因此,本文档的目的是基于现有组件(RPMsg、VirtIO)对这种通信进行标准化。
1.协议层
整个通信实现可以分为三个不同的 ISO/OSI 层——传输层、媒体访问控制层和物理层。它们中的每一层都可以单独实现,例如,传输层的多个实现可以共享媒体访问控制层(VirtIO)和物理层的相同实现。每一层在以下部分中进行描述。
1.1 物理层:共享内存
本文档中提出的解决方案仅需要两个基本硬件组件:
- 共享内存(可供通信双方访问)
- 内核间中断(在特定配置中可选)
最小配置要求每个通信内核有一条中断线,即总共需要两条中断线。
需要注意的是:
- 不需要内核间同步硬件元素:如内核间信号量、内核间队列或内核间互斥锁!
- 这是由于 virtqueue 的性质,它使用单写者单读者循环缓冲。
如果“已用”和“可用”环形缓冲区在其配置标志字段中设置了一位,则可以完全抑制中断的产生——在这种配置中,中断不是必需的。
然而,两个内核都需要轮询“环形”和“已用”环形缓冲区以获取新的传入消息,这可能不是最佳选择。
1.2 媒体访问层:virtIO
这一层是整个解决方案的关键部分:
- 由于这一层,无需进行内核间同步。
- 这是通过一种称为单写者单读者循环缓冲的技术实现的。
- 这是一种数据结构,使多个异步上下文能够交换数据。
然而,这种技术仅适用于核心对核心的配置,而不适用于核心对多核的配置,因为在这种情况下,会有多个写入者写入“输入”环形缓冲区。这将需要一个同步元素,比如信号量?这是不可取的。
上面展示的图片描述了 vring 组件。Vring 由三个基本部分组成:
- 缓冲区描述符池
- “可用”环形缓冲区(或输入环形缓冲区)
- “已用”环形缓冲区(或空闲环形缓冲区)
- 这三个元素都物理存储在共享内存中。
- 都是为了管理共享buffer而增加的数据结构。
1.2.1 buffer descriptors
每个缓冲区描述符包含:
- 一个 64 位缓冲区地址
- 该地址保存指向存储在共享内存中的缓冲区的地址(从这个虚拟环形缓冲区的“接收者”或主机物理上看)
- 一个 32 位的长度变量
- 一个 16 位的标志字段
- 一个 16 位的指向下一个缓冲区描述符的链接。
- 该链接用于链接未使用的缓冲区描述符,并将标志字段中设置了 F_NEXT 位的描述符链接到链中的下一个描述符。
- 该链接用于链接未使用的缓冲区描述符,并将标志字段中设置了 F_NEXT 位的描述符链接到链中的下一个描述符。
输入环形缓冲区包含的自身标志字段,flags:
- 其中仅使用第 0 位——如果该位被设置,当“reader”端从输入或“avail”环形缓冲区中消耗一个缓冲区时,不应通知“writer”端。
- 默认情况下,该位未被设置,因此在reader消耗一个缓冲区后,应通过触发中断来通知writer。
- 输入环形缓冲区的下一个字段是头的索引,在将包含新消息的缓冲区索引写入 ring[x]字段后,由writer更新该索引。
1.2.2 used ringbuffer
vring 的最后一部分是“used”环形缓冲区:
- 它还包含一个标志字段,并且仅使用第 0 位——如果设置了该位,当读取器更新此空闲环形缓冲区的头索引时,写入器端将不会收到通知。
- 下图显示了环形缓冲区结构。
- 已用环形缓冲区与可用环形缓冲区不同。对于每个条目,还存储了缓冲区的长度。
“used”和“avail”环形缓冲区都有一个标志字段。其目的主要是告诉写入者在更新环形缓冲区的头部时是否应该中断另一个核心。在“used”和“avail”环形缓冲区中,相同的位用于此目的:
1.3 传输层:RPMsg
1.3.1 报头定义
每个 RPMsg 消息都包含在一个缓冲区中,该缓冲区存在于共享内存中。
- 这个缓冲区由来自 vring 的缓冲区描述符池中的缓冲区描述符的地址字段指向。
- 这个缓冲区的前 16 个字节由传输层(RPMsg 层)内部使用。
- 第一个字(32 位)用作发送方或源端点的地址
- 下一个字是接收方或目标端点的地址。
- 出于对齐原因有一个保留字段(因此 RPMsg 头部是 16 字节对齐的)。
- 头部的最后两个字段是有效负载的长度(16 位)和一个 16 位的标志字段。
- 保留字段不用于在核心之间传输数据,可在 RPMsg 实现中内部使用。用户有效负载跟在 RPMsg 头部之后。
RPMsg 头的标志字段目前未被 RPMsg 使用且被保留。它可以被释放供应用程序使用,但这可能被认为是不一致的——RPMsg 头将不整齐,因此保留字段将无用。
1.3.2 RPMsg Channel通道
RPMsg 组件中的每个远程内核都由 RPMsg 设备表示,该设备在主设备和远程设备之间提供通信通道,因此 RPMsg 设备也被称为通道。RPMsg 通道由文本名称以及本地(源)和目标地址标识。RPMsg 框架使用通道名称跟踪通道。
RPMsg 端点在 RPMsg 通道之上提供逻辑连接。它允许用户在同一通道上绑定多个接收回调。
- 每个 RPMsg 端点都有一个唯一的源地址和相关联的回调函数。
- 当应用程序使用本地地址创建一个端点时,所有目标地址等于该端点本地地址的后续入站消息都将被路由到那个回调函数。
- 每个通道都有一个默认端点,这使得应用程序无需创建新的端点即可进行通信。
这里的地址是一种标识符,用于在 RPMsg 通信中区分不同的端点和通道。
例如,在一个多核处理器系统中:
- 不同的核心可以被视为不同的通信实体
- 每个核心上的应用程序可以通过分配不同的地址来创建自己的 RPMsg 端点
- 当一个核心上的应用程序发送消息时,它会使用特定的源地址来标识自己,并将目标地址设置为接收方核心上的某个特定端点的地址。
- 这些地址可以是任意的数值,但在实际应用中通常会遵循一定的命名规则或分配策略,以确保地址的唯一性和可管理性。
- 它们可以由系统自动分配,也可以由开发者根据特定的需求进行手动配置。
2.RPMsg通信流
以下图形描述了从一个内核到另一个内核传输 RPMsg 消息所使用的序列。该序列根据内核 A 和内核 B 的角色而有所不同。在图中:
- 内核 A 是主内核,内核 B 是远程内核。
- 主内核从 vring 的“used”环形缓冲区分配用于传输的缓冲区
- 主内核向其中写入 RPMsg 头和应用程序有效负载,然后将其排队到“可用”环形缓冲区。
远程核心从“avail”环形缓冲区获取接收到的 RPMsg 缓冲区,对其进行处理,然后将其返回到“used”环形缓冲区。当远程核心向主核心发送消息时,“可用”和“已用”环形缓冲区的角色会互换。