进程间共享内存模型

概述

本文档描述了用于交换数据消息的软件组件的进程间共享内存通信的模型。该模型的目标是提供适用于实时发布-订阅 DDS(数据分发服务)的基于共享内存的传输层。

语境

eProsima 已收到商业提案,以实施共享内存传输作为其 FastRPTS 产品的改进。本文档中列出的目标是从客户提案中提取的。

对标准网络传输的改进

  • 减少操作系统内核调用:这对于 UDP / TCP 等传输是不可避免的(即使使用环回接口)。
  • 大消息支持:网络堆栈需要对大数据包进行分段。
  • 避免数据序列化/反序列化过程:这在异构网络中是不可能的,但在共享内存进程间通信中是可能的。
  • 减少内存副本:一个缓冲区可以直接与多个阅读器共享,无需额外的副本。

目标

  • 提高同一台机器中进程的通信性能。
  • 创建一个可移植的共享内存库(Windows / Linux / MacOS)。
  • 文档。
  • 测试
    *示例/教程。

架构

设计理念

  • Segment:是一块固定大小的共享内存,可以从不同的进程访问。共享内存段有一个全局名称,因此任何知道该名称的进程都可以打开该段并将其映射到其地址空间中。

  • SegmentId:SegmentIds 是唯一标识共享内存段的名称,这些名称是 16 个字符的 UUID。

  • 共享内存缓冲区:是在共享内存段中分配的缓冲区。

  • 缓冲区描述符:共享内存缓冲区可以由缓冲区描述符引用,这些描述符就像指向缓冲区的指针,可以在进程之间以最低成本复制。描述符包含 SegmentId 和从段基到数据的偏移量。

  • 共享内存端口:是由 port_id(uint32_t 编号)标识的通信通道。通过这个通道,缓冲区描述符被发送到其他进程。它在共享内存中有一个环形缓冲区,用于存储描述符。同一个端口可以被多个进程打开进行读写操作。可以在一个端口中注册多个侦听器,以便在将描述符推送到环形缓冲区时得到通知。多个数据生产者可以将描述符推送到一个端口。端口包含一个原子计数器,其中注册了侦听器的数量,环形缓冲区中的每个位置也有一个初始化为侦听器数量的计数器,当侦听器读取描述符时,递减计数器,因此环形缓冲区位置将当计数器为零时被认为是空闲的。该端口还有一个进程间条件变量,侦听器将在其中等待传入的描述符。

  • Listener:监听器监听推送到端口的描述符。 Listener 为应用程序提供了一个接口来等待和访问描述符所引用的数据。当消费者从端口侦听器中弹出一个描述符时,查看描述符的 SegmentId 字段以打开源共享内存段(如果尚未在此进程中打开),一旦源段映射到本地,消费者就能够访问数据使用描述符中包含的偏移量字段。

  • SharedMemoryManager:应用程序实例化此对象以访问上述共享内存资源。每个进程至少需要一个内存管理器。管理器为应用程序提供功能以创建共享内存段、在这些段中分配数据缓冲区、将缓冲区描述符推送到共享内存端口以及创建与端口关联的侦听器。

示例场景


让我们研究一下上面的例子。有三个进程,每个进程都有一个 SharedMemManager,每个进程创建自己的共享内存段,用于存储将与其他进程共享的缓冲区。

P1、P2 和 P3 进程是 RTPS-DDS 环境中的参与者。参与者的发现是通过多播来完成的,所以我们选择了共享内存port0作为“多播”端口进行发现,因此,所有参与者首先要做的就是打开共享内存port0。每个参与者也会创建一个连接到 port0 的侦听器来读取传入的描述符。
每个参与者打开一个端口来接收单播消息,分别是端口 1、2 和 3,并创建与这些端口关联的侦听器。
参与者发送的第一条消息是多播发现消息:“我在这里,我正在监听端口 N”。所以他们在其本地段中分配一个缓冲区,将该信息写入缓冲区并通过端口0推送缓冲区的描述符。在所有进程都推送了它们的发现描述符之后,观察端口 0 的环形缓冲区如何将描述符存储到 Data1a(P1)、Data2a(P2)、Data3a(P3)。
在发现阶段之后,参与者知道其他参与者及其“单播”端口,因此他们可以通过推送到参与者的单播端口来向特定参与者发送消息。
最后,让我们观察 P1 如何与 P2 和 P3 共享 Data1c,这是通过将缓冲区描述符推送到 P2 和 P3 单播端口来完成的。这样一个共享内存缓冲区可以与多个进程共享,而无需复制缓冲区(只需复制描述符)。这是对 UDP 和 TCP 等传输的重要改进。

