问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Iceoryx2:高性能进程间通信框架(中间件)

创作时间:
作者:
@小白创作中心

Iceoryx2:高性能进程间通信框架(中间件)

引用
CSDN
1.
https://blog.csdn.net/stallion5632/article/details/143448592

Iceoryx2是一个基于Rust实现的高性能进程间通信框架,专为低延迟和零拷贝通信设计。相比其前身Iceoryx,Iceoryx2在内存安全、并发处理、模块化设计以及多平台支持上进行了优化。本文将详细介绍Iceoryx2的主要改进、架构设计、使用示例以及与Iceoryx的比较。

0. 引言

Iceoryx2是一个基于Rust实现的开源中间件,专为实现低延迟和零拷贝进程间通信而设计。相比其前身Iceoryx,Iceoryx2在内存安全、并发处理、模块化设计以及多平台支持上进行了优化。

提前阅读:

  • C++高性能通信:图形简述高性能中间件Iceoryx
  • C++高性能通信:了解Iceoryx与零拷贝技术的实现与应用
  • 详解高性能中间件Iceoryx在ROS2中的使用

1. 主要改进

  1. 零拷贝通信:Iceoryx2保留了零拷贝通信的特性,通过直接在进程间传递数据引用,极大减少了数据复制,从而提升了性能并降低了延迟。
  2. Rust语言的引入:采用Rust语言后,Iceoryx2提升了内存安全和并发安全性。Rust的所有权和借用机制有效防止了数据竞争和其他常见的并发错误。
  3. 模块化和扩展性:模块化设计允许单独替换或升级内部组件。
  4. 跨平台支持:除了在Linux和Windows上的原生支持,Iceoryx2还计划扩展到Android、QNX等多个平台。
  5. 支持多种编程语言:Iceoryx2不仅提供C和C++的API绑定,还支持Python和其他编程语言。

2. Iceoryx2的架构

以下是Iceoryx2的架构图,展示了主要应用模块、通信网关及支持的开发语言:

3. C++示例代码

以下是Iceoryx2的发布者和订阅者的示例代码:

3.1 发布者示例(publisher.cpp)

#include "iox/duration.hpp"
#include "iox/slice.hpp"
#include "iox2/node.hpp"
#include "iox2/sample_mut.hpp"
#include "iox2/service_name.hpp"
#include "iox2/service_type.hpp"
#include <cstdint>
#include <iostream>
#include <utility>

constexpr iox::units::Duration CYCLE_TIME = iox::units::Duration::fromSeconds(1);

auto main() -> int {
    using namespace iox2;
    auto node = NodeBuilder().create<ServiceType::Ipc>().expect("successful node creation");
    auto service = node.service_builder(ServiceName::create("Service With Dynamic Data").expect("valid service name"))
                       .publish_subscribe<iox::Slice<uint8_t>>()
                       .open_or_create()
                       .expect("successful service creation/opening");
    uint64_t worst_case_memory_size = 1024; // NOLINT
    auto publisher = service.publisher_builder()
                         .max_slice_len(worst_case_memory_size)
                         .create()
                         .expect("successful publisher creation");
    auto counter = 1;
    while (node.wait(CYCLE_TIME).has_value()) {
        counter += 1;
        auto required_memory_size = (8 + counter) % 16; // NOLINT
        auto sample = publisher.loan_slice_uninit(required_memory_size).expect("acquire sample");
        sample.write_from_fn([&](auto byte_idx) { return (byte_idx + counter) % 255; }); // NOLINT
        auto initialized_sample = assume_init(std::move(sample));
        send(std::move(initialized_sample)).expect("send successful");
        std::cout << "Send sample " << counter << "..." << std::endl;
    }
    std::cout << "exit" << std::endl;
    return 0;
}

3.2 订阅者示例(subscriber.cpp)

#include "iox/duration.hpp"
#include "iox/slice.hpp"
#include "iox2/node.hpp"
#include "iox2/service_name.hpp"
#include "iox2/service_type.hpp"
#include <cstdint>
#include <iostream>

constexpr iox::units::Duration CYCLE_TIME = iox::units::Duration::fromSeconds(1);

auto main() -> int {
    using namespace iox2;
    auto node = NodeBuilder().create<ServiceType::Ipc>().expect("successful node creation");
    auto service = node.service_builder(ServiceName::create("Service With Dynamic Data").expect("valid service name"))
                       .publish_subscribe<iox::Slice<uint8_t>>()
                       .open_or_create()
                       .expect("successful service creation/opening");
    auto subscriber = service.subscriber_builder().create().expect("successful subscriber creation");
    while (node.wait(CYCLE_TIME).has_value()) {
        auto sample = subscriber.receive().expect("receive succeeds");
        while (sample.has_value()) {
            std::cout << "received " << sample->payload().size() << " bytes: ";
            for (auto byte : sample->payload()) {
                std::cout << std::hex << byte << " ";
            }
            std::cout << std::endl;
            sample = subscriber.receive().expect("receive succeeds");
        }
    }
    std::cout << "exit" << std::endl;
    return 0;
}

4. 机制比较

5. 架构比较

