Eclipse iceoryx 源码详细分析

Eclipse iceoryx 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/eclipse-iceoryx/iceoryx
版本:2.0.6iceoryx_posh/package.xml),语言 C++17(含 C binding),许可证 Apache 2.0

iceoryx 是面向**进程间通信(IPC)**的中间件,核心卖点是 真零拷贝(true zero-copy):Publisher 与 Subscriber 通过 POSIX 共享内存直接传递数据指针,payload 不经 socket 拷贝。在 ROS 2 Humble 中,它主要作为 CycloneDDS 共享内存传输层的后端(ENABLE_SHM + iceoryx_binding_c),也可通过 rmw_iceoryx 作为独立 RMW 使用。


1. 总体架构

与 CycloneDDS 的“协议栈 + API”不同,iceoryx 是纯 IPC 层,不负责 DDS/ROS 的发现与 QoS,只负责内存池 + 端口 + 数据分发

应用进程RouDi 守护进程POSIX Shared Memoryloan/publishtake/releasePublisher / ClientSubscriber / ServerPoshRuntimePortManagerMemoryManager / MePooServiceRegistryManagement Segment<br/>端口/元数据Payload Segment<br/>Chunk 数据
模块 路径 职责 ROS 2 包名
iceoryx_hoofs iceoryx_hoofs/ 基础库:容器、相对指针、POSIX 封装、无锁队列 iceoryx_hoofs
iceoryx_posh iceoryx_posh/ 核心中间件 + RouDi 守护进程 iceoryx_posh
iceoryx_binding_c iceoryx_binding_c/ C API(供 CycloneDDS 等 C 项目调用) iceoryx_binding_c
iceoryx_dds iceoryx_dds/ iceoryx ↔ DDS 网关(可选) 非 Humble 默认可选
tools/introspection tools/introspection/ 运行时 introspection 工具 iceoryx_introspection

源码规模:iceoryx_posh91 个 .cppiceoryx_hoofs36 个 .cpp,合计 1300+ 文件


2. 核心概念

2.1 RouDi(Routing and Discovery)

RouDi 是 iceoryx 的中心守护进程,必须在所有应用之前启动(ROS 2 中通常由 launch 或 iox-roudi 启动)。

职责(见 internal/roudi/roudi.hpp):

  • 创建并管理 共享内存段(management + payload)
  • 处理 Runtime 的 IPC 注册(进程注册、版本校验)
  • 通过 PortManager 分配 Publisher/Subscriber/Client/Server 端口
  • 维护 ServiceRegistry(服务发现)
  • 进程异常退出时 清理 chunk 与端口
  • 提供 Introspection(mempool/port/process 监控)

应用侧通过 PoshRuntime::initRuntime(name) 与 RouDi 建立 Unix Domain Socket 通道,请求创建端口。

2.2 ServiceDescription(三元组寻址)

iceoryx 用 Service / Instance / Event 三元组标识通信端点(类似 AUTOSAR ara::com):

1
2
/// @brief class for the identification of a communication event including information on the service, the service
/// instance and the event id.

CycloneDDS SHM 集成时,会将 DDS topic 名映射为这三元组。接口类型枚举还包括 DDSROS1 等,表明其设计面向多协议网关。

2.3 Chunk — 零拷贝的数据单元

Chunk 是共享内存中的传输胶囊,布局见 doc/design/chunk_header.md

1
2
3
+===================+===============+====================+============+
| ChunkHeader | User-Header | User-Payload | Padding |
+===================+===============+====================+============+

ChunkHeadermepoo/chunk_header.hpp)包含:

  • chunkSizechunkHeaderVersion
  • originId(Publisher 唯一 ID)、sequenceNumber
  • userHeaderSize / userPayloadSize / userPayloadAlignment
  • 通过 back-offset 从 user-payload 反查 ChunkHeader

设计约束:

  • 共享内存映射到各进程不同虚拟地址禁止裸指针,必须用相对/可重定位指针
  • 支持 record & replay 的版本号与 origin 追踪

3. iceoryx_hoofs — 基础库

hoofs = Healthy Overly Optimistic Foundation Stuff(项目自嘲式命名),提供无 STL 依赖(或最小依赖)的基础能力。

3.1 目录结构

