中间件学习-Iceoryx2零基础入门

文章目录

Iceoryx2 基于Rust实现的开源中间件,专为实现低延迟、零拷贝进程间通信。

官方基准测试中,进程间传递一个 payload 的端到端延迟可以压到 100 纳秒量级,且延迟与 payload 大小无关------传 8 字节和传 8MB,耗时几乎一样。

主要优势

  1. 零拷贝通信,直接在进程间传递数据引用,减少数据复制,提升性能降低延迟
  2. 引入Rust语言,Iceoryx2提升内存安全和并发安全性,防止数据竞争和其他常见的并发错误
  3. 支持C/C++、Python、Rust

分层架构

四个核心抽象

iceoryx2围绕四个核心展开:Node、Service、Port、Sample。

Node 进程资源个体

每个参与通信的进程,会先创建一个Node,负责:

  1. 向系统注册自己,在共享内存的全局注册表里登记
  2. 监控同一域内其他Node的存活状态(基于心跳/进程探测)
  3. 进程退出时清理自己名下的资源,发现死节点时,代为清理遗留资源

Service:通信协议

Service由【名字、消息传递、payload类型】确定,支持四种消息模式

  1. Pub/Sub 发布订阅,1:N或N:M
  2. Event 事件发布,只传信号不传数据
  3. Req/Resp:请求应答
  4. Blackboard:黑板模式 键值共享状态

同名Service 必须声明完全一致的协议,运行时会进行校验,防止两个进程对同一块内存解读不一致

Port:接入端点

Port是进程在Service的接入端点,所有Port都显示声明容量,一切资源在建立连接时定死,运行时零动态分配。因为malloc的延迟不可预测。

Sample:RAII负责内存归还

对于零拷贝的流程来说,是发布端先借内存->写内存->交还->订阅端拿到只读内存。

无锁数据传递

Iceoryx2在Pub和Sub之间维护一个基于原子操作的无锁索引队列,传递的不是数据本身,而是数据块在共享内存段内偏移量。

传偏移不传指针、引用计数也在共享内存里、队列满的策略可配置

所以无论是传输8字节还是8MB的Payload,在跨进程时永远只有一个u64偏移加几次原子操作,操作延迟在100ns。

例子

零拷贝发布订阅

共享库,定义数据结构:

rust 复制代码
use iceoryx2::prelude::*;

// payload 必须是共享内存安全,无堆指针
// payload的trait   
#[derive(Debug, ZeroCopySend)]
#[repr(C)]
pub struct RadarPoint {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub intensity: u16,
}

订阅端

rust 复制代码
use core::time::Duration;
use iceoryx2::prelude::*;
use shared_types::*;

fn main() -> Result<(), Box<dyn core::error::Error>> {
    let node = NodeBuilder::new().create::<ipc::Service>()?;

    let service = node
        .service_builder(&"sensors/radar/points".try_into()?)
        .publish_subscribe::<RadarPoint>()
        .history_size(4)
        .open_or_create()?;   // 谁先启动谁创建,无启动顺序依赖

    let subscriber = service.subscriber_builder().create()?;

    while node.wait(Duration::from_millis(10)).is_ok() {
        while let Some(sample) = subscriber.receive()? {
            // sample 是发布者写入的那块物理内存的只读视图,全程零拷贝
            println!("intensity = {}", sample.intensity);
        } // drop 时自动减引用计数,必要时归还内存池
    }
    Ok(())
}

发布端

rust 复制代码
use core::time::Duration;
use iceoryx2::prelude::*;
use shared_types::*;