设计注意事项

  • 最小化全局进程间锁:进程间锁是危险的,因为如果涉及的进程之一在持有进程间锁时崩溃,所有协作进程都可能受到影响。在此设计中,将数据推送到端口并从端口读取数据是无锁操作。出于性能原因等待端口上的数据,当端口为空时,需要进程间锁定机制,如互斥锁和条件变量。这在多播端口中特别危险,因为如果一个侦听器在等待时崩溃,这可能会阻塞其余侦听器的端口。可以添加更复杂的设计功能以使库具有容错性,但这可能会以牺牲性能为代价。

  • 可扩展的进程数量:每个进程创建自己的共享内存段来存储本地生成的消息。这比在所有涉及的进程之间共享一个全局段更具可扩展性。

  • 每个应用程序/进程可自定义的内存使用量:同样,本地共享内存段允许根据应用程序的要求调整段的大小。例如,想象一个应用程序发送两种类型的数据:视频和状态信息。它可以创建一个大段来存储视频帧,并创建一个小段来存储状态消息。

未来改进

  • 容错:如设计注意事项所述,进程崩溃持有进程间资源的可能性是真实存在的。为这些情况实施容错并非易事。超时、保持活动检查和垃圾收集器是可以添加到初始设计中以实现容错的一些东西。这将在未来的修订中考虑。

将设计映射到 FastRTPS

传输层

  • Locators: LOCATOR_KIND_SHM 定义为标识共享内存传输端点。 Locator_t 字段是这样填充的:

    • kind:16(是 RTPS 供应商特定的范围)。
    • port:定位器的端口包含共享内存port_id。
    • 地址:除第一个字节用于标记单播(地址[0]=‘U’)或多播(地址[0]=‘M’)外,整个地址设置为0。
  • SharedMemTransportDescriptor:可以自定义以下值:

    • segment_size:传输保留的共享内存段的大小。
    • port_queue_capacity:共享内存端口消息队列的大小,以消息数计。
    • port_overflow_policy:丢弃或失败。
    • segment_overflow_policy:丢弃或失败
    • max_message_size:默认情况下,max_message_size 将是段大小,但可以指定一个值 <= 段大小。在这种情况下,可能会出现碎片。
  • 默认元流量组播定位器:一个定位器,端口将由参与者选择(与 UDP 的 RTPS 标准相同)。

  • 默认元流量单播定位器:一个定位器,端口将由参与者选择(默认 port_id 将与 UDP 的 RTPS 标准中的相同)。

  • 默认输出定位器:将没有默认输出定位器。

  • OpenInputChannel:将创建一个 SharedMemChannelResource 实例。如果多次打开相同的输入定位器,则重复使用通道实例,则维护打开的通道向量。

  • OpenOutputChannel:每个传输实例只有一个 SharedMemSenderResource 实例,打开端口的 unordered_map 将由 SharedMemSenderResource 对象维护,以便将目标定位器与共享内存端口匹配。

类设计

RTPS 层

在 FastRTPS 中,传输与参与者相关联,因此可以通过将 SharedMemTransportDescriptor 类的实例添加到参与者的用户传输列表中来使用新的 SHM 传输。
因此,每个参与者都有一个共享内存段,将由所有参与者的发布者共享。

传输选择:由于 RTPSParticipant 能够拥有多个传输,因此在与可通过多个传输到达的其他参与者进行通信时,传输选择机制是必要的。这里定义的行为是:如果涉及的参与者在同一主机中并且都配置了 SHM 传输,则为这些参与者之间的所有通信选择 SHM 传输。