子目录 内容
cxx/ expectedoptionalvectorstring 等轻量容器
concurrent/ 无锁队列 lockfree_queue、SOFI、TACO
posix_wrapper/ 共享内存、mutex、UDS、semaphore
internal/relocatable_pointer/ RelativePointer / relocatable_ptr
error_handling/ 统一错误处理
log/ 日志框架
platform/ linux/mac/qnx/win/unix 平台差异

3.2 相对指针(共享内存关键)

doc/design/relocatable_pointer.md 说明:

  • 各进程将同一段 SHM 映射到不同基址
  • relocatable_ptr:指针与 pointee 在同一段内,存偏移量
  • RelativePointer:跨段引用,通过全局 segment id 解析

RouDi 的 Port 数据结构、Chunk 队列、Subscriber 列表等都建立在相对指针之上,这是 iceoryx 能在多进程间安全传递“指针”的基础。

3.3 无锁队列

doc/design/lockfree_queue.md + concurrent/lockfree_queue.hpp:Subscriber 侧 chunk 队列采用无锁设计,降低 pub/sub 热路径上的锁竞争。


4. iceoryx_posh — 核心中间件

posh = Posix Shared Memory。源码按功能分目录:

1
2
3
4
5
6
7
8
iceoryx_posh/source/
├── capro/ # Capabilities & Protocol — ServiceDescription、发现消息
├── mepoo/ # Memory Pool — ChunkHeader、MemoryManager、SharedChunk
├── popo/ # Posix Objects — Publisher/Subscriber/Client/Server/WaitSet
├── roudi/ # RouDi 守护进程实现
├── runtime/ # PoshRuntime — 应用与 RouDi 的 IPC 接口
├── gateway/ # 网关相关
└── version/ # 版本与兼容性检查

4.1 MePoo(Memory Pool)

组件 说明
MePooConfig 配置多档 mempool:(chunkSize, chunkCount) 列表
MemoryManager 从 mempool 分配/释放 chunk
SharedChunk 带引用计数的 chunk 句柄
ChunkSettings user-payload/header 大小与对齐

默认 mempool 通过 MePooConfig::setDefaults() 设置;RouDi 配置文件(TOML)可覆盖。chunk 不足时 loan() 返回 AllocationError::RUNNING_OUT_OF_CHUNKS

4.2 popo — 面向用户的 API

popo = Posix Objects(仿 COM 命名)。

模式 核心操作
Publisher<T> Pub/Sub loan() → 写 payload → publish()
Subscriber<T> Pub/Sub take() / hasData()
Client / Server Request/Response loan() request → server 处理 → response
WaitSet 事件驱动 等待多个 subscriber/trigger
Listener 回调 异步通知
UntypedPublisher/Subscriber 底层 C binding 与中间件集成用

Publisher 典型流程(publisher_impl.hpp):

1
2
3
4
5
// 1. 从 mempool loan 一块共享内存
auto sample = publisher.loan();
// 2. 写入 sample->payload
// 3. 发布(ChunkDistributor 推送到所有 Subscriber 队列)
publisher.publish(std::move(sample));

publishCopyOf() 是带拷贝的便捷路径,不是零拷贝

4.3 Building Blocks — 内部构建块

Pub/Sub 内部分层(自底向上):

1
2
3
4
5
6
7
8
9
MemoryManager

ChunkSender ←→ ChunkReceiver
↓ ↓
ChunkDistributor ChunkQueuePopper
↓ ↓
PublisherPort SubscriberPort
↓ ↓
Publisher Subscriber
构建块 职责
ChunkSender 分配 chunk + 通过 ChunkDistributor 发送
ChunkReceiver 从 ChunkQueue 接收 chunk
ChunkDistributor 向多个 Subscriber 队列分发 SharedChunk,支持 history
ChunkQueuePusher/Popper 无锁队列两端

Publisher 与 Subscriber 匹配时,RouDi 的 PortManager 将 Subscriber 的 ChunkQueue 注册到 Publisher 的 ChunkDistributor(port_manager.hppacquirePublisherPortData / acquireSubscriberPortData)。

4.4 Port 架构(User / RouDi 分离)

每个 Port 有三层:

位置 作用
*PortData 共享内存 纯数据,无方法
*PortUser 应用进程 用户侧 API
*PortRouDi RouDi 进程 连接、清理、introspection

这种分离保证:RouDi 可直接操作 SHM 中的 port 元数据,而应用通过 User 层访问,进程崩溃时 RouDi 仍能 cleanup。

4.5 PoshRuntime

