Contents
IceOryx冰羚系统的结构
冰羚的组成如下:
- 一个RouDi守护进程
- 多个加载了"Posh Runtime"运行时的进程
RouDi守护进程
RouDi 的名称由来是’‘Rou’‘ting 和’‘Di’'scovery,其是冰羚系统的核心,并且负责如下功能:
- 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)。
共享内存管理
基础
当一个POSIX进程启动后,其就拥有了自己独立的虚拟地址空间。
虚拟地址空间的范围对于每一个进程都是相同的,但是对于特定地址上的数据来说,该地址上的数据对于每一个进程来说,其数据内容都是不同的。
一个应用使用指针访问其进程运行后所拥有的虚拟地址空间。在进程的虚拟地址空间中,有不少内存区域(memory areas)的数据是被加载或者映射(mapped)的。对于进程的地址空间来说,这些内存区域是不连续的。下面是可能出现在这些内存区域中的一些内容:
- 程序的运行指令(例如可执行文件中的.text段)
- 静态变量的申明(例如执行文件中的.data段)
- 加载的共享库中的执行指令(例如so中的.text段)
- 进程的栈
- 进程的堆
- 共享内存段
共享内存段依赖进程外部的物理存储(例如RAM上的一些区域或者文件系统),这些外部的物理存储是通过map映射的方式映射到自己的地址空间中,这样,进程就可以访问这些存储。
一个存储段可以被映射到多个进程中,但是在每个进程中,其被映射的基地址很可能是不同的。
The POSIX API provides the utilities for working with
shared memory segements.
组织
冰羚系统使用一个“management”段来管理服务间时间通信所使用的任意数量的“user”段。
这些“user”段是被逻辑分区到"mempools“(内存池)中,Mempools中包含一定数量的大小相同的“memory chunks”(存储块)
在冰羚系统中,Memory chunks(存储块)是访问共享内存的基础单元。
冰羚所使用内存段(segments)的数量是定义在其RouDi守护进程启动运行的时候跟随的配置文件中的[mempools]项目下的,配置文件中[mempools]下面包含一个或者多个segments的配置,其中定义了每一个segment中包含的chunk的数量和segment中每一个chunk的大小。
对于这个配置文件的使用,详细的可以查看 usage guide
通信机制
在这个章节中,我们会了解一下冰羚系统中的服务间通信的概念。
端口(Port)
端口代表的数据流的实体,冰羚中有不同类型的端口,他们在使用时,携带的信息的类型是有区别的。
目前存在的端口类型有以下几种:
SenderPort
- 服务使用这种端口来输出任意数据ReceiverPort
- 服务使用这种端口从其他服务接收任意数据InterfacePort
- 本地冰羚系统和远程冰羚系统之间交互信息所使用的网关端口(下面会有更多关于网关的说明)
本地冰羚系统中,服务之间的数据流我们一般使用Sender Port和Receiver Port之间的连接(connections)来描述。
冰羚系统中,一个 Publisher
(发布者)通过一个SenderPort
发布数据,类似的,一个Subscriber
(订阅者)通过一个 ReceiverPort
接收数据。
服务发现(Service Discovery / 端口连接(Port Wiring)
在冰羚中,Publisher
s 和 Subscriber
是通过其内在的SenderPort
s 和 ReceiverPort
s.之间的连接来进行匹配的。SenderPort
s 和 ReceiverPort
s.之间的连接是建立在服务描述(service description)上的。
服务描述(service description)有如下组成:
- A service id - 服务ID,标识服务的类型
- A service instance id - 服务实例的ID,标识具体的服务实例
- An event id - 事件ID,标识当前服务的输出
所有的SenderPort
s and ReceiverPort
s创建时都需要提供service description服务描述,冰羚系统会根据端口创建时的服务描述来进行相互匹配并且自动连接不同的Port端口。
端口出现的顺序不是重要的因素(或者说不需要很关心端口出现的顺序),已经存在的ReceiverPort
s会自动连接到后面创建的 SenderPort
s,只要其服务描述能够匹配即可连接。
此外,冰羚系统中已经存在的 SenderPort
s的信息是依赖于 InterfacePort
s.的,这就允许一些实体(例如网关)使用这些Port端口,并且hook到本地冰羚系统的数据流以及创建一个和外部冰羚系统间的桥接。
零拷贝服务间通信(Zero-copy Interservice Communication)
已经互相连接的SenderPort
s 和ReceiverPort
s 通过共享内存可以进行通信,这个催生了零拷贝通信(zero-copy communication)
一个 SenderPort
被分配了共享内存用于写入数据,在一个POSIX系统中,这个操作被文件访问权限所约束,因为这个共享内存段实际上代表了虚拟文件。
为了输出数据,一个 SenderPort
在其分配的内存段中预定了一个内存块,冰羚系统会只能的选择符合输出数据结构的大小最小的那个内存块,注意,那整个内存块被预定急事数据结构的大小小于这个内存块实际占有的大小。
一个 SenderPort
显式地选择何时写入数据到内存块中并且分发数据给到所有依附于他的ReceiverPort
s (通过服务发现机制建立连接的那些Receiver Port),当发送数据后,指向内存块的一个指针会被放置到ReceiverPort
. 的队列中,ReceiverPort
. 因此可以方便的使用指针访问这个内存块的数据。
一个 ReceiverPort
必须显式的标识出何时他已经完成了对于接收到的指定内存块的处理,之后,当所有和该 SenderPort
建立连接的 ReceiverPort
都标识出已经完成指定内存块的处理时,该内存块会被归还给内存池。
一个关于指针的备注(A Note on Pointers)
像之前已经讨论过的那样,在一个进程的虚拟地址空间中共享内存段可以被映射到不同的区域,为了处理这个特性,冰羚使用了特殊的指针类型: iox::RelativePointer
和 iox::RelocatablePointer
.
使用这些类型,不同地址的内存映射就不再会影响对于内存块的定位了。
更多关于上述指针类型的详细讨论可以参考here.
/iceoryx_utils/doc/relocatable_pointer/relocatable_pointer.md
冰羚系统间的通信(Internode Communication)
部署在不同主机上的冰羚系统间可以通过网桥(“Gateways”)进行网络连接。网桥负责同步不同冰羚系统中 SenderPort
s之间发布的数据。