文章目录
- 主要优势
- 分层架构
- 四个核心抽象
-
- [Node 进程资源个体](#Node 进程资源个体)
- Service:通信协议
- Port:接入端点
- Sample:RAII负责内存归还
- 无锁数据传递
- 例子
- 实时性优化要点
Iceoryx2 基于Rust实现的开源中间件,专为实现低延迟、零拷贝进程间通信。
官方基准测试中,进程间传递一个 payload 的端到端延迟可以压到 100 纳秒量级,且延迟与 payload 大小无关------传 8 字节和传 8MB,耗时几乎一样。
主要优势
- 零拷贝通信,直接在进程间传递数据引用,减少数据复制,提升性能降低延迟
- 引入Rust语言,Iceoryx2提升内存安全和并发安全性,防止数据竞争和其他常见的并发错误
- 支持C/C++、Python、Rust
分层架构

四个核心抽象
iceoryx2围绕四个核心展开:Node、Service、Port、Sample。
Node 进程资源个体
每个参与通信的进程,会先创建一个Node,负责:
- 向系统注册自己,在共享内存的全局注册表里登记
- 监控同一域内其他Node的存活状态(基于心跳/进程探测)
- 进程退出时清理自己名下的资源,发现死节点时,代为清理遗留资源
Service:通信协议
Service由【名字、消息传递、payload类型】确定,支持四种消息模式
- Pub/Sub 发布订阅,1:N或N:M
- Event 事件发布,只传信号不传数据
- Req/Resp:请求应答
- 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();
实时性优化要点
-
builder调用都只出现在启动阶段
-
payload设计时,按访问频率让热字段到同一条cache line。零拷贝消灭的是搬运成本,但访问成本还在自己手里。
-
用对唤醒策略
< 1us要求时,不需要用事件唤醒,直接轮询,因为一个futex本身要几百NS
ms运算,WaitSet事件驱动
混合场景:先spin几微妙,没数据再挂起。spin-then-park。