1
2
3
4
5
6
7
8
9
/// @brief The runtime that is needed for each application to communicate with the RouDi daemon
class PoshRuntime
{
public:
static PoshRuntime& initRuntime(const RuntimeName_t& name) noexcept;
virtual PublisherPortUserType::MemberType_t*
getMiddlewarePublisher(const capro::ServiceDescription& service, ...) noexcept = 0;
virtual SubscriberPortUserType::MemberType_t*
getMiddlewareSubscriber(const capro::ServiceDescription& service, ...) noexcept = 0;
  • 单例,initRuntime() 注册进程名(须唯一)
  • 通过 IPC 消息向 RouDi 请求创建 Publisher/Subscriber/Client/Server
  • 也支持 SingleProcess 模式(测试用,无需 RouDi)

4.6 Request/Response(ROS 2 Service 基础)

doc/design/request_response_communication.md

  • Client:ChunkSender 发 request + ChunkReceiver 收 response
  • Server:ChunkReceiver 收 request + ChunkSender 发 response
  • Request/Response Header 含 sequence ID,支持异步 RPC
  • 同一 ServiceDescription 只允许一个 Server

这与 ROS 2 rclcpp::Client / rclcpp::Service 的语义对齐。


5. iceoryx_binding_c — C 绑定

路径:iceoryx_binding_c/include/iceoryx_binding_c/

头文件 API
runtime.h iox_runtime_init()
publisher.h iox_pub_init(), iox_pub_loan_chunk(), iox_pub_publish_chunk()
subscriber.h iox_sub_take_chunk(), iox_sub_release_chunk()
chunk.h ChunkHeader 访问
service_description.h 三元组构造
wait_set.h / listener.h 事件等待

C binding 是 CycloneDDS SHM 集成的直接依赖——CycloneDDS 为 C 项目,不能直接用 C++ Publisher<T>


6. 与 CycloneDDS / ROS 2 的集成

6.1 CycloneDDS SHM 路径

CycloneDDS 编译选项 ENABLE_SHM=AUTO 会查找 iceoryx_binding_c,启用 DDS_HAS_SHM

关键桥接代码:cyclonedds/src/core/ddsi/src/ddsi_shm_transport.c

1
2
3
4
5
6
7
8
9
10
11
void *shm_create_chunk(iox_pub_t iox_pub, size_t size) {
// ...
iox_pub_loan_aligned_chunk_with_user_header(
iox_pub, &iox_chunk, (uint32_t)size,
IOX_C_CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT,
sizeof(iceoryx_header_t), 8);
// ...
ice_hdr->data_size = (uint32_t)size;
ice_hdr->shm_data_state = IOX_CHUNK_UNINITIALIZED;
return iox_chunk;
}

数据流(同机 ROS 2 + CycloneDDS + SHM):

1
2
3
4
5
6
7
8
9
10
rclcpp::Publisher::publish(msg)
→ rmw_cyclonedds_cpp::dds_write
→ dds_write / dds_writecdr
→ [SHM 启用且订阅者在同机]
→ iox_pub_loan_chunk (共享内存)
→ 序列化 payload 到 chunk(仍有一次写入 SHM)
→ iox_pub_publish_chunk
→ Subscriber 侧 iox_sub_take_chunk
→ dds_read 直接读 SHM 指针(零拷贝取数据)
→ [否则] UDP/TCP RTPS 网络路径

注意:

  • 跨进程时 payload 仍要写入共享内存一次(不是完全无 touch),但 subscriber 侧不再拷贝到用户 buffer(loan 模式下)
  • 网络 RTPS 只传递 chunk 指针/通知(iceoryx 内部机制),不传 payload 本体
  • 需先启动 RouDi,且 Publisher/Subscriber 在同一 iceoryx domain

6.2 依赖关系(package.xml)

1
2
3
4
<!-- cyclonedds/package.xml -->
<depend>iceoryx_binding_c</depend>
<depend>iceoryx_posh</depend>
<depend>iceoryx_hoofs</depend>

6.3 iceoryx_dds 网关(可选)

iceoryx_dds/ 提供 iceoryx ↔ Cyclone DDS 双向网关:

  • gateway/iox_to_dds.hpp — iceoryx → DDS
  • gateway/dds_to_iox.hpp — DDS → iceoryx
  • 用于跨网络或异构系统桥接,Humble 默认 ROS 工作流不必须。

7. 配置与部署

7.1 RouDi 配置

