Iceoryx技术点分析

系统结构

由以下几部分组成:
● 一个 RouDi 守护进程
● 多个加载了 “Posh Runtime” 的进程

RouDi

RouDi(Routing Discovery)是冰羚系统的核心,并且负责如下功能:
● Service discovery(服务发现):
RouDi是Publisher(发布者)和Subscriber(订阅者)的中心节点
● Shared memory management(共享内存管理):
RouDi初始化冰羚系统所使用的共享内存以及内存分配
● System introspection(系统性能监查):
RouDi掌握冰羚系统当前已经存在的端口的所有信息,包括端口的连接以及其内存的使用情况,并且RouDi提供工具给应用方来查询这些信息。

RouDi可以被认为是冰羚系统的管理服务,任何运行的冰羚系统中都有一个运行的RouDi实例,RouDi使用Posh运行库中的模组来完成其管理的功能。

Posh Runtime

Posh Runtime 是一个拥有独立内存地址空间的运行实体,并且其参与了冰羚系统的运行。
在一个 POSIX 系统中,一个 Posh Runtime 和一个 Posix 进程是等价的。
Posh Runtime 可以向冰羚系统发布服务,或者发现别的 Posh Runtime 发布的服务。
通过 Posh Runtime 发布的服务之间通过 event (事件)进行通信,并且事件流是使用了合理的发布-订阅语法。
发布的服务必须显式地向 RouDi 进行注册才能和 RouDi 以及其他 Posh Runtime 进行通信。

共享内存

冰羚系统使用一个“management”段来管理服务间时间通信所使用的任意数量的“user”段。这些“user”段是被逻辑分区到 "mempools“(内存池)中,Mempools 中包含一定数量的大小相同的“memory chunks”(存储块)作为访问共享内存的基础单元。
冰羚所使用内存段(segments)的数量是定义在其 RouDi 守护进程启动运行的时候跟随的配置文件中的 [mempools] 项目下的,配置文件中 [mempools] 下面包含一个或者多个 segments 的配置,其中定义了每一个 segment 中包含的 chunk 的数量和 segment 中每一个 chunk 的大小。
在这里插入图片描述
在这里插入图片描述

Port

端口代表的数据流的实体,冰羚中有不同类型的端口,他们在使用时,携带的信息的类型是有区别的。
目前存在的端口类型有以下几种:
● SenderPort - 服务使用这种端口来输出任意数据
● ReceiverPort - 服务使用这种端口从其他服务接收任意数据
● InterfacePort - 本地冰羚系统和远程冰羚系统之间交互信息所使用的网关端口
一个 Publisher 通过一个 SenderPort 发布数据,类似的,一个 Subscriber 通过一个 ReceiverPort 接收数据。

服务发现

在冰羚中,Publishers 和 Subscriber 是通过其内在的 SenderPorts 和 ReceiverPorts 之间的连接来进行匹配的。SenderPorts 和 ReceiverPorts 之间的连接是建立在服务描述上的。
服务描述有如下组成:
● A service id - 服务ID,标识服务的类型
● A service instance id - 服务实例的ID,标识具体的服务实例
● An event id - 事件ID,标识当前服务的输出

所有的 SenderPorts 和 ReceiverPorts 创建时都需要提供服务描述,冰羚系统会根据端口创建时的服务描述来进行相互匹配并且自动连接不同的Port端口。
端口出现的顺序不是重要的因素(或者说不需要很关心端口出现的顺序),已经存在的 ReceiverPorts 会自动连接到后面创建的 SenderPorts ,只要其服务描述能够匹配即可连接。
此外,冰羚系统中已经存在的 SenderPorts 的信息是依赖于 InterfacePorts 的,这就允许一些实体(例如网关)使用这些 Port 端口,并且hook到本地冰羚系统的数据流以及创建一个和外部冰羚系统间的桥接。

零拷贝通信

内存分配