6. Iceoryx vs Iceoryx2

特性
Iceoryx
Iceoryx2
编程语言
C++
Rust
是否支持真正的零拷贝数据传输
是否需要中央守护进程
消息传递模式
发布-订阅
发布-订阅
通知机制
轮询
事件
支持的平台
Linux, Windows, macOS, FreeBSD, QNX, FreeRTOS
Linux, Windows, macOS, FreeBSD, Android, QNX, FreeRTOS, VxWorks
语言绑定
C/C++
Rust/C/C++/Python/Go/C#/Lua

7. 为何Rust版本的Iceoryx2去除了RouDi?

7.1 C++版本Iceoryx为何引入RouDi

C++版本Iceoryx中的RouDi主要解决的是:

  • 注册:发布者和订阅者启动时向RouDi注册,声明自己发布或订阅的主题。
  • 连接:RouDi根据注册信息建立发布者和订阅者之间的连接,并配置共享内存区域。
  • 通信:发布者将数据写入共享内存,通过消息队列通知RouDi;订阅者接收RouDi的通知,从共享内存读取数据。

在早期版本的Iceoryx中,为了实现高效的内存管理和通信机制,使用了一些全局变量。这些全局变量主要用于以下几个方面:

  • 内存池管理:记录内存池的状态和配置信息。
  • 节点注册和发现:记录已注册的节点信息。
  • 通信上下文:存储通信上下文数据。
  • 系统状态和配置:记录系统的全局状态和配置信息。
  • 错误处理和日志记录:存储错误码和日志记录器。

7.2 Rust为何可以改进全局变量带来的缺陷?

Rust的全局变量和C++的全局变量在实现方式和模块间的依赖处理上有一些显著区别,特别是在模块耦合、依赖管理和重编译的影响方面。

+ 所有权系统与不可变性

Rust的所有权系统和不可变性要求使其在设计全局变量时更具有可控性。具体来说:

  • 不可变性:Rust中的全局变量一般默认不可变(const或不可变的静态全局变量),使用时无需锁定或同步,从而避免了一些并发问题。如果必须是可变的全局状态,Rust强制使用线程安全的封装,如Mutex或RwLock。
  • 所有权转移:Rust的所有权系统严格控制引用的生命周期和作用域。一个模块只能在符合所有权规则的条件下修改全局变量,避免了直接共享和修改内存的高耦合方式,降低了模块间的直接依赖关系。

+ 模块隔离与访问控制

Rust的模块系统和访问控制机制进一步隔离了全局状态的使用。通过模块内的pub关键字,可以严格限制哪些变量或函数可以被外部访问,这样不同模块之间不会因为全局变量产生直接的依赖关系。

例如:

mod config {
    pub struct SystemConfig {
        // 配置数据
    }
    pub fn get_system_config() -> &'static SystemConfig {
        // 返回系统配置的引用
    }
}
// 外部模块只能通过 get_system_config 函数访问 SystemConfig

这样设计后,即使SystemConfig改动了,也只需要重新编译config模块,而不是依赖它的所有模块。Rust的模块控制通过严格的访问权限设置,可以减少耦合,确保一个模块的全局变量更容易维护。

+ 惰性初始化与依赖隔离

Rust支持lazy_static宏来实现惰性初始化。这意味着全局变量只在首次使用时初始化,而不是在编译时或程序启动时完成初始化,从而避免了一些资源的过早占用。

  • 惰性初始化:Rust中使用lazy_static实现全局变量时,初始化代码可以独立封装在模块内部,不依赖其他模块,因此模块之间不会因为全局变量的初始化顺序产生耦合。

+ 编译和重编译的影响

Rust中,所有的全局状态通常都会被封装在一个模块内部,并通过API暴露给其他模块,这样即使内部实现发生改变,只要对外的API不变,其他模块也不会受到影响。

  • 封装隔离:Rust中,模块间的依赖是通过接口的,而不是直接访问全局变量。只要接口没有改变,模块的实现变化不会影响依赖它的模块,进而避免了过度重编译。

+ 线程安全与并发机制

Rust的全局变量要求使用Mutex或RwLock等线程安全的机制,这些锁通过所有权和借用检查,确保即使多模块同时访问全局变量,也不会引发未定义行为。而C++通常需要手动添加线程安全机制,并且由于直接访问内存,耦合性较强,线程安全性需要开发者自行维护。

Rust通过所有权系统、模块访问控制和惰性初始化等机制,有效管理全局变量的依赖关系,减少模块间的耦合性。在设计上,全局变量通常封装在模块内部,提供接口访问。这样设计更灵活,即使变量实现改动,也不会强制其他模块重新编译。相比之下,C++的全局变量在头文件中暴露且直接访问,模块间的耦合性更强,任何全局变量的改动都可能影响所有依赖该变量的模块,导致过度重编译和更高的耦合性。因此,Rust的设计原则可以减少模块间的编译依赖、提升并发的安全性,整体上避免了C++全局变量常见的问题。

8. 参考资料

  • Welcome to iceoryx2’s C / C++ documentation!
  • eclipse-iceoryx/iceoryx
  • eclipse-iceoryx/iceoryx2
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号