  • TOML 配置文件(roudi_config.toml)指定 mempool 大小、segment 数量
  • 环境变量:IOX_ROUDI_CONFIG_FILE
  • 默认 mempool 档位覆盖常见消息大小(128B ~ 4MB 等)

7.2 运行顺序

1
2
3
4
5
6
7
# 1. 启动 RouDi(必须最先)
iox-roudi

# 2. 启动 ROS 2 节点(CycloneDDS SHM 模式)
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
# cyclonedds.xml 中启用 SharedMemory
ros2 run ...

7.3 平台支持

平台 SHM 访问控制 说明
Linux 支持 ROS 2 主平台
QNX 支持 汽车场景起源
macOS 可运行但无权限隔离
Windows 开发中

8. 事件驱动 API

机制 用途
WaitSet 阻塞等待多个 subscriber/guard condition(类似 rcl_wait
Listener 注册回调,事件到达时触发(类似 rclcpp executor 回调)
UserTrigger 手动触发事件
Notification 跨进程通知 subscriber 有新数据

CycloneDDS SHM 路径中,subscriber 收到 iceoryx notification 后才会去 take chunk。


9. 错误处理与健壮性

  • ConsumerTooSlowPolicy:Subscriber 队列满时,Publisher 可 BLOCK 或 DISCARD_OLDEST_DATA
  • 进程崩溃:RouDi 检测并 cleanup 泄漏的 chunk(UsedChunkList
  • 版本兼容:Runtime 注册时校验 VersionInfo(MAJOR/MINOR/PATCH)
  • expected<T, E>:Hoofs 提供的 Rust 风格错误返回,贯穿 loan/publish API

10. 与 CycloneDDS 的职责对比

维度 CycloneDDS iceoryx
定位 完整 DDS 中间件(发现/QoS/网络) 纯 SHM IPC 传输
发现 SPDP/SEDP RouDi + ServiceRegistry(本地)
传输 UDP/TCP/SHM 仅 SHM
数据单元 serdata (CDR) Chunk
API 语言 C (dds.h) C++ 为主 + C binding
守护进程 无(P2P) RouDi 必须
ROS 2 角色 默认 RMW 后端 CycloneDDS 的 SHM 加速层

11. 推荐阅读顺序

目标:理解 ROS 2 大消息零拷贝

  1. doc/design/chunk_header.md — Chunk 内存布局
  2. doc/design/relocatable_pointer.md — 为何不能用裸指针
  3. iceoryx_posh/include/iceoryx_posh/popo/publisher.hpppublisher_impl.hpp
  4. internal/popo/building_blocks/chunk_sender.hppchunk_distributor.hpp
  5. internal/roudi/port_manager.hpp — 端口如何匹配
  6. iceoryx_binding_c/include/iceoryx_binding_c/publisher.h — C API
  7. cyclonedds/src/core/ddsi/src/ddsi_shm_transport.c — 与 DDS 的接缝
  8. 示例:iceoryx_examples/icedelivery/iceoryx_examples/singleprocess/

目标:调试 SHM 问题

  1. 确认 RouDi 运行:iox-introspection-clienticeoryx_introspection
  2. 检查 mempool 耗尽、chunk 泄漏
  3. 查看 CycloneDDS SharedMemory 配置段

12. 设计特点小结

特点 说明
真零拷贝 Subscriber 直接读 SHM 中的 chunk,无 payload 拷贝
恒定延迟 传输时间与 payload 大小无关(仅指针/通知)
RouDi 中心化 内存与端口生命周期由守护进程统一管理
三层 Port 模型 Data(User/RouDi) 分离,支持 crash cleanup
Building Blocks 组合 ChunkSender/Distributor/Queue 可复用于 Pub/Sub 与 RPC
相对指针 解决 SHM 多映射地址问题
C binding 使 C 系中间件(CycloneDDS)可集成
汽车级起源 AUTOSAR 风格 ServiceDescription、QNX 支持

如果你希望,我可以:

  1. 把本文写入 ros2doc/cyclonedds/ 同级目录 ros2doc/iceoryx/Eclipse iceoryx 源码详细分析.md
  2. 继续深入 CycloneDDS ↔ iceoryx 的 topic 映射规则RouDi 启动与 mempool 配置 的源码级追踪

文章互动

阅读 --

留言

0 条留言

正在加载留言…