fn main() -> Result<(), Box<dyn core::error::Error>> {
    // 失败直接返回错误类型
    let node = NodeBuilder::new().create::<ipc::Service>()?;

    let service = node.service_builder(&"sensors/radar/points".try_into()?)
                        .publish_subscribe::<RadarPoint>()
                        .max_publishers(1)
                        .max_subscribers(8)
                        .history_size(4)
                        .open_or_create()?;// open_or_create,打开或创建都行,即发布或订阅谁先启动都行
    
    let publisher = service.publisher_builder().create()?;

    while node.wait(Duration::from_millis(100)).is_ok() {
        // 借内存、写内存、归还内存
        let sample = publisher.loan_uninit()?;
        let sample = sample.write_payload(RadarPoint {
            x: 1.0, y: 2.0, z: 0.5, intensity: 4096,
        });
        sample.send()?; // sample被move之后无法再访问,防止use-after-send
    }

    // 成功不返回任何值
    Ok(())
}

toml添加依赖:

rust 复制代码
[package]
name = "iceoryx2_test"
version = "0.1.0"
edition = "2024"

[dependencies]
iceoryx2 = "0.9.3"
shared_types = { path = "./shared_types" } 


[[bin]]
name = "pub"
path = "src/pub.rs"

[[bin]]
name = "sub"
path = "src/sub.rs"

运行结果:

cargo run --bin pub

cargo run --bin sub

事件驱动

iceoryx2 提供 Event 服务 + WaitSet(类似 epoll 的多路事件分发器):

rust 复制代码
let event_service = node
    .service_builder(&"sensors/radar/event".try_into()?)
    .event()
    .open_or_create()?;

let listener = event_service.listener_builder().create()?;

let waitset = WaitSetBuilder::new().create::<ipc::Service>()?;
let guard = waitset.attach_notification(&listener)?;

waitset.wait_and_process(|attachment_id| {
    if attachment_id.has_event_from(&guard) {
        listener.try_wait_all(|_| { /* 有新数据,去 receive() */ }).ok();
    }
    CallbackProgression::Continue
})?;

BlackBoard

主要用来维护一些当前状态,使用者通过KV的形式拿到最新值。

rust 复制代码
// 写者
let service = node
    .service_builder(&"vehicle/state".try_into()?)
    .blackboard_creator::<u32>()
    .add::<f64>(0, 0.0)      // key=0: 车速
    .add::<i8>(1, 0)         // key=1: 档位
    .create()?;

let writer = service.writer_builder().create()?;
let speed_entry = writer.entry::<f64>(&0)?;
speed_entry.update_with_copy(72.5);

// 读者(另一个进程)
let reader = service.reader_builder().create()?;
let speed = reader.entry::<f64>(&0)?.get();

实时性优化要点

  1. builder调用都只出现在启动阶段

  2. payload设计时,按访问频率让热字段到同一条cache line。零拷贝消灭的是搬运成本,但访问成本还在自己手里。

  3. 用对唤醒策略

< 1us要求时,不需要用事件唤醒,直接轮询,因为一个futex本身要几百NS

ms运算,WaitSet事件驱动

混合场景:先spin几微妙,没数据再挂起。spin-then-park。

相关推荐
圆奋奋6 小时前
FreeRTOS学习(三)- 任务调度模块
学习·开源·freertos
吃着火锅x唱着歌6 小时前
Effective C++ 学习笔记 条款40 明智而审慎地使用多重继承
c++·笔记·学习
xian_wwq7 小时前
【学习笔记】Prompt Engineering 没死,只是它不再够用了-2/16
笔记·学习·prompt
天吾cc7 小时前
RTT-ADC
单片机·嵌入式硬件·学习
kaixin_啊啊7 小时前
test_机器学习算法学习
学习·算法·机器学习
m4Rk_8 小时前
【论文阅读】Agent 记忆机制(34):MemoryBank——用遗忘曲线管理可强化的长期对话记忆
论文阅读·人工智能·学习·开源·github
210Brian8 小时前
STM32学习笔记(五)EXTI外部中断(上)
笔记·stm32·学习
辣知8 小时前
辣知·化智19 人类文明的轮回
学习
xian_wwq10 小时前
【学习笔记】RAG 只是 Context Engineering 的一小块-5/16
笔记·学习·rag
123_不打狼10 小时前
零基础到就业:完整计算机视觉系统化学习路线指南
人工智能·学习·计算机视觉