FastRTSP 内部设计相关推荐

  1. vcm驱动芯片原理_T6322A|电源芯片的内部设计是怎样的?

    很多工程师每天都跟芯片打交道,却不了解芯片的内部结构是什么样的,导致芯片出了问题不能准确地判断,这是基础知识不够完备的表现.下面就来讲讲电源芯片的内部设计是怎样的. 一.电源芯片的各个功能是怎么实现的 ...

  2. 解析IntelliJ IDEA内部设计

    IntelliJ IDEA第一版发布于2001年1月,这是第一款集成了高级代码导航和代码重构功能的Java IDE. 2009年,JetBrains开源了其社区版.从那时开始,就新出现了许多基于其社区 ...

  3. 安卓收藏功能怎么实现_从电源芯片的内部设计,看各个功能是怎么实现的

    作为一名电源研发工程师,自然经常与各种芯片打交道,可能有的工程师对芯片的内部并不是很了解,不少同学在应用新的芯片时直接翻到Datasheet的应用页面,按照推荐设计搭建外围完事.如此一来即使应用没有问 ...

  4. intellij idea_IntelliJ IDEA内部设计

    intellij idea IntelliJ IDEA的第一个版本于2001年1月发布,当时它是第一个集成了高级代码导航和代码重构功能的Java IDE之一. 2009年,JetBrains开源了其社 ...

  5. IntelliJ IDEA内部设计

    IntelliJ IDEA的第一版于2001年1月发布,当时它是第一个集成了高级代码导航和代码重构功能的Java IDE之一. 2009年,JetBrains开源了其社区版本 . 从那时起,创建了许多 ...

  6. 一颗芯片的内部设计原理和结构

    关注.星标公众号,直达精彩内容 来源:网络素材 摘要 作为一名电源研发工程师,自然经常与各种芯片打交道,可能有的工程师对芯片的内部并不是很了解,不少同学在应用新的芯片时直接翻到Datasheet的应用 ...

  7. 三星w2014android,三星W2014评测:机身细节及内部设计

    更多细节 作为三星最顶级的商务手机,三星W2014的做工拥有不错的表现,下面从转轴.卡槽.键盘和机身内部了解下. 三星W2014机身的插槽较多,SIM卡和扩展卡插槽都被设置在翻盖这半部分.与W2013 ...

  8. Mxnet - Understanding weight shape for Dense Layer MXNET权重参数形状的疑惑(内部设计形式行列谁在前不用管,多个转置运算而已)

    Mxnet - Understanding weight shape for Dense Layer  MXNET https://stackoverflow.com/questions/474778 ...

  9. ros2 代码结构及源码解析

    一.ros2 软件架构 ROS 2 Documentation: Rolling 这部分网上内容很多 ROS2应用开发框架 消息数据流 二.ros2 命令代码解析 ros2 加载过程 ros2 act ...

最新文章

  1. linux交换分区的优化-参数优化必选
  2. 关于学习Python的一点学习总结(31->继承及多态)
  3. VS2010/MFC编程入门之二十九(常用控件:列表视图控件List Control 下)
  4. ABAP TBL控制插入和更改
  5. java注释日志打印_java 注解结合 spring aop 实现自动输出日志
  6. IDEA 2019.1 不支持lombok插件问题解决方案
  7. 拒绝做焦虑贩卖者的韭菜
  8. 打败 IE 的葵花宝典:CSS Bug Table
  9. linux配置git(一)安装git
  10. 并发-阻塞队列源码分析
  11. 一步步教你开发鸿蒙系统应用,So Easy
  12. 互联网的长在线、心跳和断线重连
  13. php中级联,php级联
  14. android客户端与服务器端的搭建,android客户端与服务器端的搭建.ppt
  15. Python调用华为API实现名人识别
  16. 九阳神功,扎马练起!类、对象、实例、实例化的理解!
  17. Simpletron模拟器(二)
  18. 新加坡国立大学计算机系访学,高盛华课题组徐衍钰(博)2019年8月-2020年1月于新加坡国立大学交流访学...
  19. 微博小尾巴自定义名字中的Android,新浪微博自定义来自XX小尾巴怎么改 新浪微博显示来自XX小尾巴设置教程...
  20. 多态是什么 父类如何调用子类的方法(美团面试)

热门文章

  1. 基于gnuradio的自适应陷波滤波器OOT模块(notch filter)
  2. 什么是动态DNS(DDNS)?
  3. JavaScript 输入一个数 返回 2数相乘 使得 2数尽可能接近
  4. 永远的金大侠-人工智能的江湖
  5. it企业实习_IT行业实习报告3篇
  6. classpath类路径是什么
  7. QT5:获取本机摄像头/usb摄像头 实现拍照保存功能
  8. 顺丰快递单号查询API接口调试demo-快递鸟API接口
  9. 数据库 课后习题答案
  10. 一次游戏服务器编码规则制定的经历