一个 SenderPort 被分配了共享内存用于写入数据,在一个 POSIX 系统中,这个操作被文件访问权限所约束,因为这个共享内存段实际上代表了虚拟文件。为了输出数据,一个 SenderPort 在其分配的内存段中预定了一个内存块,冰羚系统会选择满足输出数据结构大小的最小的那个内存块。
一个 SenderPort 显式地选择何时写入数据到内存块中并且分发数据给到所有依附于他的 ReceiverPorts (通过服务发现机制建立连接的那些 Receiver Port ),当发送数据后,指向内存块的一个指针会被放置到ReceiverPort 的队列中,ReceiverPort 因此可以方便的使用指针访问这个内存块的数据。
一个 ReceiverPort 必须显式的标识出何时完成了对于接收到的指定内存块的处理,之后,当所有和该 SenderPort 建立连接的 ReceiverPort 都标识出已经完成指定内存块的处理时,该内存块会被归还给内存池。

指针类型

在一个进程的虚拟地址空间中共享内存段可以被映射到不同的区域,为了处理这个特性,冰羚使用了特殊的指针类型: iox::RelativePointer(相对指针) 和 iox::RelocatablePointer(可重定位指针),这两种类型的指针可进行重定向,从而解决不同地址的内存映射对于内存块的定位的影响。
RelocatablePointer:
● pointer 和 pointee 位于同一个共享内存段
● 通过两者间的偏移量指向同一个 object,即存储的是两者间的偏移量而不是指向 object 的原始指针
● 拥有原始指针的大部分特性,由于只需要存储与指针本身大小相同的一个差异值,因此与原始指针相比开销更小
RelativePointer:
● pointer 和 pointee 位于不同的共享内存段(pointer in S1 指向 S2 的 object)
● 通过额外的注册机制解决两个不同内存段兼得问题:
○ 其中注册了要使用的所有段的起始地址和使用的内存大小
○ 每个段都由唯一的id标识,该id可以在首个应用程序注册时提供
● 要存储段基址(指针地址)和到 object 的距离,因此开销会比可重定位指针大一些
上述两种指针类型在 std::atomic 中不可用,可以使用 atomic_rocatable_ptr 模板来支持原子可重定向指针的用例,该对象可以由多个线程并发使用。

冰羚系统间通信

部署在不同主机上的冰羚系统间可以通过网桥(Gateways)进行网络连接。网桥负责同步不同冰羚系统中 SenderPorts之间发布的数据。目前 Iceoryx 支持 Cyclone-dds 在不同 host 间通信,如果小伙伴们感兴趣,愿意提供其他通信方式,Iceoryx 的开发者会很愿意在 GitHub 上单独为你建立一个仓库来实现这个需求(Iceoryx 的开发者们都很友善)。

下面是一个iceoryx发布订阅的C代码示例,其中一个发布者发布一个消息,而两个订阅者订阅这个消息: ```c #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include "iceoryx_posh/popo/subscriber.hpp" #include "iceoryx_posh/popo/publisher.hpp" #include "iceoryx_posh/runtime/posh_runtime.hpp" #include "iceoryx_posh/roudi/introspection_types.hpp" int main() { // 初始化iceoryx runtime iox::runtime::PoshRuntime::initRuntime("publisher"); // 创建发布者和订阅者 iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); iox::popo::Subscriber subscriber1({"Radar", "FrontLeft", "Object"}); iox::popo::Subscriber subscriber2({"Radar", "FrontLeft", "Object"}); // 订阅者1等待消息 subscriber1.subscribe(); printf("Subscriber 1 waiting for messages...\n"); // 订阅者2等待消息 subscriber2.subscribe(); printf("Subscriber 2 waiting for messages...\n"); // 发布者发布消息 printf("Publisher publishing message...\n"); publisher.publish("Hello, world!"); // 等待订阅者接收消息 while (true) { if (subscriber1.hasData()) { printf("Subscriber 1 received message: %s\n", subscriber1.getChunk()->userPayload()); break; } if (subscriber2.hasData()) { printf("Subscriber 2 received message: %s\n", subscriber2.getChunk()->userPayload()); break; } } // 清理资源并退出 iox::runtime::PoshRuntime::shutdownRuntime(); return 0; } ``` 需要注意的是,这个示例代码需要使用iceoryx库,需要将iceoryx库链接到您的项目中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值