首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

rmw_cyclonedds 源码详细分析

rmw_cyclonedds 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rmw_cyclonedds
版本:1.3.4(Humble),子包 1 个,构建类型 ament_cmake,语言 C++,许可证 Apache 2.0,质量等级 QL2

rmw_cyclonedds_cpp 是 ROS 2 面向 Eclipse CycloneDDS 的 RMW 实现,将标准 rmw_* C API 映射到 Cyclone 的 DDS C APIdds/dds.h)及底层 DDSI serdata/sertype 插件接口。Humble 上常与 rmw_fastrtps_cpp 并列可选;启用共享内存时需 Cyclone 编译 SHM 支持并配置 CYCLONEDDS_URI

外部依赖:DDS 本体在独立仓库 eclipse-cyclonedds/cyclonedds。未找到 CycloneDDS CMake 包时,本包 跳过编译


1. 总体认识

1.1 核心职责

能力 说明
RMW 全量实现 几乎所有 rmw_* 符号在单文件 rmw_node.cpp(~5511 行)中 extern "C" 导出
自研 CDR 序列化 基于 rosidl introspection 遍历消息结构,非 Fast-CDR typesupport
DDSI 集成 自定义 sertype_rmw / serdata_rmw 接入 Cyclone 传输层
Graph 发现 嵌入 rmw_dds_common::Context,双通道(ParticipantEntitiesInfo + DCPS builtin)
Service/Client Basic RPC 映射:inline cdds_request_header(GUID + seq)
共享内存 可选 iceoryx SHM(DDS_HAS_SHM),支持 fixed-size 类型的 loan API
Domain 管理 按 domain id 延迟创建 Cyclone domain,支持 localhost-only

1.2 在 ROS 2 栈中的位置

上层rmw_cyclonedds 仓库ROS 共享DDS 栈rcl / rclcpp / rclpyrmw_cyclonedds_cpp\n~10K 行 C++rmwrmw_dds_commonrosidl_typesupport_introspectionCycloneDDS libddsciceoryx_binding_c\n可选 SHM
对比项 rmw_cyclonedds_cpp rmw_fastrtps_cpp rmw_connextdds
DDS CycloneDDS Fast-DDS RTI Connext
序列化 自研 introspection CDR Fast-CDR typesupport Fast-CDR typesupport
主源码组织 单巨型 .cpp + 序列化模块 多文件 OOP common + 薄 API 层
Graph rmw_dds_common 部分 common rmw_dds_common
Service RPC 自定义 basic header extended/basic 可配置 basic/extended
SHM 零拷贝 iceoryx(需配置) 有限 Pro 异步 publish

1.3 与 CycloneDDS 的边界

1
2
3
4
5
6
rcl → rmw_cyclonedds_cpp
├── rmw_node.cpp # RMW 语义、实体生命周期、wait、graph
├── serdata.cpp / serdes # DDSI 样本封装与 CDR 缓冲
├── TypeSupport2.cpp # introspection → CDR 读写器
└── dds_* API # Participant/Writer/Reader/WaitSet
└── Cyclone DDSI # RTPS、发现、可选 SHM 传输

2. 包结构与构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rmw_cyclonedds/
├── README.md
├── shared_memory_support.md # iceoryx / RouDi 使用说明
└── rmw_cyclonedds_cpp/
├── package.xml
├── CMakeLists.txt
├── cmake/get_rmw_cyclonedds_output_filter.cmake
└── src/
├── rmw_node.cpp # ★ 主实现 (~5511 行)
├── serdata.cpp / serdata.hpp
├── serdes.cpp / serdes.hpp
├── TypeSupport.cpp / TypeSupport2.cpp / *.hpp
├── Serialization.cpp # CDR cursor 引擎
├── demangle.cpp
├── u16string.cpp # wstring 序列化辅助
├── exception*.cpp
├── rmw_get_network_flow_endpoints.cpp
└── rmw_cyclonedds_topic.idl # 占位 IDL(dummy topic)
源文件 行数(约) 职责
rmw_node.cpp 5511 全部 RMW API、实体 struct、discovery 线程
serdata.cpp 740 serdata_rmw、SHM chunk 管理
TypeSupport_impl.hpp 537 sertype 操作函数
Serialization.cpp 621 CDR 对齐、序列化/反序列化 cursor
TypeSupport2.cpp 239 introspection 类型树
其余 <200 each 辅助

2.1 依赖(package.xml

1
2
3
4
5
6
7
8
rmw_cyclonedds_cpp
├── cyclonedds # libddsc(必需)
├── iceoryx_binding_c # 仅当 Cyclone 启用 SHM 时链接
├── rmw / rmw_dds_common
├── rcutils / rcpputils
├── rosidl_runtime_c
├── rosidl_typesupport_introspection_c/cpp
└── tracetools # 追踪点

Typesupport 注册:

1
2
3
register_rmw_implementation(
"c:rosidl_typesupport_c:rosidl_typesupport_introspection_c"
"cpp:rosidl_typesupport_cpp:rosidl_typesupport_introspection_cpp")

与 FastRTPS/Connext 不同,注册 rosidl_typesupport_fastrtps_*

2.2 条件编译:共享内存

1
2
3
4
5
get_target_property(_cyclonedds_has_shm CycloneDDS::ddsc SHM_SUPPORT_IS_AVAILABLE)
if(_cyclonedds_has_shm)
find_package(iceoryx_binding_c REQUIRED)
target_link_libraries(... iceoryx_binding_c)
endif()

启用后定义 DDS_HAS_SHM,开放 loan API 与 iceoryx chunk 路径。


3. 全局状态与 Domain 管理

3.1 全局单例 Cddsgcdds()

1
2
3
4
5
6
7
8
9
struct Cdds {
std::mutex lock;
std::mutex domains_lock;
std::map<dds_domainid_t, CddsDomain> domains;

/* 空 waitset 时 attach 永不触发的 guard,避免 Cyclone 无实体时立即返回 */
dds_entity_t gc_for_empty_waitset;
std::unordered_set<CddsWaitset *> waitsets;
};
  • CddsDomain:每个 ROS domain id 一份,含 localhost_onlyrefcountdomain_handle
  • localhost-only:创建 domain 时在 CYCLONEDDS_URI 前注入 "localhost" 网络接口配置。
  • 同一 domain 内所有 node 的 localhost_only 必须一致,否则创建失败。

3.2 rmw_context_impl_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct rmw_context_impl_s {
rmw_dds_common::Context common;
dds_domainid_t domain_id;
dds_entity_t ppant; // DomainParticipant
rmw_gid_t ppant_gid;

dds_entity_t rd_participant; // DCPS builtin readers
dds_entity_t rd_subscription;
dds_entity_t rd_publication;

dds_entity_t dds_pub; // 共用 DDS Publisher
dds_entity_t dds_sub; // 共用 DDS Subscriber

size_t node_count;
std::mutex initialization_mutex;
bool is_shutdown;
uint32_t client_service_id;
};

3.3 生命周期时序

与 Connext RMW 类似,Participant 在首个 node 创建时初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rmw_init()
└── 仅分配 rmw_context_impl_t,拷贝 init_options(不创建 DDS 实体)

rmw_create_node() [首个 node]
└── rmw_context_impl_t::init()
├── check_create_domain() # 必要时 dds_create_domain
├── dds_create_participant()
├── DCPSParticipant/Publication/Subscription readers
├── dds_create_publisher/subscriber
├── create_publisher/subscription("ros_discovery_info")
├── discovery_thread_start()
└── graph_cache.add_node + publish

rmw_destroy_node() [最后一个 node]
└── rmw_context_impl_t::fini() → tear down participant / domain

rmw_shutdown 仅设 is_shutdown=truermw_context_fini 要求已 shutdown,释放 impl。


4. 实体内部结构

struct 关键字段 说明
CddsPublisher enth, sertype, pubiid, gid, is_loaning_available DataWriter + sertype 引用
CddsSubscription enth, rdcondh, gid, data_allocator DataReader + ReadCondition
CddsClient / CddsService CddsCS { pub, sub, id } 请求/响应 writer+reader 对
CddsGuardCondition gcondh DDS GuardCondition
CddsWaitset waitseth, attach 缓存向量 支持 reattach 优化
CddsEvent enth, event_type QoS 事件 status condition

rmw_*_t->data 指向上述结构;implementation_identifier = "rmw_cyclonedds_cpp"


5. Topic 命名

前缀(namespace_prefix.hpp):

前缀 用途
rt 普通 topic:/foort/foo
rq + Request Service 请求侧
rr + Reply Service 响应侧

make_fqtopic(prefix, name, suffix, qos) 组装 DDS topic 名;avoid_ros_namespace_conventions 时跳过 rt 前缀。

内部 graph topic:ros_discovery_infoParticipantEntitiesInfo,TRANSIENT_LOCAL)。

demangle.cpp 提供 topic/type 名还原,供 graph API 返回 ROS 语义字符串。


6. 类型系统与序列化

6.1 设计思路

Cyclone RMW 不使用 rosidl_typesupport_fastrtps_*,而是:

  1. 通过 introspection(C 或 C++)获取 MessageMembers / MessageMember 元数据;
  2. TypeSupport2.hpp 构建 StructValueType 类型树;
  3. Serialization.cppCDRCursor 按 OMG CDR 规则读写;
  4. serdata_rmw 继承 ddsi_serdata,持有 CDR 字节流或 iceoryx chunk 指针。

6.2 sertype_rmw / serdata_rmw

1
2
3
4
5
6
7
8
9
10
11
12
13
struct sertype_rmw : ddsi_sertype {
CddsTypeSupport type_support;
bool is_request_header; // service 请求类型带 header
std::unique_ptr<const BaseCDRWriter> cdr_writer;
bool is_fixed;
std::mutex serialize_lock;
};

class serdata_rmw : public ddsi_serdata {
size_t m_size;
std::unique_ptr<byte[]> m_data; // 前 4 字节:CDR encapsulation
// SHM: iox_chunk, iox_subscriber 等
};

Service 请求/响应包装:

1
2
typedef struct cdds_request_header { uint64_t guid; int64_t seq; } cdds_request_header_t;
typedef struct cdds_request_wrapper { cdds_request_header_t header; void * data; } cdds_request_wrapper_t;

这是 非标准 basic RPC 变体(header 布局与 Connext extended / Fast-DDS 不同),导致 默认情况下 service 难以跨 rmw 厂商互通(Connext 需 RMW_CONNEXT_CYCLONE_COMPATIBILITY_MODE)。

6.3 Typesupport 选择

get_typesupport() 优先 introspection C,其次 C++:

1
2
get_message_typesupport_handle(type_supports, rosidl_typesupport_introspection_c__identifier)
// fallback: rosidl_typesupport_introspection_cpp::typesupport_identifier

6.4 rmw_serialize / rmw_deserialize

rmw_node.cpp 中调用 serdes 与 TypeSupport 模块,格式字符串 "cdr"rmw_get_serialization_format())。


7. 发布与订阅数据路径

7.1 常规发布

1
2
3
4
rmw_publish()
→ CddsPublisher::write path
→ 序列化 ros_message → serdata_rmw
→ dds_writecdr(writer, serdata) 或 dds_write

7.2 常规 take

1
2
3
4
rmw_take() / rmw_take_with_info()
→ dds_take / dds_takecdr
→ 反序列化 serdata → ros_message
→ message_info_from_sample_info()

7.3 共享内存与 Loan(DDS_HAS_SHM

当 Cyclone 启用 iceoryx 且消息为 fixed-size 时:

API 行为
rmw_borrow_loaned_message dds_data_allocator 从 writer loan chunk
rmw_publish_loaned_message dds_writecdr + IOX_CHUNK_CONTAINS_RAW_DATA
rmw_take_loaned_message 直接返回 iox chunk 指针(RAW)或反序列化(SERIALIZED)
rmw_return_loaned_message_from_* fini_and_free_sample 释放 chunk

未编译 SHM 时,上述 API 返回 RMW_RET_UNSUPPORTED

配置见仓库 shared_memory_support.md:需 CYCLONEDDS_URI 启用 SharedMemory、运行 iox-roudi 等。

7.4 自包含类型检测

is_type_self_contained() 决定是否设置 can_loan_messages / is_loaning_available(无 string/动态数组等)。


8. Service / Client

8.1 创建逻辑(create_client_service

  • Service:sub 收 rq/...Request,pub 发 rr/...Reply
  • Client:pub 发 rq/...Request,sub 收 rr/...Reply
  • 各端创建带 is_request_header=truesertype_rmw
  • Writer/Reader QoS user_data 写入 serviceid= / clientid= 十六进制串,用于配对

8.2 请求-响应路径

1
2
3
4
5
6
7
8
9
10
rmw_send_request()
→ 填充 cdds_request_header { guid, seq }
→ dds_write(client.pub, &wrap)

rmw_take_request() / rmw_take_response()
→ dds_take → 解析 wrap.header
→ 重建 rmw_request_id_t(writer_guid + publication_handle + seq)

rmw_send_response()
→ 同 send_request,使用 service 侧 publisher

rmw_service_server_is_available 通过 matched endpoints + user_data 检查 client reader 是否就绪。


9. Graph 与 Discovery

9.1 初始化(rmw_context_impl_s::init 内)

与 Connext/FastRTPS 新实现一致:

  1. 创建 ros_discovery_info pub/sub(ParticipantEntitiesInfo
  2. 创建 graph guard_condition
  3. 启动 discovery 线程

9.2 Discovery 线程(discovery_thread

WaitSet 监听:

  • rd_participanthandle_DCPSParticipant
  • rd_publicationhandle_DCPSPublication
  • rd_subscriptionhandle_DCPSSubscription
  • common.subhandle_ParticipantEntitiesInfo
  • 退出 guard condition

更新 rmw_dds_common::GraphCache,必要时 rmw_publish 广播本地变更。

9.3 Node 创建时的 graph 更新

1
2
3
std::lock_guard<std::mutex> guard(common->node_update_mutex);
auto participant_msg = common->graph_cache.add_node(common->gid, name, namespace_);
rmw_publish(common->pub, &participant_msg, ...);

锁保证 update + publish 原子性,避免交错消息覆盖。

9.4 Graph API

rmw_get_node_namesrmw_get_topic_names_and_typesrmw_get_publishers_info_by_topic 等均在 rmw_node.cpp 后半部分,读取 graph_cache 并 demangle。


10. Wait Set 与 rmw_wait

10.1 空 WaitSet 问题

Cyclone 在 waitset 无任何 condition 时会立即返回。RMW 创建全局 dummy guard condition(永不 trigger)并 attach 到每个 waitset,使空 wait 能正确阻塞。

10.2 rmw_wait 流程

  1. 检查 ws->inuse(禁止同一 waitset 并发 wait)
  2. 若 subscription/guard/service/client/event 数组变化 → waitset_detach 后重新 attach
  3. Subscription/Service/Client 使用 ReadConditionrdcondh
  4. dds_waitset_wait + timeout
  5. 未就绪条目在 rcl 传入的数组中置 NULL

Event 通过 gather_event_entities 收集 status condition 并 attach。


11. QoS

11.1 ROS → DDS

create_readwrite_qos() / rmw_duration_to_dds() 映射:

  • history / depth
  • reliability(RELIABLE / BEST_EFFORT)
  • durability(VOLATILE / TRANSIENT_LOCAL)
  • deadline / lifespan / liveliness

11.2 DDS → ROS

dds_qos_to_rmw_qos()get_readwrite_qos() 用于 rmw_publisher_get_actual_qos 等。

11.3 兼容性检查

1
2
3
rmw_qos_profile_check_compatible(...) {
return rmw_dds_common::qos_profile_check_compatible(...);
}

11.4 Cyclone 配置

运行时行为大量受 CYCLONEDDS_URI 影响(发现 peer、追踪日志、SHM、网卡选择等),见 README 与 Cyclone 手册。


12. 事件与回调

12.1 支持的事件

mask_map 映射 rmw 事件到 DDS status:

  • RMW_EVENT_*_QOS_INCOMPATIBLE
  • RMW_EVENT_*_DEADLINE_MISSED
  • RMW_EVENT_LIVELINESS_*
  • RMW_EVENT_MESSAGE_LOST

12.2 两种通知机制

  1. Wait 路径rmw_take_event 读取 status
  2. Callback 路径rmw_*_set_on_new_message_callbackrmw_event_set_callback 使用 DDS listener + user_callback_data_t 计数

13. 安全(SROS2)

1
2
3
#if DDS_HAS_SECURITY && DDS_HAS_PROPERTY_LIST_QOS
#define RMW_SUPPORT_SECURITY 1
#endif

configure_qos_for_security() 在创建 participant 时设置 security 相关 QoS property;enforce 模式下失败则 init 中止。依赖 Cyclone 编译时启用 security。

Participant user_data 写入 enclave=...; 供 graph 使用。


14. 日志与追踪

  • rmw_set_log_severitydds_set_log_mask(DDS_LC_*) 映射 rcutils 级别
  • tracetools:关键路径插入 LTTng 追踪点(与 ROS 2 全局 tracing 集成)
  • get_rmw_cyclonedds_output_filter.cmake:注册控制台输出过滤正则(网卡选择警告)

15. 未实现或受限功能

API / 能力 状态
rmw_get_serialized_message_size RMW_RET_UNSUPPORTED
rmw_publisher/subscription_get_network_flow_endpoints 未实现
rmw_subscription_set/get_content_filter 未实现
rmw_*_allocation(pub/sub 预分配) 未实现 / 报错
Loan API DDS_HAS_SHM + fixed-size 类型
Strict unique network flow endpoints 创建 subscription 时拒绝

16. 与 rmw_dds_common 的协作

组件 用法
rmw_dds_common::Context rmw_context_impl_t::common
GraphCache 节点/endpoint/topic 图
ParticipantEntitiesInfo discovery topic 消息
qos_profile_check_compatible QoS 兼容 API
security.hpp participant QoS 安全配置

Cyclone 实现是 rmw_dds_common 的主要消费者之一,graph 协议与 FastRTPS/Cyclone/Connext 新版对齐。


17. 互操作说明

场景 默认互操作
Pub/Sub(CDR + rt/ topic) 与 Fast-DDS、Connext 等 通常可互通
Service/Client 仅同 RMW 或专门兼容模式(header 布局自定义)
Connext ↔ Cyclone service Connext 侧 RMW_CONNEXT_CYCLONE_COMPATIBILITY_MODE=y
SHM 零拷贝 仅本机 + 同 RMW + fixed 类型 + QoS 满足 iceoryx 限制

18. 调试建议

  1. 确认 RMWros2 doctor --report 或检查 RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
  2. Discovery 问题:配置 CYCLONEDDS_URI 静态 peer;用 Cyclone ddsperf sanity 排除网络层。
  3. 大消息延迟:增大 Linux net.core.rmem_max(README 推荐)。
  4. Graph 空:检查 ros_discovery_info 与 DCPS reader 是否创建成功;看 discovery 线程是否运行。
  5. SHM:确认 RouDi 运行、QoS 符合 shared_memory_support.md 限制。
  6. Cyclone 跟踪CYCLONEDDS_URI='<Tracing><Verbosity>trace</>...'

19. 推荐阅读顺序

  1. rmw_node.cpp 前 450 行 — 全局 struct、identifier、日志
  2. rmw_context_impl_s::init(~1160 行) — Participant + discovery 初始化
  3. create_publisher / create_cdds_publisher — topic + sertype 创建
  4. serdata.hpp + serdata.cpp — DDSI 样本与 SHM
  5. TypeSupport2.hpp + Serialization.cpp — CDR 序列化核心
  6. discovery_thread + graph 回调 — 图更新
  7. rmw_wait(~4037 行) — waitset 语义
  8. Service 段(~4550 行)cdds_request_wrapper
  9. Cyclone 上游dds_create_*ddsi_serdata API
  10. shared_memory_support.md — 零拷贝配置

20. 小结

rmw_cyclonedds单包、单主文件 型 RMW 实现,特点鲜明:

  • 对上:完整覆盖 Humble RMW 契约(loan/network flow 等部分可选);
  • 对下:深度集成 Cyclone DDSI serdata 插件,序列化走 自研 introspection CDR
  • 横向:通过 rmw_dds_common 统一 ROS graph;
  • 性能:可选 iceoryx 共享内存实现 fixed 类型零拷贝;
  • 注意:Service RPC 使用 自定义 inline header,跨厂商调用需额外兼容配置。

Humble 默认 RMW 仍为 Fast-DDS,选用 Cyclone 需显式 export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp 并在构建时确保 cyclonedds 包可用。

rmw_dds_common 源码详细分析

rmw_dds_common 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rmw_dds_common
版本:1.6.0(Humble),子包 1 个,构建类型 ament_cmake,语言 C++,许可证 Apache 2.0,质量等级 QL1

rmw_dds_common 是 ROS 2 中 DDS 系 RMW 实现的共享 C++ 库:提供 ROS Graph 缓存跨进程节点发现消息QoS 兼容性检查GID/安全/时间 等工具。它 直接调用 DDS API,也不实现 rmw_* 符号;各 rmw_*_cpp 在 init/graph 路径中嵌入并驱动本库。

设计背景:ROS 2 Node Discovery (design #250)


1. 总体认识

1.1 核心职责

能力 说明
GraphCache 维护 Participant / Node / DataReader / DataWriter 关系与 QoS
发现消息 定义 ParticipantEntitiesInfo 等 msg,在 ros_discovery_info topic 传播
Context 模板 rmw_dds_common::Context 汇总 graph 所需 RMW 句柄与线程字段
QoS 工具 qos_profile_check_compatible 实现 RMW QoS 兼容 API
GID 工具 rmw_gid_t 比较、转换、调试输出
SROS2 辅助 从 enclave 目录收集 DDS Security 证书文件路径
时间转换 clamp_rmw_time_to_dds_time 适配 DDS 32 位 Time/Duration

1.2 在 ROS 2 栈中的位置

上层DDS RMW 实现rmw_dds_commonrmw 包rclros2 topic/node listrmw_cyclonedds_cpprmw_connextddsrmw_fastrtps_cpp\n部分 graph APIGraphCacheContextParticipantEntitiesInfoqos_profile_check_compatiblermw.h 类型与 graph API 声明
消费者 使用方式
rmw_cyclonedds_cpp rmw_context_impl_t::common 嵌入 Context;graph 全走 GraphCache
rmw_connextdds 同上 + graph_cache.hpp 钩子
rmw_fastrtps_cpp 主要使用 qos_profile_check_compatible;graph 仍有自有逻辑
rcl 间接通过 RMW graph API(rmw_get_node_names 等)

关键区分:本库是 库(library),不是 RMW 实现;链接 librmw_dds_common.so 不会提供 rmw_init


2. 包结构与构建

1
2
3
4
5
6
7
8
9
10
rmw_dds_common/
└── rmw_dds_common/
├── msg/ # rosidl 消息定义
│ ├── Gid.msg
│ ├── NodeEntitiesInfo.msg
│ └── ParticipantEntitiesInfo.msg
├── include/rmw_dds_common/ # 6 个头文件
├── src/ # 5 个 .cpp(~1646 行)
├── test/ # gmock + benchmark
└── docs/FEATURES.md
组件 行数(约) 说明
graph_cache.cpp 1069 GraphCache 全部逻辑
graph_cache.hpp 566 API 与内部类型
qos.cpp 377 QoS 兼容性
gid_utils.cpp 75 GID 转换
security.cpp 64 SROS2 文件查找
time_utils.cpp 61 时间截断

2.1 依赖(package.xml

1
2
3
4
5
6
rmw_dds_common
├── rmw # rmw_gid_t、qos、topic_endpoint_info 等
├── rcutils # 分配器、日志、字符串
├── rcpputils # 文件系统(security)
├── rosidl_runtime_cpp # 生成消息 C++ 类型
└── rosidl_default_generators

2.2 构建产物

1
2
3
4
5
6
7
8
9
10
11
12
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Gid.msg"
"msg/NodeEntitiesInfo.msg"
"msg/ParticipantEntitiesInfo.msg")

add_library(${PROJECT_NAME}_library SHARED
src/gid_utils.cpp
src/graph_cache.cpp
src/qos.cpp
src/security.cpp
src/time_utils.cpp)
# OUTPUT_NAME → librmw_dds_common.so

PUBLIC 链接 rmw::rmw 与生成的 rosidl_typesupport_cpp 目标。


3. 发现消息(rosidl)

3.1 消息层次

1
2
3
4
5
6
7
ParticipantEntitiesInfo
├── Gid gid # DDS Participant 的 rmw_gid
└── NodeEntitiesInfo[] node_entities_info_seq
├── string node_namespace
├── string node_name
├── Gid[] reader_gid_seq # 该节点拥有的 DataReader
└── Gid[] writer_gid_seq # 该节点拥有的 DataWriter
1
2
# Gid.msg — 与 rmw_gid_t.data 对齐(24 字节)
char[24] data

3.2 传播路径(由各 RMW 实现 wiring)

1
2
3
4
5
6
7
8
本地 rmw_create_node / create_publisher
→ GraphCache::add_node / associate_writer
→ 返回 ParticipantEntitiesInfo
→ rmw_publish("ros_discovery_info", msg)

远端 rmw_take("ros_discovery_info")
→ GraphCache::update_participant_entities(msg)
→ 合并 node ↔ endpoint GID 映射

DDS builtin topic(DCPSPublication/Subscription/Participant)则通过 add_reader / add_writer 填充 topic 名、类型名、QoS,与 ROS 层 node 关联 正交——GraphCache 将两路信息合并后回答 graph 查询。


4. GraphCache 数据模型

4.1 三张内部表

1
2
3
4
5
6
class GraphCache {
EntityGidToInfo data_writers_; // gid → EntityInfo
EntityGidToInfo data_readers_;
ParticipantToNodesMap participants_; // gid → ParticipantInfo
std::mutex mutex_;
};
结构 字段 含义
EntityInfo topic_name, topic_type, participant_gid, qos 单个 DataReader/Writer 的 DDS 发现信息
ParticipantInfo node_entities_info_seq, enclave 某 Participant 下的 ROS 节点及 endpoint GID 列表

EntityGidToInfo 使用 Compare_rmw_gid_t 作为 map 键(按 24 字节 lexicographical 比较)。

4.2 更新 API 分组

graph_cache.hpp 将方法分为四组:

分组 方法 数据来源
dds_discovery_api add_reader/writer, remove_*, add_entity DCPS builtin / 远端 DDS 发现
common_api add_participant, remove_participant Participant 出现/消失
ros_discovery_api update_participant_entities ros_discovery_info 消息
local_api add_node, remove_node, associate_*, dissociate_* 本进程创建/销毁实体;返回 ParticipantEntitiesInfo 供 publish
introspection_api get_node_names, get_names_and_types, get_*_info_by_topic 只读查询

4.3 变更回调

1
2
3
graph_cache.set_on_change_callback([]() {
rmw_trigger_guard_condition(graph_guard_condition);
});

RMW 实现注册回调,在 graph 变化时唤醒 rcl wait set(用于 ros2 topic list 等刷新)。


5. GraphCache 工作流程

5.1 本地节点创建(典型时序)

ros_discovery_info PublisherGraphCachermw_*_cppros_discovery_info PublisherGraphCachermw_*_cppnode_update_mutex 保护 update+publish 原子性add_participant(participant_gid, enclave)add_node(gid, name, ns)ParticipantEntitiesInformw_publish(msg)

5.2 本地 Publisher 创建

1
2
3
4
create_publisher()
→ GraphCache::add_writer(writer_gid, topic, type, participant_gid, qos) // DDS 侧信息
→ GraphCache::associate_writer(writer_gid, participant_gid, node_name, ns) // 挂到 node
→ 返回 ParticipantEntitiesInfo → publish

5.3 远端更新

1
2
3
take(ros_discovery_info)
→ GraphCache::update_participant_entities(msg)
→ 覆盖 participants_[gid].node_entities_info_seq

5.4 introspection 查询

  • get_names_and_types:遍历 data_writers_/data_readers_,经 demangle 回调 还原 ROS topic/type 名,聚合为 rmw_names_and_types_t
  • get_node_names:遍历 participants_ 中所有 NodeEntitiesInfo
  • get_writers/readers_info_by_topic:按 DDS topic 名过滤 entity,填充 rmw_topic_endpoint_info_t(含 node 名、namespace、GID、QoS)。

Demangle 由 RMW 实现注入DemangleFunctionT),本库不硬编码 rt/ 前缀规则。


6. Context 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace rmw_dds_common {
struct Context {
rmw_gid_t gid; // 本 Participant 的 GID
rmw_publisher_t * pub; // 发布 ParticipantEntitiesInfo
rmw_subscription_t * sub; // 订阅 ros_discovery_info
GraphCache graph_cache;
std::mutex node_update_mutex; // update + publish 互斥
std::thread listener_thread; // 由 RMW 创建,非本库
std::atomic_bool thread_is_running;
rmw_guard_condition_t * listener_thread_gc;
rmw_guard_condition_t * graph_guard_condition;
};
}

本库只定义结构体,不创建线程或 DDS 实体。rmw_cyclonedds_cpp / rmw_connextdds 在 context init 时:

  1. 创建 internal pub/sub on ros_discovery_info
  2. 启动 listener/discovery 线程处理 builtin + ParticipantEntitiesInfo
  3. graph_guard_condition 注册到 GraphCache 回调

7. GID 工具(gid_utils

API 作用
Compare_rmw_gid_t std::map<rmw_gid_t, ...> 排序键
operator== 24 字节 memcmp
operator<< 十六进制调试打印
convert_gid_to_msg / convert_msg_to_gid rmw_gid_tmsg/Gid
1
2
3
void convert_gid_to_msg(const rmw_gid_t * gid, msg::Gid * msg_gid) {
std::memcpy(&msg_gid->data, gid->data, RMW_GID_STORAGE_SIZE);
}

implementation_identifier 字段 进入 msg,仅在 rmw_gid_t 全结构中使用。


8. QoS 兼容性(qos.cpp

实现 rmw_qos_profile_check_compatible 的共享逻辑,被 Cyclone/Connext/FastRTPS RMW 转发调用。

8.1 判定级别

结果 含义
RMW_QOS_COMPATIBILITY_OK 可通信
RMW_QOS_COMPATIBILITY_WARNING 可能有问题(如 depth 不足)
RMW_QOS_COMPATIBILITY_ERROR 确定不兼容

8.2 主要 ERROR 规则(节选)

条件 原因
Pub BEST_EFFORT + Sub RELIABLE 可靠订阅收不到尽力 pub
Pub VOLATILE + Sub TRANSIENT_LOCAL 晚加入订阅者收不到历史
Sub deadline 更严而 Pub 无/更松 deadline 契约不满足
Pub AUTOMATIC liveliness + Sub MANUAL_BY_TOPIC liveliness 策略冲突
Sub lease 短于 Pub lease lease 不匹配

WARNING 包括 history depth 小于订阅需求等。reason 缓冲区可选,用 _append_to_buffer 拼接人类可读说明。


9. 安全工具(security.cpp

1
2
3
4
bool get_security_files(
const std::string & prefix,
const std::string & secure_root,
std::unordered_map<std::string, std::string> & result);

secure_root(SROS2 enclave 目录)查找 必需 文件:

文件名
IDENTITY_CA identity_ca.cert.pem
CERTIFICATE cert.pem
PRIVATE_KEY key.pem
PERMISSIONS_CA permissions_ca.cert.pem
GOVERNANCE governance.p7s
PERMISSIONS permissions.p7s

可选:CRLcrl.pem。缺失任一必需文件则返回 false 并清空 result。RMW 将 result 映射到 DDS Security QoS property。


10. 时间工具(time_utils.cpp

DDS IDL Duration_t / Time_t 在 C 绑定中为 32 位 sec/nsec。clamp_rmw_time_to_dds_time

  1. 将 nsec 归一化到 < 1s
  2. 若总时长超过 INT_MAX 秒,截断到 INT_MAX sec + (10^9-1) nsec 并打 debug 日志

各 RMW 在 ROS QoS → DDS QoS 转换时调用,避免静默溢出。


11. 线程安全与锁

位置 用途
GraphCache::mutex_ GraphCache 内部 所有 cache 读写
Context::node_update_mutex RMW 层使用 add_node + rmw_publish 原子性

GraphCache 方法自身已加锁;RMW 在 publish 发现消息前额外持有 node_update_mutex,防止交错更新导致远端 cache 状态不一致(见 Cyclone rmw_create_node 注释)。


12. 测试

测试 文件 覆盖
test_graph_cache test/test_graph_cache.cpp add/remove/associate、introspection、并发
test_gid_utils test/test_gid_utils.cpp GID 比较与转换
test_qos test/test_qos.cpp QoS 兼容边界
test_time_utils test/test_time_utils.cpp 时间截断
test_security test/test_security.cpp enclave 文件查找
benchmark_graph_cache test/benchmark/ GraphCache 性能

启用 RCUTILS_ENABLE_FAULT_INJECTION 测试分配失败路径。


13. RMW 实现集成要点

rmw_cyclonedds_cpp 为例(Connext 结构类似):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct rmw_context_impl_s {
rmw_dds_common::Context common;
// ... DDS handles ...
};

// init 时
common.pub = create_publisher(..., "ros_discovery_info", ...);
common.sub = create_subscription(..., "ros_discovery_info", ...);
common.gid = ppant_gid;

// create_node 时
std::lock_guard<std::mutex> guard(common.node_update_mutex);
auto msg = common.graph_cache.add_node(common.gid, name, namespace_);
rmw_publish(common.pub, &msg, ...);

// DCPS 回调
graph_cache.add_writer(gid, dds_topic, dds_type, participant_gid, qos);

// graph API
return common.graph_cache.get_node_names(..., demangle_fn);

Fast-DDS RMW 对 GraphCache 的采用程度较低,但 QoS 检查security 文件 仍复用本库。


14. 与 rmw 包的分工

主题 rmw 包 rmw_dds_common
rmw_* API 声明
rmw_validate_*、QoS 字符串
Graph 缓存与发现 msg
rmw_qos_profile_check_compatible 实现 声明 ✅ 实现
DDS 调用

15. 调试建议

  1. Graph 不完整:确认 ros_discovery_info pub/sub 存在;检查 update_participant_entities 是否被调用。
  2. topic list 与 echo 不一致:可能是 demangle 回调未剥离 rt/;或 DDS 侧 add_writer 与 ROS 侧 associate_writer 未配对。
  3. QoS 兼容误报:阅读 reason 字符串;对照 qos.cpp 规则。
  4. Security 启动失败:用 get_security_files 检查 enclave 目录六文件是否齐全。
  5. 打印 cache 状态operator<<(ostream, GraphCache) 可 dump 内部 map(调试构建)。

16. 推荐阅读顺序

  1. docs/FEATURES.md — 功能清单
  2. msg/*.msg — 发现协议数据结构
  3. include/rmw_dds_common/context.hpp — RMW 嵌入字段
  4. graph_cache.hpp API 分组 — 理解四路更新
  5. graph_cache.cppadd_node / associate_writer / get_names_and_types
  6. qos.cpp — QoS 兼容规则
  7. gid_utils.cpp — GID 互转
  8. 下游 rmw_cyclonedds_cpp/src/rmw_node.cpp — discovery 线程如何喂 GraphCache
  9. 设计文档 ros2/design#250

17. 小结

rmw_dds_common 是 ROS 2 DDS RMW 之间的“图与发现公共层”

  • 对上:为 rmw_get_node_namesros2 topic list 等提供统一的缓存与查询;
  • 对下:通过标准 msg 在 Participant 间同步 Node ↔ Endpoint 映射,并与 DDS builtin 发现互补;
  • 横向:QoS、GID、Security、Time 工具避免在 Cyclone/Connext/FastRTPS 中重复实现。

理解本库是阅读 rmw_cycloneddsrmw_connextdds graph 代码的前提;其本身 不包含 DDS 传输,仅维护 ROS 语义层面的分布式图

rmw_fastrtps 源码详细分析

rmw_fastrtps 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rmw_fastrtps
版本:6.2.10(Humble),子包 3 个,构建类型 ament_cmake,语言 C++,许可证 Apache 2.0,质量等级 QL2

rmw_fastrtps 是 ROS 2 面向 eProsima Fast-DDS 的 RMW 实现族,将标准 rmw_* C API 映射到 Fast-DDS 的 DDS C++ APIfastdds/dds/*)及 RTPS 层。Humble 上它是 默认 RMWget_default_rmw_implementation() 优先 rmw_fastrtps_cpp);也可选用 rmw_fastrtps_dynamic_cpp

外部依赖:DDS 本体在独立仓库 eProsima/Fast-DDS(Humble 对应 2.6.x),序列化依赖 Fast-CDR


1. 总体认识

1.1 核心职责

能力 说明
双 RMW 注册 rmw_fastrtps_cpp(静态 typesupport)与 rmw_fastrtps_dynamic_cpp(introspection)各自注册为独立 .so
共享核心 rmw_fastrtps_shared_cpp 实现全部 __rmw_* 逻辑,单独注册 RMW
Fast-CDR 序列化 通过 TypeSupport 继承 TopicDataType,桥接 ROS 消息 ↔ RTPS payload
Graph 发现 嵌入 rmw_dds_common::Context;Fast-DDS 扩展 listener + ros_discovery_info topic
Service/Client Fast-DDS extended RPCSampleIdentity / related_sample_identity
零拷贝 SharedMem 传输 + Data Sharing + Loan API(plain 类型 + XML 启用 Data Sharing)
QoS 可配置 ROS QoS + 环境变量 + Fast-DDS XML profile

1.2 在 ROS 2 栈中的位置

上层rmw_fastrtps 仓库ROS 共享DDS 栈rcl / rclcpp / rclpyrmw_fastrtps_cpp\n~4328 行 .cpprmw_fastrtps_dynamic_cpp\n~4588 行 .cpprmw_fastrtps_shared_cpp\n~6898 行 .cpprmwrmw_dds_commonrosidl_typesupport_fastrtps_*rosidl_typesupport_introspection_*Fast-DDS libfastrtpsFast-CDR
对比项 rmw_fastrtps_cpp rmw_fastrtps_dynamic_cpp rmw_cyclonedds_cpp rmw_connextdds
DDS Fast-DDS Fast-DDS CycloneDDS RTI Connext
序列化 Fast-CDR + 编译期 callbacks Fast-CDR + introspection 遍历 自研 introspection CDR Fast-CDR typesupport
源码组织 shared + 薄 API + 实体创建 同左 + TypeSupportRegistry 单巨型 rmw_node.cpp common + 薄 API
Graph rmw_dds_common 同左 rmw_dds_common rmw_dds_common
Service RPC extended(SampleIdentity) 同左 basic header basic/extended 可配
默认 Humble RMW
SHM 零拷贝 Data Sharing + SHM transport 同左 iceoryx(需 RouDi) Pro 异步 publish

1.3 与 Fast-DDS 的边界

1
2
3
4
5
6
7
8
9
10
rcl → rmw_fastrtps_cpp / rmw_fastrtps_dynamic_cpp
│ extern "C" rmw_*() ← 薄包装,传 implementation identifier
└── rmw_fastrtps_shared_cpp::__rmw_*()
├── CustomParticipantInfo / CustomPublisherInfo / CustomSubscriberInfo
├── ParticipantListener → rmw_dds_common::GraphCache(RTPS 发现)
├── listener_thread → ros_discovery_info take
├── TypeSupport → Fast-CDR serialize/deserialize
└── Fast-DDS API
DomainParticipant / DataWriter / DataReader / WaitSet
└── Fast-DDS RTPS(UDPv4 + SharedMem)

2. 三包结构与构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
rmw_fastrtps/
├── README.md # 环境变量、XML QoS、零拷贝说明
├── rmw_fastrtps_shared_cpp/ # ★ 共享实现(非 RMW 注册包)
│ ├── include/rmw_fastrtps_shared_cpp/
│ │ ├── rmw_common.hpp # 全部 __rmw_* 声明
│ │ ├── rmw_context_impl.hpp # rmw_context_impl_s 定义
│ │ ├── custom_participant_info.hpp # ParticipantListener + GraphCache
│ │ ├── TypeSupport.hpp # TopicDataType 抽象基类
│ │ └── custom_*_info.hpp # pub/sub/service/client 私有数据
│ └── src/
│ ├── participant.cpp # create_participant、QoS/安全/传输
│ ├── listener_thread.cpp # ros_discovery_info 后台线程
│ ├── rmw_wait.cpp # Fast-DDS WaitSet
│ ├── rmw_publish.cpp / rmw_take.cpp
│ ├── rmw_request.cpp / rmw_response.cpp # Service RPC
│ ├── qos.cpp # rmw ↔ DDS QoS 映射
│ └── TypeSupport_impl.cpp # serialize/deserialize 通用逻辑
├── rmw_fastrtps_cpp/ # 默认 RMW(静态 typesupport)
│ ├── src/
│ │ ├── rmw_*.cpp # extern "C" 薄包装
│ │ ├── init_rmw_context_impl.cpp # 首 node 时 lazy init
│ │ ├── publisher.cpp / subscription.cpp # 实体创建 + TypeSupport
│ │ ├── type_support_common.cpp # message_type_support_callbacks_t
│ │ └── rmw_service.cpp / rmw_client.cpp
│ └── CMakeLists.txt # register_rmw_implementation
└── rmw_fastrtps_dynamic_cpp/ # introspection RMW
├── src/
│ ├── type_support_registry.cpp # 运行时 TypeSupport 引用计数
│ └── (其余镜像 rmw_fastrtps_cpp 结构)
└── CMakeLists.txt
.cpp 行数(约) 职责
rmw_fastrtps_shared_cpp 6898 全部 __rmw_*、wait、graph listener、QoS、安全
rmw_fastrtps_cpp 4328 rmw_* 导出 + 静态 TypeSupport 实体创建
rmw_fastrtps_dynamic_cpp 4588 同上 + TypeSupportRegistry
合计 ~17644

2.1 依赖(package.xml 摘要)

1
2
3
4
5
6
7
8
9
10
rmw_fastrtps_cpp
├── fastrtps / fastcdr / fastrtps_cmake_module
├── rmw / rmw_dds_common / rmw_fastrtps_shared_cpp
├── rosidl_typesupport_fastrtps_c/cpp ← 仅 static 包
├── rcutils / rcpputils / tracetools
└── member_of_group: rmw_implementation_packages

rmw_fastrtps_dynamic_cpp
├── (同上,但 typesupport 换为 rosidl_typesupport_introspection_c/cpp)
└── 不依赖 rosidl_typesupport_fastrtps_*

3. 双实现 + identifier 模式

3.1 两个 RMW identifier

identifier 常量 注册方式
rmw_fastrtps_cpp "rmw_fastrtps_cpp"eprosima_fastrtps_identifier register_rmw_implementation()
rmw_fastrtps_dynamic_cpp "rmw_fastrtps_dynamic_cpp" 同上

每个 handle(rmw_node_trmw_publisher_t 等)的 implementation_identifier 必须与传入的 identifier 一致,否则返回 RMW_RET_INCORRECT_RMW_IMPLEMENTATION

3.2 薄包装 → 共享实现

rmw_fastrtps_cpp 中典型模式:

1
2
3
4
5
6
// rmw_fastrtps_cpp/src/rmw_publisher.cpp
rmw_ret_t rmw_publish(const rmw_publisher_t * publisher, const void * ros_message, ...)
{
return rmw_fastrtps_shared_cpp::__rmw_publish(
eprosima_fastrtps_identifier, publisher, ros_message, allocation);
}

共享库中校验 identifier 并执行 Fast-DDS 调用:

1
2
3
4
5
6
// rmw_fastrtps_shared_cpp/src/rmw_publish.cpp
RMW_CHECK_TYPE_IDENTIFIERS_MATCH(
publisher, publisher->implementation_identifier, identifier, ...);
auto info = static_cast<CustomPublisherInfo *>(publisher->data);
SerializedData data{false, const_cast<void *>(ros_message), info->type_support_impl_};
info->data_writer_->write(&data);

3.3 差异仅在 TypeSupport 与实体创建

步骤 rmw_fastrtps_cpp rmw_fastrtps_dynamic_cpp
获取 TypeSupport TypeSupport::set_members(message_type_support_callbacks_t*) TypeSupportRegistry::get_message_type_support()
typesupport 来源 rosidl_typesupport_fastrtps_* rosidl_typesupport_introspection_*
生命周期 每 publisher 独立 TypeSupport 实例 Registry 引用计数共享
其余路径 调用相同的 __rmw_publish / __rmw_take / __rmw_wait 同左

4. Context 与 Participant 生命周期

4.1 rmw_context_impl_s

定义于 rmw_fastrtps_shared_cpp/include/rmw_fastrtps_shared_cpp/rmw_context_impl.hpp

1
2
3
4
5
6
7
struct rmw_context_impl_s {
void * common; // rmw_dds_common::Context*
void * participant_info; // CustomParticipantInfo*
std::mutex mutex;
uint64_t count; // 引用计数(按 node 计数)
bool is_shutdown;
};

4.2 初始化时序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rmw_init(options, context)
├── 设置 implementation_identifier、actual_domain_id
├── 分配 context->impl(此时尚未创建 Participant)
└── 拷贝 init_options

rmw_create_node(context, ...) ← 第一个 node 触发 heavyweight init
└── increment_context_impl_ref_count(context) [rmw_fastrtps_cpp/init_rmw_context_impl.cpp]
├── 若 count==0 → init_context_impl()
│ ├── new rmw_dds_common::Context
│ ├── create_participant() → CustomParticipantInfo + ParticipantListener
│ ├── create_publisher/subscription on "ros_discovery_info"
│ ├── create graph_guard_condition
│ ├── run_listener_thread()
│ └── graph_cache.set_on_change_callback → trigger GC
└── count++

rmw_destroy_node(...)
└── decrement_context_impl_ref_count() [shared/init_rmw_context_impl.cpp]
├── 若 count→0:join listener、destroy discovery pub/sub、destroy participant
└── delete rmw_dds_common::Context

rmw_shutdown(context) → is_shutdown = true
rmw_context_fini(context) → 要求 count==0 且已 shutdown

设计要点:Participant 与 graph 基础设施 按 context 懒加载,首个 rmw_create_node 时才创建 Fast-DDS 实体;多 node 共享同一 DomainParticipant

4.3 create_participant 关键行为

participant.cppcreate_participant()

配置项 行为
enclave 写入 DomainParticipantQos.user_dataenclave=<name>;,供 graph 过滤
localhost_only 禁用 builtin transport,仅 127.0.0.1 UDPv4 + SharedMem
RMW_FASTRTPS_USE_QOS_FROM_XML 1leave_middleware_default_qos=true,history/publication 由 XML 决定
RMW_FASTRTPS_PUBLICATION_MODE SYNCHRONOUS / ASYNCHRONOUS / AUTO(XML 模式时忽略)
默认 memory policy PREALLOCATED_WITH_REALLOC(非 XML 模式)
Data Sharing 默认 OFF;通过 XML 启用
安全 security_root_path 存在时配置 PKI-DH + AES-GCM-GMAC + Access-Permissions

Participant 内还预创建 PublisherSubscriber 对象(entity_creation_mutex_ 保护后续 topic/writer/reader 创建)。


5. Graph 与 rmw_dds_common

5.1 双通道更新 GraphCache

本进程远端进程RTPS 发现DDS topicrmw_create/destroy_nodegraph_cache.add/remove_noderos_discovery_info publisherros_discovery_info subscriptionlistener_threadgraph_cache.update_participant_entitiesParticipantListenergraph_cache add/remove participant/entity

通道 1 — 应用层 discovery topic

  • Topic:ros_discovery_infoavoid_ros_namespace_conventions=true
  • 消息:rmw_dds_common::msg::ParticipantEntitiesInfo
  • Publisher QoS:TRANSIENT_LOCAL + KEEP_LAST depth=1
  • Subscriber QoS:KEEP_ALL
  • listener_thread 循环 __rmw_wait + __rmw_take,忽略本 participant 的 GID

通道 2 — Fast-DDS 扩展 listener

ParticipantListenercustom_participant_info.hpp)重写:

  • on_participant_discovery → 解析 user_data 中 enclavegraph_cache.add/remove_participant
  • on_subscriber_discovery / on_publisher_discoverygraph_cache.add/remove_entity(含 QoS)

节点增删时在 __rmw_create_node / __rmw_destroy_node 中加锁发布 ParticipantEntitiesInfonode_update_mutex 保证 publish 原子性)。

5.2 Graph Guard Condition

graph_cache.set_on_change_callback 注册 lambda,在图变化时 __rmw_trigger_guard_condition(graph_guard_condition),供 rmw_node_get_graph_guard_condition 使用。


6. TypeSupport 与序列化

6.1 抽象基类

TypeSupport 继承 eprosima::fastdds::dds::TopicDataType

1
2
3
4
5
struct SerializedData {
bool is_cdr_buffer; // true → data 指向 FastBuffer/Cdr
void * data;
const void * impl; // typesupport callbacks 或 introspection members
};
  • serialize() / deserialize()TypeSupport_impl.cpp 中实现,根据 is_cdr_buffer 分支
  • 普通 publish:is_cdr_buffer=falseimpl 指向 message_type_support_callbacks_t 或 introspection members

6.2 静态 typesupport(rmw_fastrtps_cpp

type_support_common.cpp

  • set_members() 读取 ROSIDL_TYPESUPPORT_FASTRTPS_* 宏判断 plain/bounded
  • serializeROSmessage() 调用编译期生成的 callbacks->cdr_serialize
  • is_plain() 决定 Data Sharing / loan 能力

6.3 动态 typesupport(rmw_fastrtps_dynamic_cpp

TypeSupportRegistry(单例 + 引用计数):

  • get_message_type_support() / get_request_type_support() / get_response_type_support()
  • 支持 rosidl_typesupport_introspection_c_cpp
  • 进程退出时 ~TypeSupportRegistry() 清理残留

适用场景:运行时切换消息类型、工具链不生成 fastrtps typesupport 的包;性能通常低于静态路径。


7. Publisher / Subscription 数据路径

7.1 创建流程(以 publisher 为例)

rmw_fastrtps_cpp/src/publisher.cppcreate_publisher()

  1. 校验 topic 名、QoS、require_unique_network_flow_endpoints
  2. map_ros_to_dds() 生成 topic/type 名(rt/ 前缀、ros2msg/ros2srv 等)
  3. 构造包内 TypeSupport,注册到 participant
  4. rmw_qos_to_dds_attributes() + XML profile 加载(按 topic 名匹配 profile_name
  5. 创建 Topic + DataWriter + 可选 PubListener
  6. 填充 CustomPublisherInfo,设置 publisher_gid
  7. can_loan_messages = has_data_sharing && type_support->is_plain()

7.2 发布

API 路径
rmw_publish SerializedData{ros_message}TypeSupport::serializeDataWriter::write
rmw_publish_serialized_message 预序列化 CDR buffer → is_cdr_buffer=true
rmw_publish_loaned_message 直接 DataWriter::write(loaned_buffer)(无 SerializedData 包装)

7.3 接收

rmw_take.cpp(~558 行):

  • DataReader::takedeserializeROSmessage 或 loaned 路径
  • 支持 rmw_take_with_info(publication handle 等)
  • ignore_local_publications 在 init 时设为 true(注释:fastrtps 尚未完整实现该选项语义)

7.4 实体私有结构

结构体 主要字段
CustomPublisherInfo DataWriter*, TypeSupport, type_support_impl_, PubListener
CustomSubscriberInfo DataReader*, loan 相关状态
CustomServiceInfo request reader + response writer
CustomClientInfo request writer + response reader + GUID 映射

均继承或组合 CustomEventInfo,支持 QoS/liveliness/deadline 事件。


8. Service / Client(Extended RPC)

Fast-DDS 使用 DDS 扩展 WriteParams 关联 request/response,与 Connext extended 模式互操作。

8.1 Client 发请求

rmw_request.cpp__rmw_send_request

1
2
3
wparams.related_sample_identity().writer_guid() = info->reader_guid_;
info->request_writer_->write(&data, wparams);
*sequence_id = (high << 32) | low; // 来自 sample_identity

8.2 Service 收请求 / 发响应

  • __rmw_take_request:从 request reader take,读取 related_sample_identity 填入 rmw_service_info_t
  • __rmw_send_response:设置 wparams.related_sample_identity() 指向原 request

8.3 Client 收响应

__rmw_take_response:校验 related_sample_identity.writer_guid 匹配 client 的 reader/writer GUID,再反序列化。

互操作:与 Cyclone basic header 不兼容;与 Connext extended 可互通。跨 RMW service 调用需统一 RPC 映射。


9. Wait Set 与 rmw_wait

rmw_wait.cpp 使用 Fast-DDS WaitSet

  1. 遍历 subscriptions / clients / services / events,attach 各 StatusCondition
  2. 若已有 untaken sample(get_first_untaken_info)→ skip_wait=true
  3. attach guard conditions(含 graph GC、listener thread GC)
  4. wait_set->wait()wait(timeout)
  5. 未就绪条目在 rcl 数组中置 NULL

WaitSet 存储于 rmw_wait_set_t->dataeprosima::fastdds::dds::WaitSet*)。


10. QoS 与环境变量

10.1 默认 Fast-DDS 策略(非 XML 模式)

策略 默认值
History memory PREALLOCATED_WITH_REALLOC
Publication mode SYNCHRONOUS
Data Sharing OFF

10.2 环境变量

变量 作用
RMW_IMPLEMENTATION rmw_fastrtps_cpprmw_fastrtps_dynamic_cpp
RMW_FASTRTPS_PUBLICATION_MODE SYNCHRONOUS / ASYNCHRONOUS / AUTO
RMW_FASTRTPS_USE_QOS_FROM_XML 1 时 history/publication/datasharing 由 XML 覆盖
FASTRTPS_DEFAULT_PROFILES_FILE XML profile 路径
(工作目录) DEFAULT_FASTRTPS_PROFILES.xml

10.3 XML Profile 匹配规则

  • Publisher/Subscription:profile_name = 最终 topic 名(含 namespace,FQN 以 / 开头时不拼 namespace)
  • Service:profile_name="service" 或 mangled 服务名
  • Client:profile_name="client" 或 mangled 服务名
  • 回退:is_default_profile="true"

10.4 ROS ↔ DDS 映射

qos.cpp / rmw_qos.cpp

  • rmw_qos_to_dds_attributes() 创建实体时使用
  • rtps_qos_to_rmw_qos()ParticipantListener 写入 graph
  • rmw_qos_profile_check_compatible() 委托 rmw_dds_common::qos_profile_check_compatible()

11. Loaned Message 与零拷贝

11.1 启用条件

1
2
// publisher.cpp / rmw_take.cpp
can_loan_messages = has_data_sharing && type_support_->is_plain();

需同时满足:

  1. XML 中 Data Sharing 为 AUTOMATICON(且 RMW_FASTRTPS_USE_QOS_FROM_XML=1
  2. 消息类型为 plain(POD,静态 typesupport 由 ROSIDL_TYPESUPPORT_FASTRTPS_PLAIN_TYPE 判定)

11.2 API 路径

API 实现要点
rmw_borrow_loaned_message DataWriter::loan_sample()
rmw_publish_loaned_message 直接 write 借出的 buffer
rmw_return_loaned_message_from_publisher return_loan()
rmw_take_loaned_message reader loan + 反序列化跳过

11.3 传输层

默认 builtin transports:UDPv4(跨主机)+ SharedMem(同主机)。零拷贝 pipeline = Loan API + Data Sharing;仅 SHM transport 不等同于零拷贝。

Humble 上 plain 类型仍需 XML 启用 Data Sharing;Iron 及以后对 POD 要求放宽(见仓库 README)。


12. 事件、安全与追踪

12.1 RMW 事件

通过 CustomEventInfo + DataWriterListener / DataReaderListener 收集:

  • RMW_EVENT_*_QOS_INCOMPATIBLE
  • RMW_EVENT_*_DEADLINE_MISSED
  • RMW_EVENT_LIVELINESS_*
  • RMW_EVENT_MESSAGE_LOST

rmw_event.cpp + event_helpers.hpp 实现 rmw_take_event 与 callback 注册。

12.2 安全(SROS2)

participant.cppHAVE_SECURITY 且提供 security_root_path 时:

  • 使用 rmw_dds_common::get_security_files() 解析证书路径
  • 配置 PropertyPolicy(PKI-DH、AES-GCM-GMAC、Access-Permissions)
  • rmw_security_logging.cpp 处理安全日志属性

12.3 追踪

关键路径插入 tracetools 追踪点(如 TRACEPOINT(rmw_publish, ...))。

12.4 特性查询

rmw_features.cpp 当前仅声明支持:

  • RMW_FEATURE_MESSAGE_INFO_PUBLICATION_SEQUENCE_NUMBER

13. 未实现或受限功能

API / 能力 状态
rmw_init_publisher/subscription_allocation RMW_RET_UNSUPPORTED
RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_STRICTLY_REQUIRED 创建 pub/sub 时拒绝
ignore_local_publications 订阅选项强制为 true,功能未完整实现
Loan API 需 Data Sharing + plain 类型
Keyed topics discovery 路径显式 keyed=false
Graph 与 typesupport discovery 使用静态 ParticipantEntitiesInfo typesupport

14. 与 rmw_dds_common 的协作

组件 用法
rmw_dds_common::Context context->impl->common;含 pub/sub/gid/graph_cache/listener 线程
GraphCache 节点、participant、endpoint、topic 图
ParticipantEntitiesInfo ros_discovery_info 消息类型
qos_profile_check_compatible QoS 兼容 API
security.hpp 安全文件路径解析
gid_utils GID 比较(listener 忽略本地消息)

Fast-DDS 实现与 Cyclone/Connext 新版共用同一 graph 协议,保证 ros2 topic list / ros2 node info 等工具跨 RMW 一致。


15. 互操作说明

场景 默认互操作
Pub/Sub(CDR + rt/ topic) 与 Cyclone、Connext 等 通常可互通
Service/Client 同 RMW 或 extended 兼容实现
Fast-DDS ↔ Cyclone service 不互通(RPC 映射不同)
Data Sharing 零拷贝 同进程/同机 + 同 RMW + plain + XML 配置

16. 调试建议

  1. 确认 RMWecho $RMW_IMPLEMENTATIONros2 doctor --report;默认应为 rmw_fastrtps_cpp
  2. Discovery 问题:检查防火墙与 multicast;localhost_only 时仅 127.0.0.1 + SHM。
  3. Graph 空:确认 ros_discovery_info pub/sub 创建成功;listener_thread 是否运行;enclave user_data 是否解析。
  4. QoS 不符预期:用 rmw_publisher_get_actual_qos;检查 RMW_FASTRTPS_USE_QOS_FROM_XML 与 XML profile 名是否匹配 topic。
  5. 零拷贝不生效:确认 can_loan_messages、Data Sharing XML、消息是否 plain。
  6. Fast-DDS 日志:通过 Fast-DDS 自身日志配置(Consumer 等)排查 RTPS 层。

17. 推荐阅读顺序

  1. 仓库 README.md — 环境变量、XML 示例、零拷贝
  2. rmw_fastrtps_cpp/src/rmw_init.cpp + init_rmw_context_impl.cpp — context 懒加载与 discovery 拓扑
  3. custom_participant_info.hpp — ParticipantListener 与 graph 关系
  4. listener_thread.cppros_discovery_info 消费循环
  5. rmw_fastrtps_cpp/src/publisher.cpp — 实体创建、XML QoS、loan 标志
  6. TypeSupport.hpp + TypeSupport_impl.cpp — 序列化核心
  7. rmw_publish.cpp / rmw_take.cpp — 数据平面
  8. rmw_request.cpp / rmw_response.cpp — Service RPC
  9. rmw_wait.cpp — WaitSet 语义
  10. type_support_registry.cpp(dynamic 包)— 运行时 typesupport
  11. Fast-DDS 上游文档 — Data Sharing、XML profiles、PublishMode

18. 小结

rmw_fastrtps 是 Humble 默认 RMW,采用 三包分层 设计:

  • 对上rmw_fastrtps_cpp / rmw_fastrtps_dynamic_cpp 导出完整 rmw_* API,差异仅在 typesupport 策略;
  • 对中rmw_fastrtps_shared_cpp 集中实现 __rmw_*,避免双倍维护 wait/graph/service 逻辑;
  • 对下:映射到 Fast-DDS 2.6.x + Fast-CDR,利用 SharedMem、Data Sharing、extended RPC 等 eProsima 扩展;
  • 横向:通过 rmw_dds_common 统一 ROS graph,与 Cyclone/Connext 工具链对齐。

选用 rmw_fastrtps_dynamic_cpp 可换运行时 introspection,但生产环境通常保留默认 rmw_fastrtps_cpp 以获得编译期 typesupport 性能。高级调优依赖 RMW_FASTRTPS_* 环境变量FASTRTPS_DEFAULT_PROFILES_FILE XML 组合,而非仅 ROS QoS API。

rmw_implementation 源码详细分析

rmw_implementation 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rmw_implementation
子包数量:2


1. 定位

rmw 实现代理包RMW_IMPLEMENTATION 环境变量或 ament 默认选择具体 rmw 后端,对上层透明链接。


2. 子包列表

包名 版本 说明
rmw_implementation 2.8.5 Proxy implementation of the ROS 2 Middleware Interface.
test_rmw_implementation 2.8.5 Test suite for ROS middleware API.

3. 核心组件

rmw_implementationtest_rmw_implementation。CMake 导出 get_default_rmw_implementation()


4. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


5. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

6. 小结

rmw_implementation 为含 2 个子包的源码树,是 ROS 2 Humble 发行版的一部分。

rmw 源码详细分析

rmw 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rmw
版本:6.1.2(Humble),子包 2 个,构建类型 ament_cmake,语言 C,许可证 Apache 2.0

rmw(ROS Middleware Interface)是 ROS 2 中间件抽象层:在 RCL 与具体 DDS 实现之间定义统一的 C API 契约rcl / rclcpp / rclpy 只依赖 rmw.h 中的符号名,运行时通过 rmw_implementation 代理库链接到 rmw_cyclonedds_cpprmw_fastrtps_cpp 等后端。

本仓库的关键定位rmw 包提供 头文件 API 声明 + 与实现无关的公共工具实现(校验、QoS 字符串、init 零初始化、graph 辅助结构体等)。rmw_create_publisherrmw_waitrmw_init核心中间件操作在本仓库仅声明,实现在各 rmw_*_cpp 包中

设计文档参考:ROS Middleware Interface


1. 总体认识

1.1 核心职责

能力 说明
API 契约 rmw.h 及 50+ 头文件定义 init/node/pub/sub/service/client/wait/graph 等全部 C 接口
公共类型 rmw_*_t 句柄、QoS 枚举/结构、GID、message_info、wait 容器
共享实现 20 个 .c 源文件:命名校验、QoS 字符串、init_options 默认值、topic_endpoint_info 等
构建辅助 rmw_implementation_cmake 提供 CMake 宏,选择/枚举 RMW 实现
实现注册 register_rmw_implementation() 向 ament index 注册 typesupport 资源

1.2 在 ROS 2 栈中的位置

语言客户端RCL 层rmw 仓库独立仓库 - 实际 DDS 调用DDS 厂商rclcpprclpyrclrmw 包\n(API + 公共工具)rmw_implementation_cmakermw_implementation\n符号转发代理rmw_cyclonedds_cpprmw_fastrtps_cpprmw_connextddsrmw_dds_commonCycloneDDSFast-DDSConnext
层级 职责边界
rcl ROS 语义:namespace 展开、remap、timer、wait_set 组装、参数 CLI
rmw(本仓库) 中间件无关的类型与 API 签名;部分校验/工具函数
rmw_implementation 编译期链接所选后端;运行时 dlopen 或直接链接
rmw_*_cpp rmw_* 映射到 DDS Participant/DataWriter/DataReader 等

1.3 API 声明 vs 实现分离

这是阅读 rmw 源码时最容易混淆的一点:

类别 位置 示例
仅声明(实现在后端) include/rmw/rmw.hinit.hinit_options.hevent.h rmw_initrmw_create_publisherrmw_waitrmw_take
本仓库实现 src/*.c(20 文件) rmw_validate_full_topic_namermw_qos_reliability_policy_to_strrmw_get_zero_initialized_init_options
头文件 + 零/辅助实现 types.h + src/types.c rmw_get_zero_initialized_message_info

CMakeLists.txtadd_library(rmw ...) 只编译 20 个 .c 文件,不包含任何 DDS 调用。链接 librmw.so 的应用若直接调用 rmw_init 会得到 undefined symbol,必须通过 rmw_implementation 或具体后端包提供实现。


2. 子包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rmw/
├── rmw/ # 核心 API 包 ★
│ ├── include/rmw/ # 51 个头文件
│ │ ├── rmw.h # 主 API(~3214 行)
│ │ ├── types.h # 核心句柄与 QoS 类型
│ │ ├── init.h / init_options.h # 生命周期
│ │ ├── get_*.h # Graph introspection
│ │ ├── events_statuses/ # QoS/存活事件状态结构
│ │ └── impl/cpp/ # C++ 辅助(demangle 等)
│ ├── src/ # 20 个 .c(~10K 行含注释)
│ └── cmake/ # configure_rmw_library、register_rmw_implementation
└── rmw_implementation_cmake/ # CMake 宏包
└── cmake/
├── get_available_rmw_implementations.cmake
└── get_default_rmw_implementation.cmake
版本 职责
rmw 6.1.2 API 头文件 + 公共 C 工具库
rmw_implementation_cmake 构建时枚举/选择 RMW 实现

2.1 依赖关系(rmw/package.xml

1
2
3
rmw
├── rcutils # 错误链、分配器、日志级别映射、字符校验
└── rosidl_runtime_c # typesupport 相关类型(build_export)

运行时 依赖任何 DDS 库;DDS 依赖在各 rmw_*_cpp 包中。


3. 句柄设计模式

几乎所有 RMW 实体采用相同的 type-erased handle 模式:

1
2
3
4
5
6
7
typedef struct rmw_publisher_s {
const char * implementation_identifier; // 如 "rmw_cyclonedds_cpp"
void * data; // 后端私有结构指针
const char * topic_name; // ROS 图可见名
rmw_publisher_options_t options;
bool can_loan_messages;
} rmw_publisher_t;
字段 作用
implementation_identifier 防止混用不同后端的句柄;不匹配时返回 RMW_RET_INCORRECT_RMW_IMPLEMENTATION
data 指向后端内部对象(如 CycloneDDS 的 dds_entity_t 封装)
其余字段 ROS/RMW 层可见的元数据,由创建函数填充

同类结构:rmw_node_trmw_subscription_trmw_service_trmw_client_trmw_guard_condition_trmw_wait_set_trmw_event_t

3.1 分配器辅助(src/allocators.c

后端实现创建实体时可复用本仓库提供的 句柄内存分配

  • rmw_node_allocate() / rmw_node_free()
  • rmw_publisher_allocate() / rmw_publisher_free()
  • 以及 subscription、service、client、guard_condition、wait_set 的对称函数

底层使用 rcutils 默认分配器,保证各实现一致的句柄布局。


4. 生命周期:init / context / shutdown

4.1 核心类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// init_options.h — 传入 rmw_init() 的配置
typedef struct rmw_init_options_s {
uint64_t instance_id;
const char * implementation_identifier;
size_t domain_id; // RMW_DEFAULT_DOMAIN_ID = 0
rmw_security_options_t security_options;
rmw_localhost_only_t localhost_only;
char * enclave; // SROS2 密钥库路径名
rmw_init_options_impl_t * impl; // 后端私有
rcutils_allocator_t allocator;
} rmw_init_options_t;

// init.h — rmw_init() 成功后持有
typedef struct rmw_context_s {
uint64_t instance_id;
const char * implementation_identifier;
rmw_init_options_t options;
size_t actual_domain_id;
rmw_context_impl_t * impl;
} rmw_context_t;

4.2 本仓库提供的零初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/init.c
rmw_context_t rmw_get_zero_initialized_context(void) {
return (const rmw_context_t){ .instance_id = 0, .impl = NULL };
}

// src/init_options.c
rmw_init_options_t rmw_get_zero_initialized_init_options(void) {
return (const rmw_init_options_t){
.domain_id = RMW_DEFAULT_DOMAIN_ID,
.localhost_only = RMW_LOCALHOST_ONLY_DEFAULT,
.security_options = rmw_get_default_security_options(),
// ...
};
}

4.3 实现侧必须提供的函数(仅声明)

函数 说明
rmw_init_options_init / fini / copy / set_domain_id / set_enclave 配置 init 选项
rmw_init 初始化 DDS participant 工厂等
rmw_shutdown 关闭 context,释放 impl
rmw_context_fini 销毁 context 结构

rcl_init() 调用链:rcl_initrmw_init_options_init → 设置 enclave/domain → rmw_init → 保存 rmw_context_trcl_context_t


5. rmw.h 主 API 分组

rmw/include/rmw/rmw.h 是单一入口,按功能可划分为:

分组 主要 API 说明
标识 rmw_get_implementation_identifierrmw_get_serialization_format 返回后端名字与序列化格式(如 "cdr"
Node rmw_create_nodermw_destroy_nodermw_node_get_graph_guard_condition DDS participant 上的逻辑节点
Publisher create/destroyrmw_publishrmw_publish_loaned_messagermw_publish_serialized_message 发布路径
Subscription create/destroyrmw_take* 系列、rmw_take_sequence 订阅与批量 take
Loaned message rmw_borrow_loaned_messagermw_return_loaned_message_from_* 零拷贝(实现可选)
Serialize rmw_serializermw_deserializermw_get_serialized_message_size 与 rosidl typesupport 配合
Service/Client create/destroyrmw_take_requestrmw_send_responsermw_send_requestrmw_take_response DDS RPC 映射
Wait rmw_create_wait_setrmw_waitrmw_destroy_wait_set 多路复用等待
Guard condition rmw_create_guard_conditionrmw_trigger_guard_condition 唤醒 wait(timer、graph 等)
Graph rmw_get_node_namesrmw_count_publishers/subscribersrmw_get_gid_for_publisher 发现与 introspection
QoS 查询 rmw_publisher_get_actual_qosrmw_subscription_get_actual_qos 协商后的实际 QoS
Liveliness rmw_node_assert_livelinessrmw_publisher_assert_liveliness 手动断言存活
Callback rmw_subscription_set_on_new_message_callback 替代 wait 的异步通知(可选)
Event rmw_event_set_callback QoS 不兼容、deadline 等事件
其他 rmw_compare_gids_equalrmw_service_server_is_availablermw_set_log_severity 工具与日志

Graph 相关 API 还分散在独立头文件中(见第 8 节)。


6. QoS 体系

6.1 rmw_qos_profile_t

1
2
3
4
5
6
7
8
9
10
11
typedef struct rmw_qos_profile_s {
enum rmw_qos_history_policy_e history; // KEEP_LAST / KEEP_ALL
size_t depth;
enum rmw_qos_reliability_policy_e reliability; // RELIABLE / BEST_EFFORT
enum rmw_qos_durability_policy_e durability; // VOLATILE / TRANSIENT_LOCAL
struct rmw_time_s deadline;
struct rmw_time_s lifespan;
enum rmw_qos_liveliness_policy_e liveliness;
struct rmw_time_s liveliness_lease_duration;
bool avoid_ros_namespace_conventions; // 跳过 rt 前缀
} rmw_qos_profile_t;

时间字段使用 RMW_DURATION_UNSPECIFIED / RMW_DURATION_INFINITE 表示“用实现默认”或“无限”。

6.2 预设 Profile(qos_profiles.h

常量 典型用途
rmw_qos_profile_sensor_data 传感器:BEST_EFFORT,depth=5
rmw_qos_profile_default 普通 topic:RELIABLE,depth=10
rmw_qos_profile_parameters 参数服务:RELIABLE,depth=1000
rmw_qos_profile_services_default Service:RELIABLE
rmw_qos_profile_parameter_events 参数事件
rmw_qos_profile_system_default 全部 SYSTEM_DEFAULT
rmw_qos_profile_unknown 未知/未设置

rcl 层将这些 profile 映射到 rcl_qos_profile_t,再传给 rmw_create_publisher

6.3 QoS 字符串转换(src/qos_string_conversions.c

本仓库 完整实现 policy 枚举 ↔ 字符串互转,供 CLI、日志、测试使用:

  • rmw_qos_reliability_policy_to_str / _from_str
  • rmw_qos_durability_policy_to_str / _from_str
  • rmw_qos_history_policy_to_str / _from_str
  • rmw_qos_liveliness_policy_to_str / _from_str
  • rmw_qos_policy_kind_to_str / rmw_qos_policy_kind_from_str

7. Wait Set 与 rmw_wait 契约

7.1 相关类型

1
2
3
4
5
6
7
8
9
10
typedef struct rmw_subscriptions_s {
size_t subscriber_count;
void ** subscribers; // 指向 rmw_subscription_t->data
} rmw_subscriptions_t;

typedef struct rmw_wait_set_s {
const char * implementation_identifier;
rmw_guard_conditions_t * guard_conditions;
void * data;
} rmw_wait_set_t;

还有对称的 rmw_services_trmw_clients_trmw_events_trmw_guard_conditions_t

7.2 rmw_wait 语义(实现在后端)

1
2
3
4
5
6
7
8
rmw_ret_t rmw_wait(
rmw_subscriptions_t * subscriptions,
rmw_guard_conditions_t * guard_conditions,
rmw_services_t * services,
rmw_clients_t * clients,
rmw_events_t * events,
rmw_wait_set_t * wait_set,
const rmw_time_t * wait_timeout);

契约要点(摘自 rmw.h 文档):

  1. 调用前:数组中非 NULL 条目表示要等待的实体;NULL 条目无效。
  2. 调用后:未触发的实体对应数组项被设为 NULL;触发的保留原指针。
  3. 返回值:RMW_RET_OK(有就绪)、RMW_RET_TIMEOUT(超时)、RMW_RET_INVALID_ARGUMENT
  4. 线程安全:同一 wait_set 不可并发 wait;同一实体不可被多个 wait_set 并发 wait。
  5. 所有实体须属于与 wait_set 相同的 rmw_context

7.3 rcl 层如何使用

1
2
3
4
5
6
7
8
rcl_wait_set_t
├── rmw_subscriptions_t ← 从 rcl_subscription_t 收集
├── rmw_guard_conditions_t ← timer、graph、用户 guard
├── rmw_services_t / rmw_clients_t
└── rmw_events_t

rcl_wait() → rmw_wait() → 后端 DDS WaitSet / waitset
返回后 rcl 扫描 NULL 位,标记哪些 subscription/timer 就绪

8. Graph Introspection API

rmw.h 中的 rmw_get_node_namesrmw_count_publishers 外,专用头文件定义更丰富的图 API:

头文件 API 用途
get_topic_names_and_types.h rmw_get_topic_names_and_types 全局 topic 列表
get_service_names_and_types.h rmw_get_service_names_and_types 全局 service 列表
get_node_info_and_types.h rmw_get_*_names_and_types_by_node 按节点查 pub/sub/service/client
get_topic_endpoint_info.h rmw_get_publishers_info_by_topicrmw_get_subscriptions_info_by_topic endpoint 级 QoS/GID
get_network_flow_endpoints.h rmw_publisher_get_network_flow_endpoints 网络流信息(调试用)

辅助数据结构在本仓库实现:

  • rmw_names_and_types_tsrc/names_and_types.c(init/fini/check_zero)
  • rmw_topic_endpoint_info_tsrc/topic_endpoint_info.c(set topic_type、gid、qos 等)
  • rmw_topic_endpoint_info_array_tsrc/topic_endpoint_info_array.c

9. 命名校验(本仓库完整实现)

ROS 2 图命名规则在 RMW 层统一校验,供 rcl 与后端共用:

模块 文件 规则摘要
Topic validate_full_topic_name.c 必须以 / 开头、不以 / 结尾、仅 [a-zA-Z0-9_/]
Namespace validate_namespace.c 类似 topic,允许空 namespace
Node name validate_node_name.c 不含 /,字母数字与 _

每个校验函数返回 RMW_RET_OK + validation_result 枚举(非错误码),并提供 *_validation_result_string() 人类可读描述。

示例(topic 校验入口):

1
2
3
4
rmw_ret_t rmw_validate_full_topic_name(
const char * topic_name,
int * validation_result,
size_t * invalid_index);

10. 事件(Events)

10.1 事件类型(event.h

枚举 方向 含义
RMW_EVENT_REQUESTED_QOS_INCOMPATIBLE Subscription 请求 QoS 与 offer 不兼容
RMW_EVENT_OFFERED_QOS_INCOMPATIBLE Publisher 提供 QoS 与 request 不兼容
RMW_EVENT_LIVELINESS_CHANGED / LOST Sub / Pub 存活策略变化
RMW_EVENT_*_DEADLINE_MISSED Sub / Pub 错过 deadline
RMW_EVENT_MESSAGE_LOST Subscription 消息丢失(实现相关)

10.2 状态结构(events_statuses/

incompatible_qos.hliveliness_changed.h 等定义 DDS 事件计数的 C 结构,与 rmw_take_event(后端实现)配合。

10.3 本仓库实现

  • rmw_get_zero_initialized_event()src/event.c
  • rmw_event_fini() — 重置为零结构(不释放 impl,impl 由后端管理)

rmw_publisher_event_initrmw_subscription_event_initrmw_take_event仅声明


11. 安全与网络选项

11.1 Security(src/security_options.c

1
2
3
4
rmw_security_options_t rmw_get_default_security_options(void);
rmw_ret_t rmw_security_options_set_root_path(...);
rmw_ret_t rmw_security_options_copy(...);
rmw_ret_t rmw_security_options_fini(...);

与 SROS2 集成:enclave + security_root_path 指向密钥库。具体 DDS Security 插件加载在各后端。

11.2 Localhost / Domain

  • domain_id.hRMW_DEFAULT_DOMAIN_ID(0),隔离不同 ROS 域
  • localhost.hRMW_LOCALHOST_ONLY_DEFAULT 等,限制仅本机通信

12. 消息路径辅助类型

类型 文件 说明
rmw_serialized_message_t serialized_message.h 字节缓冲区 + 分配器
rmw_message_info_t types.h source_timestamp、publication_handle、sequence_number
rmw_message_sequence_t message_sequence.c 批量 take 的消息数组
rmw_request_id_t types.h Service 关联 writer_guid + sequence_number

rmw_get_zero_initialized_message_info()src/types.c 实现。

12.1 Loaned Message

API 在 rmw.h 中声明;can_loan_messages 标志由 rmw_create_publisher/subscription 设置。是否支持取决于后端与 typesupport(Fast-DDS 支持较好)。

12.2 Serialize / Deserialize

1
2
3
4
5
6
7
8
9
rmw_ret_t rmw_serialize(
const void * ros_message,
const rosidl_message_type_support_t * type_support,
rmw_serialized_message_t * serialized_message);

rmw_ret_t rmw_deserialize(
const rmw_serialized_message_t * serialized_message,
const rosidl_message_type_support_t * type_support,
void * ros_message);

实现通常委托给 rosidl_typesupport_* 的 CDR 序列化,再经 DDS 发送。


13. 错误处理

error_handling.h 薄封装 rcutils 线程局部错误链:

1
2
3
4
5
6
#define RMW_SET_ERROR_MSG(msg) RCUTILS_SET_ERROR_MSG(msg)
#define RMW_SET_ERROR_MSG_WITH_FORMAT_STRING(...) \
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(__VA_ARGS__)
rmw_error_string_t rmw_get_error_string(void);
bool rmw_error_is_set(void);
void rmw_reset_error(void);

13.1 返回码(ret_types.h

含义
RMW_RET_OK 0 成功
RMW_RET_ERROR 1 通用错误(查 error string)
RMW_RET_TIMEOUT 2 wait 超时
RMW_RET_UNSUPPORTED 3 不支持的操作
RMW_RET_BAD_ALLOC 10 内存分配失败
RMW_RET_INVALID_ARGUMENT 11 无效参数
RMW_RET_INCORRECT_RMW_IMPLEMENTATION 12 句柄与当前后端不匹配

src/convert_rcutils_ret_to_rmw_ret.c 提供 rmw_convert_rcutils_ret_to_rmw_ret(),供后端统一转换 rcutils 返回值。

13.2 断言宏(sanity_checks.c / macros.h

RMW_CHECK_ARGUMENT_FOR_NULLRMW_CHECK_TYPE_IDENTIFIERS_MATCH 等在后端实现中广泛使用,保证跨实现一致的错误报告。


14. 特性查询(features.h

1
2
3
4
5
6
typedef enum rmw_feature_e {
RMW_FEATURE_MESSAGE_INFO_PUBLICATION_SEQUENCE_NUMBER,
RMW_FEATURE_MESSAGE_INFO_RECEPTION_SEQUENCE_NUMBER,
} rmw_feature_t;

bool rmw_feature_supported(rmw_feature_t feature); // 后端实现

用于上层判断 rmw_message_info_t 中 sequence number 字段是否可靠填充。


15. src/ 模块一览

源文件 实现的公开 API
allocators.c rmw_*_allocate/freermw_allocate/free
convert_rcutils_ret_to_rmw_ret.c rcutils → rmw 返回码映射
event.c event 零初始化、fini
init.c rmw_get_zero_initialized_context
init_options.c rmw_get_zero_initialized_init_options
message_sequence.c message / message_info sequence init/fini
names_and_types.c rmw_names_and_types_*
network_flow_endpoint.c 网络 endpoint 零初始化、set address
network_flow_endpoint_array.c endpoint 数组管理
publisher_options.c rmw_get_default_publisher_options
subscription_options.c rmw_get_default_subscription_options
subscription_content_filter_options.c Content Filter Topic 选项
qos_string_conversions.c QoS 枚举 ↔ 字符串
sanity_checks.c string array 零检查
security_options.c security options 生命周期
time.c rmw_time_equalrmw_time_from_nsec
topic_endpoint_info.c endpoint info 字段设置
topic_endpoint_info_array.c endpoint 数组
types.c rmw_get_zero_initialized_message_info
validate_*.c topic/namespace/node 名校验

16. rmw_implementation_cmake

16.1 枚举可用实现

get_available_rmw_implementations(var)

  1. 从 ament index 读取 rmw_typesupport 资源(排除 rmw_implementation 代理包本身)
  2. 可通过环境变量 RMW_IMPLEMENTATIONS 或 CMake 变量过滤
  3. 结果写入 ${var} 列表

16.2 选择默认实现

get_default_rmw_implementation(var)

  1. 若未设置 RMW_IMPLEMENTATION(CMake 或环境变量):
    • 优先 rmw_fastrtps_cpp
    • 否则取字母序第一个
  2. 若已设置:验证其在可用列表中,find_package 该包
  3. Humble 上默认编译链接 Fast-DDS不是 CycloneDDS(除非显式设置 RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

16.3 注册实现(register_rmw_implementation.cmake

各后端 CMakeLists.txt 调用:

1
2
3
register_rmw_implementation(
"c:rosidl_typesupport_introspection_c"
"cpp:rosidl_typesupport_introspection_cpp")

向 ament index 写入:

  • rmw_typesupport — 所有 typesupport 包名
  • rmw_typesupport_c / rmw_typesupport_cpp — 分语言列表

17. 构建与导出

cmake/configure_rmw_library.cmake 设置:

  • RMW_BUILDING_DLL(Windows 导出)
  • 默认 symbol visibility hidden,仅 RMW_PUBLIC 导出

rmw-extras.cmake + ament_export_* 导出 librmw 与 include 路径。


18. 数据路径(端到端)

18.1 发布

DDS DataWriterrmw_implementationrcl用户代码DDS DataWriterrmw_implementationrcl用户代码typesupport 序列化(若需要)rcl_publish(msg)rmw_publish(pub, msg, alloc)dds_write / write

18.2 订阅

1
2
3
4
rmw_take(sub, msg, &taken, alloc)
→ 后端 DataReader take
→ rosidl 反序列化到 ros_message
→ 填充 rmw_message_info_t(时间戳、GID 等)

变体:rmw_take_with_informw_take_serialized_messagermw_take_loaned_messagermw_take_sequence

18.3 等待

1
2
3
4
5
6
rcl_wait(wait_set, timeout)
→ 组装 rmw_subscriptions_t / guard_conditions / services / clients / events
→ rmw_wait(...)
→ 后端 waitset wait
→ 未就绪句柄置 NULL
→ rcl 标记对应实体 ready

18.4 Service

1
2
3
Client: rmw_send_request  → DDS request writer
Server: rmw_take_request → 处理 → rmw_send_response
Client: rmw_take_response

19. 与 rcl 的职责对比

主题 rcl rmw
Namespace / remap ✅ 展开 ~/foo → 绝对名 接收最终字符串
QoS 默认值 rcl_qos_profile_* rmw_qos_profile_*
Wait set 组装 ✅ timer → guard_condition 执行底层 wait
参数 / YAML ✅ CLI 解析
日志 ✅ rcl_logging rmw_set_log_severity 转发 DDS 日志
命名校验 调用 rmw_validate_* ✅ 实现校验逻辑
DDS 实体 ✅ 后端实现

20. 相关仓库

仓库/包 关系
rmw_implementation 代理库;RMW_IMPLEMENTATION 环境变量在运行时切换(若构建支持)
rmw_cyclonedds_cpp CycloneDDS 后端
rmw_fastrtps_cpp Fast-DDS 后端;Humble 默认
rmw_connextdds RTI Connext 后端
rmw_dds_common 多后端共享:graph 缓存、GID、demangle
rosidl_typesupport_* 消息序列化;与 rmw_serialize 配合
rcl RMW 主要调用方

21. 推荐阅读顺序

  1. rmw/types.h — 掌握所有句柄与 QoS 类型(~650 行)
  2. rmw/init.h + init_options.h — 理解 context 生命周期契约
  3. rmw/rmw.h — 按 pub/sub/wait/graph 分段阅读(不必一次读完 3200 行)
  4. rmw/src/validate_full_topic_name.c — 看本仓库“有实现”代码的风格
  5. rmw_implementation_cmake/get_default_rmw_implementation.cmake — 理解构建默认后端
  6. rmw_implementation 仓库 — 符号如何转发到具体后端
  7. 任选一后端 — 如 rmw_cyclonedds_cpprmw_create_publisher / rmw_wait 实现
  8. rmw_dds_common — graph 与 GID 共享逻辑

22. 小结

rmw 仓库是 ROS 2 可插拔中间件契约层 + 公共工具库

  • 对上:为 rcl 提供稳定 C ABI 与一致的类型定义;
  • 对下:不绑定任何 DDS,具体通信由 rmw_*_cpp 实现;
  • 本仓库源码价值:理解 ROS 2 中间件 接口设计(句柄、wait 语义、QoS、graph API)以及 跨实现共享逻辑(命名校验、QoS 字符串、init 默认值);
  • 调试通信问题:沿 rcl_ret_trmw_ret_trmw_get_error_string() 向下追至具体后端。

换 DDS 只需安装并选择对应 rmw_*_cpp 实现,rcl / rclcpp / rclpy 无需修改。

ros2_tracing 源码详细分析

ros2_tracing 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/ros2_tracing
版本:4.1.2(Humble),子包 8 个,许可证 Apache 2.0tracetools 质量等级 QL1

ros2_tracing 是 ROS 2 的 LTTng 追踪框架:在 rcl / rclcpp / rmw 等核心栈插入 tracepoint,提供会话配置工具(CLI / launch),并支持用 Babeltrace 读取 CTF 轨迹做测试与分析。

平台限制:当前 仅支持 Linux + LTTng(LTTng-UST 用户态追踪;可选内核追踪需 lttng-modules-dkms)。
分析/visualization 在独立仓库 tracetools_analysis


1. 总体认识

1.1 核心职责

能力 实现包 说明
Instrumentation API tracetools TRACEPOINT() 宏 + LTTng TRACEPOINT_EVENT 定义
会话配置 tracetools_trace Python 封装 LTTng session/channel/event
CLI 入口 ros2trace ros2 trace 子命令
Launch 集成 tracetools_launch Trace action + LD_PRELOAD 辅助库
轨迹读取 tracetools_read Babeltrace → Python dict
测试框架 tracetools_test TraceTestCase 自动化 trace 验证
自测 test_tracetools / test_tracetools_launch 覆盖各 tracepoint 与 launch action

1.2 在 ROS 2 栈中的位置

应用 / rclcpp 节点已插桩核心栈ros2_tracing 仓库LTTng 栈Node / Executor / Callbacksrclcpprclrmw_*tracetools\nlibtracetools.sotracetools_trace\nLTTng Python APIros2trace\nros2 tracetracetools_launch\nTrace actiontracetools_read\nbabeltraceLTTng-UST\n用户态 tracepointlttng-sessiondCTF 轨迹文件

关键设计:插桩代码 始终链接 tracetools;是否产生轨迹取决于 运行时 是否启动 LTTng session 并 enable 对应 event。未配置 session 时 tracepoint 开销极低(LTTng-UST fast path 几乎无系统调用)。

1.3 数据流概览

1
2
3
4
5
6
7
8
9
10
11
编译期:
rclcpp/rcl/rmw 源码 #include tracetools/tracetools.h
→ 链接 libtracetools.so
→ tp_call.h 生成 LTTng provider "ros2" 的 TRACEPOINT_EVENT

运行期:
ros2 trace / Trace launch action
→ lttng.create + enable events (ros2:rcl_publish 等)
→ lttng.start
→ 用户启动节点 → tracepoint 写入 UST buffer → CTF 文件
→ tracetools_read / tracetools_analysis 解析

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
ros2_tracing/
├── README.md # 构建、ros2 trace、launch 示例、实时性说明
├── doc/design_ros_2.md # ★ 设计文档:tracepoint 语义、分层表
├── tracing.repos # 可选 vendoring
├── tracetools/ # ★ C/C++ 插桩库(QL1)
├── tracetools_trace/ # LTTng session 管理
├── tracetools_launch/ # Launch Trace action
├── tracetools_read/ # Babeltrace 读取
├── tracetools_test/ # TraceTestCase 测试基类
├── ros2trace/ # ros2cli 扩展
├── test_tracetools/ # ping/pong 等可执行 + Python 单测
└── test_tracetools_launch/ # launch 集成测试
构建类型 源码规模(约) 职责
tracetools ament_cmake ~1595 行 C/C++ 宏、LTTng event 定义、符号解析
tracetools_trace ament_python ~1190 行 Python lttng_impl.setup/start/stop
tracetools_launch ament_python ~654 行 Python Trace + LdPreload
tracetools_read ament_python ~189 行 Python CTF → DictEvent
tracetools_test ament_python case/utils 测试 harness
ros2trace ament_python ~126 行 Python CLI 薄包装
test_tracetools ament_cmake + pytest ~966 行 系统测试
test_tracetools_launch ament_python launch 测试

3. 包 tracetools:插桩核心

3.1 构建与开关

CMakeLists.txt 关键选项:

CMake 选项 效果
(默认)检测到 lttng-ust TRACETOOLS_LTTNG_ENABLED=ON,编译 tp_call.c,链接 LTTng
TRACETOOLS_DISABLED=ON 宏变为空操作;libtracetools 变为 INTERFACE 库(仅头文件)
TRACETOOLS_NO_RDYNAMIC 不导出 -rdynamic(影响回调符号解析)
TRACETOOLS_STATUS_CHECKING_TOOL 构建 ros2 run tracetools status 工具

Windows 上默认 TRACETOOLS_DISABLED=ON

检测逻辑:

1
2
3
4
5
pkg_check_modules(LTTNG lttng-ust)
if(LTTNG_FOUND)
set(TRACETOOLS_LTTNG_ENABLED TRUE)
endif()
configure_file(config.h.in → config.h) # TRACETOOLS_DISABLED / TRACETOOLS_LTTNG_ENABLED

运行时检查:

1
2
3
ros2 run tracetools status
# Tracing enabled ← TRACETOOLS_LTTNG_ENABLED 且未 DISABLED
# Tracing disabled

3.2 三层头文件

头文件 职责
tracetools.h 对外 TRACEPOINT() / DECLARE_TRACEPOINT() 宏;全部 event 函数声明
tp_call.h LTTng TRACEPOINT_EVENT 定义(provider ros2);CTF 字段布局
utils.hpp tracetools::get_symbol() — 从 std::function/lambda 解析 demangled 符号

3.3 TRACEPOINT 宏机制

启用 LTTng 时,调用链为:

1
2
3
TRACEPOINT(rcl_publish, publisher_handle, message);
ros_trace_rcl_publish(...) // tracetools.c 中实现
tracepoint(ros2, rcl_publish, ...) // LTTng UST

禁用时:

1
#define TRACEPOINT(...) ((void)(0))

tracetools.cTRACETOOLS_LTTNG_ENABLED#include "tracetools/tp_call.h",用 CONDITIONAL_TP 包装每个函数。

3.4 LTTng Event 命名

Python 侧 tracepoints.py 与 LTTng 注册名一致:

1
2
3
4
5
6
ros2:rcl_init
ros2:rcl_node_init
ros2:rmw_publisher_init
...
ros2:callback_start
ros2:rclcpp_executor_execute

28 个 ROS tracepoint(Humble),覆盖 init / pub-sub / service / timer / lifecycle / executor / callback。

3.5 Tracepoint 分层与关联字段

设计目标(见 doc/design_ros_2.md)是用 指针 handle 在层间关联对象,用 message 指针 跟踪消息在 rclcpp→rcl→rmw 的传播。

层级 初始化类 tracepoint 运行时类 tracepoint
rmw rmw_publisher_init(含 GID), rmw_subscription_init rmw_publish, rmw_take(含 source_timestamp, taken)
rcl rcl_init, rcl_node_init, rcl_publisher_init, rcl_subscription_init, rcl_service_init, rcl_client_init, rcl_timer_init, lifecycle rcl_publish, rcl_take
rclcpp rclcpp_subscription_init, *_callback_added, rclcpp_timer_link_node, rclcpp_callback_register rclcpp_publish/take, callback_start/end, executor 三件套

Executor 相关

  • rclcpp_executor_get_next_ready — 开始取 ready executable
  • rclcpp_executor_wait_for_workwait 阶段(带 timeout ns)
  • rclcpp_executor_execute — 执行某 rcl handle(timer/subscription)

Callback 相关

  • rclcpp_callback_register — 注册 demangled 符号(需 -rdynamic
  • callback_start / callback_end — 回调边界;is_intra_process 标记 intra-process

3.6 符号解析(-rdynamic

utils.cpp 通过 dladdr + abi::__cxa_demangle 解析回调函数名,供 rclcpp_callback_register 写入 trace。

CMake 在 LTTng 启用时默认:

1
2
target_link_libraries(tracetools "-rdynamic")
ament_export_link_flags("-rdynamic")

若链接时去掉 -rdynamic,符号可能变为 UNKNOWN

3.7 下游插桩位置(核心栈,非本仓库)

插桩散布在 ROS 2 核心包,例如:

典型文件 tracepoint
rclcpp subscription.hpp, timer.hpp, executor.cpp callback_*, executor, publish/take
rcl rcl/publisher.c, rcl/subscription.c rcl_publish, rcl_take, init
rmw_fastrtps rmw_publish.cpp, rmw_take.cpp rmw_publish, rmw_take, init

所有上述包 package.xmlexec_depend/build_depend tracetools


4. 包 tracetools_trace:LTTng 会话管理

4.1 模块结构

1
2
3
4
5
6
7
8
9
10
11
tracetools_trace/
├── trace.py # init/fini 入口(交互式 press enter)
└── tools/
├── lttng.py # 门面:is_lttng_installed, lttng_init/fini
├── lttng_impl.py # ★ setup/start/stop/destroy
├── lttng_stub.py # 无 lttng 模块时的 stub
├── names.py # DEFAULT_EVENTS_ROS/KERNEL, DEFAULT_CONTEXT
├── tracepoints.py # ros2:* 字符串常量
├── args.py # CLI 参数解析
├── path.py # ROS_TRACE_DIR / ~/.ros/tracing
└── signals.py # SIGINT 时 fini session

4.2 lttng_impl.setup() 流程

  1. 若无 lttng-sessiondlttng-sessiond --daemonize
  2. 若启用 kernel events → lttng list -k 检查内核追踪器
  3. lttng.create(session_name, full_path) — 输出 CTF 目录
  4. 创建 UST domainBUFFER_PER_UID)+ channel ros2
  5. 可选 KERNEL domain + channel kchan
  6. _enable_events — 每个 event 类型 EVENT_TRACEPOINT
  7. _add_context — pid/tid/procname 等

Channel 调优(偏实时友好,见 README):

参数 UST Kernel
overwrite 0(discard) 0
subbuf_size 8×4096 32×4096
num_subbuf 2 2
read_timer_interval 200 ms 200 ms
switch_timer 0(禁用) 0

4.3 默认启用事件

names.DEFAULT_EVENTS_ROS = tracepoints.py 中全部 28 个 ROS event。

DEFAULT_EVENTS_KERNELros2 trace -k 时可追加)示例:

  • sched_switch, kmem_mm_page_*, power_cpu_frequency

4.4 轨迹目录

优先级(README 与 path.get_tracing_directory()):

  1. $ROS_TRACE_DIR(非空)
  2. $ROS_HOME/tracingROS_HOME 默认 ~/.ros

Session 名默认 session-YYYYMMDDHHMMSSpath.append_timestamp)。


5. 包 ros2traceros2 trace CLI

1
2
3
4
5
# ros2trace/command/trace.py
class TraceCommand(CommandExtension):
def main(...):
init(session_name=..., ros_events=..., kernel_events=..., ...)
fini(session_name=...)

CLI 参数(args.py):

选项 含义
-s/--session-name session 名
-p/--path 基目录
-u/--ust EVENT... UST events(无参数 = 禁用全部 UST)
-k/--kernel EVENT... 内核 events
-c/--context CONTEXT... context 字段
-l/--list 打印 enabled 列表

典型用法:

1
2
3
4
ros2 trace                    # 默认全部 ROS tracepoint
ros2 trace -k sched_switch # 附加内核 event
ros2 trace -u # 仅内核/无 ROS(若再配合 -k)
# 另一终端运行节点,回到 trace 终端按 Enter 停止

trace.pyinit() 在开始前 input('press enter to start...')fini()input('press enter to stop...') 后 destroy session — 交互式 录制。


6. 包 tracetools_launch:Launch 集成

6.1 Trace Action

1
2
3
# example.launch.py
Trace(session_name='my-tracing-session'),
Node(package='test_tracetools', executable='test_ping', ...),

生命周期

  1. execute()_setup()lttng.lttng_init(...) + lttng.start
  2. 注册 OnShutdown_destroy()lttng_fini
  3. 返回 LdPreload 子 action 列表(按需 LD_PRELOAD UST helper)

Launch 前端@expose_action('trace'),支持 Python / XML / YAML(events-ust, events-kernel, context-fields 等)。

6.2 LdPreload 与 UST helper

当 UST event 列表匹配特定 pattern 时自动 preload:

典型 events
liblttng-ust-libc-wrapper.so lttng_ust_libc:malloc
liblttng-ust-pthread-wrapper.so lttng_ust_pthread:*
liblttng-ust-cyg-profile-fast.so 函数 entry/exit(fast)
liblttng-ust-dl.so lttng_ust_dl:*

通过 whereis -b 查找 .so 路径,追加 LD_PRELOAD 环境变量。

6.3 与 ros2 trace 对比

维度 ros2 trace Trace launch action
启停 手动 Enter 随 launch 自动 start/stop
适用 任意已运行进程 与 launch 文件中的 Node 同步
LD_PRELOAD 按 events 自动配置
交互

7. 包 tracetools_read:读取 CTF

1
2
3
4
from tracetools_read.trace import get_trace_events

events = get_trace_events('/path/to/session-xxx')
# List[DictEvent]: {'_name', '_timestamp', ...fields}
  • 使用 Babeltrace 1.x Python API(babeltrace.TraceCollection
  • add_traces_recursive(path, 'ctf')
  • 过滤 CTF 元数据字段(packet_size, stream_id 等)

辅助函数(tracetools_read/__init__.py):get_event_name, get_field, get_procname 等。


8. 包 tracetools_test:测试基础设施

8.1 TraceTestCase

自动化流程(case.py):

  1. run_and_trace() — 创建 LTTng session → 启动指定 package 的 nodes → 停止
  2. get_trace_events() 读 CTF
  3. 断言:event 集合、时间戳、procname、handle 指针有效性、字段类型
  4. tearDown() 删除 trace 目录(除非 TRACETOOLS_TEST_DEBUG 非空)

8.2 test_tracetools 用法示例

1
2
3
4
5
6
7
8
9
class TestPublisher(TraceTestCase):
def __init__(self, *args):
super().__init__(
*args,
session_name_prefix='session-test-publisher',
events_ros=[tp.rcl_publisher_init, tp.rmw_publish, ...],
package='test_tracetools',
nodes=['test_publisher'],
)

覆盖:publisher、subscription、service、timer、executor、lifecycle、intra-process 等场景。


9. 构建与部署

9.1 依赖安装(Ubuntu)

1
2
3
4
sudo apt install lttng-tools liblttng-ust-dev python3-babeltrace python3-lttng
# 可选内核追踪:
sudo apt install lttng-modules-dkms
sudo groupadd -r tracing && sudo usermod -aG tracing $USER

9.2 从源码启用 tracing

若先装了 ROS 二进制再装 LTTng,需重编至少到 tracetools

1
2
3
colcon build --packages-up-to tracetools --cmake-force-configure
source install/setup.bash
ros2 run tracetools status

9.3 完全禁用

1
colcon build --cmake-args "-DTRACETOOLS_DISABLED=ON"

核心栈仍包含 TRACEPOINT 调用,但编译为空操作,不链接 LTTng。


10. 与分析工具的关系

本仓库 不负责 统计/可视化;设计文档指向 tracetools_analysis

  • 读取 CTF / 构建 callback 时长、消息年龄、调度干扰等指标
  • 可与 kernel events(sched_switch 等)联合分析

数据契约即 tp_call.h 中的 CTF 字段 + context 字段。


11. 实时性说明

LTTng-UST 面向生产环境低延迟追踪(README 引用的 RA-L 论文)。默认 channel 配置已采用:

  • discard 模式(不阻塞业务线程写缓冲)
  • read timer 而非 switch timer(减少 write() syscall)
  • 较小 sub-buffer 数量

进一步实时调优(手动 URCU 注册、sub-buffer 大小等)需直接使用 LTTng API;ros2 trace / Trace action 尚未 暴露全部 channel 参数(见 issue #129)。


12. 环境变量与配置

变量 作用
ROS_TRACE_DIR 轨迹基目录(优先于 ~/.ros/tracing
ROS_HOME 默认 ~/.ros,其下 tracing/
TRACETOOLS_TEST_DEBUG 测试后保留 trace 目录
LD_PRELOAD launch 可能追加 LTTng UST helper

13. 扩展插桩指南

在自定义包中添加 tracing(摘自 design doc):

  1. package.xml 添加 depend tracetools
  2. #include "tracetools/tracetools.h"
  3. 在合适位置调用已有 TRACEPOINT(...)
  4. tp_call.h + tracetools.h + tracetools.c 增加新 event(需改 tracetools 包并 release)
  5. tracepoints.py / DEFAULT_EVENTS_ROS 注册 LTTng 名
  6. tracetools_test.TraceTestCase 验证

用户态 generic tracepoint 可直接使用 LTTng lttng_ust_tracepoint API,不必经过 tracetools 宏。


14. 调试建议

  1. 确认编译启用ros2 run tracetools status
  2. 确认 session 在跑lttng list
  3. 无 event:检查 enable 的 event 名是否为 ros2:*;节点是否用带 tracing 的 overlay 构建
  4. 空 trace 目录:session 未 start、或进程 domain 与 session 不匹配
  5. 符号 UNKNOWN:检查 -rdynamic、回调是否为 lambda 且无 target<>
  6. 读 trace 失败:安装 python3-babeltrace,路径是否为 session 根目录
  7. 内核 event 失败:用户是否在 tracing 组、是否加载 lttng-modules

15. 推荐阅读顺序

  1. 仓库 README.md — 构建、CLI、launch 快速上手
  2. doc/design_ros_2.md — tracepoint 语义与分层表
  3. tracetools/include/tracetools/tracetools.h — 全部 API
  4. tracetools/include/tracetools/tp_call.h — CTF 字段定义
  5. tracetools/src/tracetools.c — 宏到 LTTng 的桥接
  6. tracetools_trace/tools/lttng_impl.py — session 创建细节
  7. tracetools_launch/action.py — Launch 生命周期 + LD_PRELOAD
  8. tracetools_test/case.py — 如何写 trace 单测
  9. test_tracetools/test/test_publisher.py — 字段断言示例
  10. 核心栈插桩点rclcpp/executor.cpp, rmw_fastrtps/rmw_publish.cpp

16. 小结

ros2_tracing 将 ROS 2 运行时行为映射为 LTTng CTF 轨迹,形成「插桩 → 会话 → 存储 → 读取/分析」完整链路:

  • tracetools:稳定 C API + LTTng provider ros2,被 rcl/rclcpp/rmw 硬依赖(可编译为空操作);
  • tracetools_trace + ros2trace:交互式 ros2 trace 会话管理;
  • tracetools_launch:与 launch 同生命周期自动 tracing,并智能 LD_PRELOAD
  • tracetools_read / tracetools_test:CTF 消费与回归测试。

Humble 上若仅安装二进制 ROS 而未重编 tracetools,默认 Tracing disabled;性能分析、executor 调度可视化、回调延迟测量等场景,需安装 LTTng 并重编核心栈,再配合 tracetools_analysis 或自定义 Babeltrace 脚本处理 ~/.ros/tracing/session-* 下的 CTF 数据。

ros2cli_common_extensions 源码详细分析

ros2cli_common_extensions 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/ros2cli_common_extensions
子包数量:1


1. 定位

ros2cli_common_extensions 目录含 1 个 ROS 2 包,工作区路径见下。


2. 子包列表

包名 版本 说明
ros2cli_common_extensions 0.1.2 Meta package for ros2cli common extensions

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

5. 小结

ros2cli_common_extensions 为单包仓库,提供 ros2cli_common_extensions 功能。

ros2cli 源码详细分析

ros2cli 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/ros2cli
版本:0.18.18(Humble),子包 15 个,构建类型 ament_python,许可证 Apache 2.0

ros2cli 是 ROS 2 命令行工具框架内置 introspection 命令 的 monorepo:单一 ros2 可执行文件通过 setuptools entry point 插件 扩展为 ros2 topic listros2 node info 等;图查询类命令默认走 后台 daemon + XML-RPC,数据面命令(echo/pub)使用 DirectNode(rclpy)

范围说明:本仓库含 15 个包;ros2 bagros2 launchros2 security 等命令在 其他仓库 注册同一 ros2cli.command 组(见 §12)。ros2cli_common_extensions 仅为依赖聚合 metapackage。


1. 总体认识

1.1 核心职责

能力 实现位置 说明
CLI 框架 ros2cli argparse 分层、entry point 发现、按需加载
插件系统 plugin_system.py / entry_points.py 版本校验、实例化、失败降级
图 introspection NodeStrategy + _ros2_daemon 复用长生命周期 rclpy 节点,加速 list/info
数据面工具 ros2topic/ros2param verb 独立 rclpy 节点、订阅/服务客户端
包/可执行文件 ros2pkg / ros2run ament index、subprocess 启动
健康检查 ros2doctor 可插拔 check/report entry point
Shell 补全 argcomplete + *.completer 可选 python3-argcomplete

1.2 在 ROS 2 栈中的位置

用户ros2cli 仓库_ros2_daemon 进程ROS 2 运行时echo/pubros2 topic echo /chatterros2cli/cli.pyCommandExtension\ntopic/node/...VerbExtension\nlist/echo/pub/...NodeStrategyDirectNodeDaemonNode XML-RPCNetworkAwareNodeLocalXMLRPCServer\n:11511+domain_idrclpyrmw / DDS graph
对比项 ros2cli daemon 路径 DirectNode 路径
进程 独立 _ros2_daemon 每次命令内嵌 rclpy
启动成本 首次 spawn,后续 RPC 快 每次 rclpy.init + spin
适用 get_topic_names_and_types 等图 API echopub、参数服务
端口 11511 + ROS_DOMAIN_ID
禁用 --no-daemon 默认 fallback

1.3 命令行分层模型

1
2
3
ros2                          ← console_scripts: ros2cli.cli:main
└── <command> ← entry point 组 ros2cli.command
└── <verb> ← entry point 组 ros2<cmd>.verb(可选)

示例:

1
2
3
ros2 topic list -t
# command = topic (TopicCommand)
# verb = list (ListVerb)

部分 command 无 verb(扁平结构):

  • ros2 run pkg exe
  • ros2 doctor -r
  • ros2 daemon status

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ros2cli/
├── README.md
├── ros2cli/ # ★ 框架核心 (~2072 行 Python)
│ └── ros2cli/
│ ├── cli.py # main() 入口
│ ├── command/ # CommandExtension、add_subparsers_on_demand
│ ├── plugin_system.py
│ ├── entry_points.py
│ ├── node/ # DirectNode、DaemonNode、NodeStrategy
│ ├── daemon/ # _ros2_daemon 实现
│ ├── xmlrpc/ # RPC 客户端/服务端、rclpy 序列化
│ └── verb/daemon/ # ros2 daemon start|stop|status
├── ros2topic/ # 最大命令包之一
├── ros2node/ ros2service/ ros2param/
├── ros2action/ ros2component/ ros2lifecycle/
├── ros2interface/ ros2doctor/ ros2multicast/
├── ros2pkg/ ros2run/
├── ros2cli_test_interfaces/ # 测试用 msg/srv/action
└── ros2lifecycle_test_fixtures/

3. 子包一览

ros2 命令 verbs / 行为
ros2cli daemon, extension_points, extensions start / stop / status
ros2topic topic bw, delay, echo, find, hz, info, list, pub, type
ros2node node info, list
ros2service service call, find, list, type
ros2param param delete, describe, dump, get, list, load, set
ros2action action info, list, send_goal
ros2component component list, load, standalone, types, unload
ros2lifecycle lifecycle get, list, nodes, set
ros2interface interface list, package, packages, proto, show
ros2doctor doctor, wtf check/report + 可扩展 verb
ros2multicast multicast receive, send
ros2pkg pkg create, executables, list, prefix, xml
ros2run run (无 verb)直接启动可执行文件
ros2cli_test_interfaces 测试接口定义
ros2lifecycle_test_fixtures lifecycle 测试节点

4. 框架包 ros2cli

4.1 入口 cli.py

1
2
3
4
5
6
7
8
9
def main(*, script_name='ros2', argv=None, ...):
parser = argparse.ArgumentParser(...)
add_subparsers_on_demand(parser, 'ros2', '_command', 'ros2cli.command', ...)
args = parser.parse_args(args=argv)
extension = getattr(args, '_command', None)
if extension is None:
parser.print_help()
return 0
return extension.main(parser=parser, args=args)

特性:

  • 默认 行缓冲 stdoutreconfigure(line_buffering=True) 或 patch print(flush=True)
  • 捕获 KeyboardInterrupt → 返回 SIGINT
  • 捕获 ExternalShutdownExceptionSIGTERM
  • 可选 argcomplete 自动补全

4.2 插件发现:entry_points.py

函数 作用
get_entry_points(group_name) 读取 setuptools entry point 组
load_entry_points(group_name) entry_point.load() 得到类
get_all_entry_points() 扫描 ros2cli.extension_point 注册的所有组

扩展点注册:第三方包在 setup.py 中声明:

1
2
3
'ros2cli.extension_point': [
'ros2mytool.verb = ros2mytool.verb:VerbExtension',
],

只有注册在 ros2cli.extension_point 的组才会被 ros2 extension_points 列出。

4.3 按需加载:add_subparsers_on_demand

优化启动速度的关键设计:

  1. 先为每个 command 创建 空 subparser(无参数)
  2. parse_known_args 判断用户选了哪个 command
  3. 仅实例化被选中的 CommandExtension 并调用其 add_arguments
  4. 未选 command 时加载全部 extension 仅为了生成 help 描述

支持 argv= 参数(测试用)与 argcomplete 的 COMP_LINE 解析。

4.4 CommandExtension / VerbExtension

1
2
3
4
class CommandExtension:
EXTENSION_POINT_VERSION = '0.1'
def add_arguments(self, parser, cli_name, *, argv=None): ...
def main(self, *, parser, args): raise NotImplementedError()

构造时 satisfies_version(PLUGIN_SYSTEM_VERSION, '^0.1') — caret 语义 major/minor 兼容检查。

plugin_system.instantiate_extensions

  • 单例缓存 extension 实例(按 class)
  • PluginException → warning 并跳过
  • 其他异常 → error 并跳过

4.5 内建调试命令

命令 作用
ros2 extensions 列出已加载的 command 扩展
ros2 extension_points 列出所有已注册的 extension point 组

5. 节点策略:DirectNode 与 Daemon

5.1 DirectNode

1
2
3
4
rclpy.init(args=argv)
self.node = rclpy.create_node(NODE_NAME_PREFIX + suffix, ...)
# 默认 spin_time=0.5s 等待图 discovery
rclpy.spin_once(...) until timeout
  • 节点名前缀 _ros2cli_ros2cli.node.NODE_NAME_PREFIX
  • 通过 __getattr__ 转发 node.get_* 图 API
  • 补全 action 相关 API(rclpy.action.*

退出 context 时 destroy_node + rclpy.shutdown()

5.2 _ros2_daemon 与 XML-RPC

独立进程console_scripts: _ros2_daemon = ros2cli.daemon:main):

配置
地址 127.0.0.1
端口 11511 + ROS_DOMAIN_ID
路径 /ros2cli/
空闲超时 默认 2 小时 → 自退出

serve()NetworkAwareNode 注册 RPC 方法(与 rclpy graph API 一一对应):

  • get_node_names_and_namespaces_with_enclaves
  • get_topic_names_and_types / get_service_names_and_types
  • get_publishers_info_by_topic / get_subscriptions_info_by_topic
  • count_publishers / count_subscribers
  • action 相关 get_action_*

DaemonNodeServerProxy 调用;NodeStrategy.__getattr__ 优先走 daemon 方法表。

Spawn 流程spawn_daemon):

  • 先绑定 XML-RPC socket(防 TOCTOU)
  • daemonize() 子进程执行 _ros2_daemon
  • 父进程 wait_for 直到 RPC 可用

5.3 NodeStrategy

1
2
with NodeStrategy(args) as node:
node.get_topic_names_and_types()

逻辑:

  1. 若未 --no-daemon 且 daemon 已运行 → DaemonNode
  2. 若 daemon 未连接 → fallback DirectNode
  3. 若 daemon 未运行 → spawn_daemonDirectNode(首次)

add_strategy_node_arguments 添加:

  • --spin-time — DirectNode discovery 等待
  • --no-daemon — 禁用 daemon
  • --use-sim-time

5.4 NetworkAwareNode

Daemon 内使用:监听网卡变化(netifaces),若地址集变化则 重建 DirectNode,避免网络切换后 graph 数据陈旧。


6. 典型命令包模式(以 ros2topic 为例)

6.1 三层文件组织

1
2
3
4
5
6
7
ros2topic/
├── command/topic.py # TopicCommand
├── verb/
│ ├── __init__.py # VerbExtension 基类
│ ├── list.py # ListVerb
│ └── echo.py # EchoVerb
└── api/__init__.py # 共享 helper、argcomplete

6.2 TopicCommand

1
2
3
4
5
6
7
8
class TopicCommand(CommandExtension):
def add_arguments(self, parser, cli_name):
parser.add_argument('--include-hidden-topics', ...)
add_subparsers_on_demand(parser, cli_name, '_verb', 'ros2topic.verb')

def main(self, *, parser, args):
extension = getattr(args, '_verb', None)
return extension.main(args=args)

6.3 entry_points(setup.py

1
2
3
4
5
6
7
'ros2cli.command': ['topic = ros2topic.command.topic:TopicCommand'],
'ros2cli.extension_point': ['ros2topic.verb = ros2topic.verb:VerbExtension'],
'ros2topic.verb': [
'list = ros2topic.verb.list:ListVerb',
'echo = ros2topic.verb.echo:EchoVerb',
...
],

6.4 两类 verb 实现

A. 仅图查询(走 NodeStrategy)

list.py

1
2
with NodeStrategy(args) as node:
topic_names_and_types = get_topic_names_and_types(node=node, ...)

B. 数据面(自建 rclpy 节点)

echo.py

  • NodeStrategy 仅用于解析 topic 类型(get_msg_class
  • 随后创建订阅、spin 打印 YAML/CSV

api/__init__.py 提供:

  • TopicNameCompleter / TopicTypeCompleter — argcomplete
  • qos_profile_from_short_keys--qos-profile sensor_data
  • get_msg_class — 阻塞等待 publisher 出现

7. 其他命令包要点

7.1 ros2run

  • 无 verbRunCommand.main 直接执行
  • get_executable_path() 查 ament index 安装路径
  • subprocess.Popen + 转发 KeyboardInterrupt
  • 依赖 ros2pkg.api.get_executable_paths

7.2 ros2pkg

  • create — 从 .em 模板生成 ament_cmake / ament_python / cargo 包
  • executables / list / prefix / xml — 包元数据 introspection
  • package_name_completer 供其他命令复用

7.3 ros2param

  • 图查询 + 参数服务客户端AsyncParametersClient
  • dump/load — YAML 参数文件

7.4 ros2doctor

额外 entry point 组(非 verb 分层):

1
2
'ros2doctor.checks': ['PlatformCheck = ...', 'NetworkCheck = ...', ...],
'ros2doctor.report': ['PlatformReport = ...', 'RMWReport = ...', ...],

ros2 doctor 运行 checks;-r 输出 reports。ros2 wtf 为别名 command。

7.5 ros2component

  • composition 包交互:加载/卸载 component 到 container
  • 依赖 launch / component_manager 服务

7.6 ros2interface

  • 纯 Python introspection:rosidl_runtime_py / ament index
  • 需要 ROS graph(通常不用 daemon)

8. XML-RPC 序列化层

ros2cli/xmlrpc/

模块 职责
local_server.py 单线程 XML-RPC server
client.py ServerProxy 包装
marshal/rclpy.py 将 rclpy 返回的复杂类型转为可 RPC 传输结构
marshal/generic.py 通用类型

Daemon 暴露的是 纯函数调用,不是 DDS 代理;每次 RPC 在 daemon 进程内执行 rclpy API。


9. Shell 补全

  • 依赖 python3-argcompleteros2cli exec_depend)
  • cli.py 调用 autocomplete(parser, ...)
  • 各 verb 为 argument 设置 .completer = TopicNameCompleter(...)
  • 安装 share/ros2cli/environment/completion/ros2-argcomplete.bash

10. 测试体系

测试方式
ros2cli pytest:test_strategy.py(NodeStrategy)、test_daemon.py
ros2topic test_cli.py — subprocess 调用真实 ros2
公共 ament lint(flake8/pep257/copyright/xmllint)

CLI 测试常用 launch_testing + fixture nodes(见 ros2topic/test/fixtures/)。


11. 扩展开发指南

11.1 添加新 command(新包)

  1. 创建 ament_python 包,install_requires=['ros2cli']
  2. 实现 XxxCommand(CommandExtension)
  3. setup.py
1
2
3
entry_points={
'ros2cli.command': ['mytool = ros2mytool.command.mytool:MytoolCommand'],
}
  1. colcon buildros2 mytool --help

11.2 添加 verb(已有 command)

1
2
3
4
entry_points={
'ros2cli.extension_point': ['ros2topic.verb = ros2topic.verb:VerbExtension'],
'ros2topic.verb': ['myverb = ros2topic.verb.myverb:MyVerb'],
}

11.3 使用图 API 的最佳实践

  • 只读 introspection → with NodeStrategy(args) as node:
  • 需要 subscription/publisher/service → 独立 rclpy.create_node,勿长期占用 daemon
  • CI 并行 → 固定 ROS_DOMAIN_ID--no-daemon 避免 daemon 端口冲突

12. 本仓库外的 CLI 扩展

以下命令 不在 ros2cli 目录,但使用同一插件机制:

仓库 / 包 命令
rosbag2/ros2bag bag
launch_ros/ros2launch launch
ros2_tracing/ros2trace trace
ros2_testing/ros2test test
sros2/sros2 security
其他第三方 自定义 ros2cli.command

Humble 桌面安装常通过 ros2cli_common_extensions metapackage 一次性依赖本仓库全部 command + ros2launch + sros2 等。


13. 依赖关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ros2cli (框架)
├── rclpy
├── python3-importlib-metadata / pkg-resources
├── python3-argcomplete
└── python3-netifaces (daemon NetworkAwareNode)

ros2topic / ros2service / ...
├── ros2cli
├── rclpy
└── rosidl_runtime_py

ros2run / ros2pkg
├── ros2cli
└── ament_index_python (通过 ros2pkg.api)

ros2doctor
├── ros2cli
└── 各 check 模块 (platform, network, rmw, ...)

14. 调试建议

  1. 命令未出现ros2 extension_points / 检查包是否 overlay source + setup.py entry_points
  2. daemon 问题ros2 daemon status--no-daemon 对比;检查 11511+domain_id 端口占用
  3. 空 topic list:增大 --spin-time;检查 ROS_DOMAIN_ID 与节点一致
  4. echo 无输出:QoS 不匹配(试 --qos-profile);topic 名/remapping
  5. 插件加载失败:查看 stderr warning(Failed to load entry point
  6. 慢启动:首次 spawn daemon 正常;后续应走 RPC

15. 推荐阅读顺序

  1. ros2cli/ros2cli/cli.py — 总入口
  2. ros2cli/command/__init__.pyadd_subparsers_on_demand
  3. ros2cli/node/strategy.py + daemon/__init__.py — daemon 架构
  4. ros2topic/command/topic.py + verb/list.py — command/verb 模式
  5. ros2topic/verb/echo.py — 数据面 + QoS
  6. ros2run/command/run.py — 无 verb 命令
  7. ros2doctor/command/doctor.py — 多 entry point 组
  8. ros2pkg/verb/create.py — 模板生成
  9. 仓库 README.md — 官方扩展示例链接

16. 小结

ros2cli 仓库 = 可扩展 CLI 框架 + ROS 2 标准 introspection 工具集

  • 对上:统一 ros2 用户体验、argcomplete、daemon 加速图查询;
  • 对中:三层 entry point(command → verb → 可选 checks/reports)与按需加载;
  • 对下:rclpy graph API、ament index、subprocess 启动节点。

理解 NodeStrategy / DirectNode / Daemon 三者关系是阅读各 verb 源码的钥匙:列表类命令几乎总是 NodeStrategyecho/pub/param set 等在图查询之外另建 rclpy 实体。扩展新工具只需新 Python 包 + setuptools entry point,无需修改框架源码。

ros2 源码总览

ros2 源码总览

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2
文档目录:/home/cp/work2/ros2Learn/ros2doc/ros2/

本目录是 ROS 2 Humble 核心源码集合(非完整发行版,但涵盖 client library、middleware、idl、launch、cli、bag、rviz、tf2 等主干)。


1. 分层架构

应用层客户端库RCL 层接口层中间件层基础层demos / examplesros2clirvizrclcpprclpyrosidl toolchainrmw_implementationrmw APIrmw_cycloneddsrmw_fastrtpsrcutilsrcpputilsRCLMSG

2. 目录索引(按功能分组)

2.1 基础库

目录 文档
rcutils rcutils源码详细分析.md
rcpputils rcpputils源码详细分析.md
rpyutils rpyutils源码详细分析.md

2.2 中间件 RMW

目录 文档
rmw rmw源码详细分析.md
rmw_implementation rmw_implementation源码详细分析.md
rmw_cyclonedds rmw_cyclonedds源码详细分析.md
rmw_fastrtps rmw_fastrtps源码详细分析.md
rmw_connextdds rmw_connextdds源码详细分析.md
rmw_dds_common rmw_dds_common源码详细分析.md

2.3 RCL / 客户端库

目录 文档
rcl rcl源码详细分析.md
rclcpp rclcpp源码详细分析.md
rclpy rclpy源码详细分析.md
rcl_logging rcl_logging源码详细分析.md

2.4 接口定义与代码生成

目录 文档
rosidl rosidl源码详细分析.md
rosidl_typesupport rosidl_typesupport源码详细分析.md
rosidl_typesupport_fastrtps rosidl_typesupport_fastrtps源码详细分析.md
rosidl_python rosidl_python源码详细分析.md
rosidl_runtime_py rosidl_runtime_py源码详细分析.md
rosidl_defaults rosidl_defaults源码详细分析.md
rosidl_dds rosidl_dds源码详细分析.md
common_interfaces common_interfaces源码详细分析.md
rcl_interfaces rcl_interfaces源码详细分析.md
example_interfaces example_interfaces源码详细分析.md
unique_identifier_msgs unique_identifier_msgs源码详细分析.md
test_interface_files test_interface_files源码详细分析.md

2.5 Launch

目录 文档
launch launch源码详细分析.md
launch_ros launch_ros源码详细分析.md

2.6 CLI / 工具

目录 文档
ros2cli ros2cli源码详细分析.md
ros2cli_common_extensions ros2cli_common_extensions源码详细分析.md
rosbag2 rosbag2源码详细分析.md
ros2_tracing ros2_tracing源码详细分析.md
sros2 sros2源码详细分析.md

2.7 可视化 / TF

目录 文档
rviz rviz源码详细分析.md
geometry2 geometry2源码详细分析.md
urdf urdf源码详细分析.md
message_filters message_filters源码详细分析.md

2.8 示例与测试

目录 文档
demos demos源码详细分析.md
examples examples源码详细分析.md
system_tests system_tests源码详细分析.md
ros_testing ros_testing源码详细分析.md

2.9 Vendor / 构建辅助

目录 文档
ament_cmake_ros ament_cmake_ros源码详细分析.md
python_cmake_module python_cmake_module源码详细分析.md
*_vendor 各 vendor 目录对应文档
eigen3_cmake_module eigen3_cmake_module源码详细分析.md
performance_test_fixture performance_test_fixture源码详细分析.md
realtime_support realtime_support源码详细分析.md
tlsf tlsf源码详细分析.md

3. 推荐学习路径

  1. 消息从哪来common_interfacesrosidlrosidl_typesupport
  2. 数据怎么发rcutilsrmwrclrclcpp(配合 demos/demo_nodes_cpp
  3. 系统怎么起launchlaunch_rosros2launch
  4. 怎么调试ros2cli + rviz + rosbag2
  5. 坐标系geometry2 + urdf

4. 统计

  • 顶层目录:54
  • 子 package.xml 总数:约 250+(含 nested 包)

5. 小结

ros2/ 目录是理解 ROS 2 Humble 运行时与工具链 的主干源码;与 ros/ros-perception/ 等并列,共同构成完整工作区。建议按分层自底向上阅读,每目录详见对应 *源码详细分析.md

ros_testing 源码详细分析

ros_testing 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/ros_testing
版本:0.4.0(Humble),子包 2 个,许可证 Apache 2.0

ros_testing 仓库体量很小(源码文件约 16 个),本身几乎不包含测试框架逻辑。它的定位是 ROS 2 集成测试的 统一入口(metapackage):把 launch_testing 生态、ros2 test CLI 与 CMake add_ros_test() 串成一条链路,供涉及 Node / DDS / launch 的包做 colcon test 集成测试。

重要区分:真正的 launch 集成测试框架在 launchlaunch_ros 仓库的 launch_testing / launch_testing_ros 包中。ros_testing 是对 ROS 场景的薄封装与依赖聚合。


1. 总体认识

1.1 核心职责

能力 实现位置 说明
CMake 注册 launch 测试 ros_testing/cmake/add_ros_test.cmake 包装 ament_add_test,命令改为 ros2 test
依赖聚合 ros_testing/package.xml 导出 launch_testing* + ros2test,下游 test_depend 一处搞定
CLI 入口 ros2test/command/test.py ros2 test 子命令,带 ROS 域隔离
ROS 专用 runner 委托 launch_testing_ros.LaunchTestRunner launch_test 相同,预留 ROS 扩展点

1.2 在 ROS 2 测试栈中的位置

被测包 CMakeLists.txtros_testing 仓库launch 生态基础设施ros2 test ...add_ros_test\ntest/foo.pyros_testing\nCMake extrasros2test\nros2 test CLIlaunch_testing_ament_cmake\nparse_launch_test_argumentslaunch_testing\nLaunchTestRunnerlaunch_testing_ros\nWaitForTopics 等colcon test / ctestdomain_coordinator\nROS_DOMAIN_ID
对比项 ros_testing launch_testing_ament_cmake launch_testing
角色 ROS 集成测试 入口包 通用 CMake 集成 Python 测试 引擎
CMake 函数 add_ros_test() add_launch_test()
运行命令 ros2 test python -m launch_testing.launch_test 被上述两者调用
域隔离 domain_coordinator
适用场景 涉及 ROS Node 的包 任意 launch 测试(含非 ROS) 框架本体

1.3 与 ROS 1 rostest 的关系

README 仍写 “rostest”,CMake 宏里保留 rostest__strip_prefix 命名——这是从 ROS 1 rostest 移植 CMake 辅助逻辑时的历史痕迹。ROS 2 中:

  • 不再有 rostest 包与 test_rostest 宏;
  • 等价能力 = launch_testing + launch_testing_ros + ros_testing
  • 测试文件是 Python launch test*_launch_test.pytest_*.py),不是 .test XML。

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ros_testing/
├── README.md # 一行说明:Node 测试统一入口
├── LICENSE
├── ros_testing/ # ★ CMake 聚合包(无编译产物)
│ ├── package.xml
│ ├── CMakeLists.txt # 仅 install cmake/ + ament_package
│ ├── ros_testing-extras.cmake # include add_ros_test.cmake
│ ├── cmake/add_ros_test.cmake # 唯一核心 CMake 逻辑(~82 行)
│ └── CHANGELOG.rst
└── ros2test/ # ★ ros2cli 扩展
├── package.xml
├── setup.py # entry_point: ros2cli.command
├── ros2test/command/test.py # TestCommand(~51 行)
├── pytest.ini
└── test/ # ament lint 自检
构建类型 源码规模 职责
ros_testing ament_cmakeproject(... NONE) ~82 行 CMake 导出 add_ros_test、传递依赖
ros2test ament_python ~51 行业务逻辑 ros2 test 命令

3. 包 ros_testing:CMake 入口

3.1 CMakeLists.txt

1
2
3
4
project(ros_testing NONE)
find_package(ament_cmake_core REQUIRED)
ament_package(CONFIG_EXTRAS "${PROJECT_NAME}-extras.cmake")
install(DIRECTORY cmake DESTINATION share/${PROJECT_NAME})

特点project(... NONE) 表示 不编译任何目标,纯配置/安装包。安装后下游通过 find_package(ros_testing REQUIRED) 获得 add_ros_test()

3.2 ros_testing-extras.cmake

1
2
find_package(launch_testing_ament_cmake REQUIRED)
include("${ros_testing_DIR}/add_ros_test.cmake")

依赖链:ros_testinglaunch_testing_ament_cmake(复用其 parse_launch_test_arguments 宏)。

3.3 add_ros_test() — 核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function(add_ros_test filename)
parse_launch_test_arguments(_ros_test ${filename} ${ARGN})

set(cmd
"ros2"
"test"
"${_ros_test_FILE_NAME}"
"${_ros_test_ARGS}"
"--junit-xml=${_ros_test_RESULT_FILE}"
"--package-name=${PROJECT_NAME}"
)

ament_add_test(
"${_ros_test_TARGET}"
COMMAND ${cmd}
OUTPUT_FILE "${CMAKE_BINARY_DIR}/ros_test/${_ros_test_TARGET}.txt"
RESULT_FILE "${_ros_test_RESULT_FILE}"
TIMEOUT "${_ros_test_TIMEOUT}"
${_ros_test_UNPARSED_ARGUMENTS}
)
endfunction()

add_launch_test()唯一实质差异COMMAND

函数 执行命令
add_launch_test ${PYTHON_EXECUTABLE} -m launch_testing.launch_test ...
add_ros_test ros2 test ...

其余(TARGET 推导、TIMEOUT 默认 60s、xUnit 路径、ARGS 传递)均来自共享宏 parse_launch_test_arguments

parse_launch_test_arguments 行为摘要

(定义于 launch_testing_ament_cmake/cmake/add_launch_test.cmake

参数 默认 说明
TIMEOUT 60 ctest 超时秒数
TARGET 由文件路径生成 去掉 PROJECT_SOURCE_DIR 前缀,/_
ARGS 传给 launch 的 name:=value 参数
LABELS launch_test ctest 标签(add_ros_test 未显式设置,继承 ament_add_test 默认)
RESULT_FILE $AMENT_TEST_RESULTS_DIR/$PROJECT_NAME/$TARGET.xunit.xml JUnit 报告

3.4 package.xml 依赖设计

1
2
3
4
5
6
7
<buildtool_export_depend>launch_testing_ament_cmake</buildtool_export_depend>
<buildtool_export_depend>ros2test</buildtool_export_depend>
<build_export_depend>launch_testing</build_export_depend>
<build_export_depend>launch_testing_ros</build_export_depend>
<exec_depend>launch_testing</exec_depend>
<exec_depend>launch_testing_ros</exec_depend>
<exec_depend>ros2test</exec_depend>

下游包只需:

1
<test_depend>ros_testing</test_depend>
1
2
find_package(ros_testing REQUIRED)
add_ros_test(test/test_lifecycle.py TIMEOUT 60)

即可在 colcon test 时拉起完整 ROS launch 测试链。不必单独声明 launch_testing_ament_cmake / ros2test(但直接依赖 launch_testing_ament_cmake 也常见,见下文)。


4. 包 ros2testros2 test CLI

4.1 注册方式

setup.py

1
2
3
4
5
entry_points={
'ros2cli.command': [
'test = ros2test.command.test:TestCommand',
],
}

安装后可用:

1
2
3
4
ros2 test /path/to/test_lifecycle.py
ros2 test --show-args test/foo.py
ros2 test test/foo.py arg1:=value --verbose
ros2 test test/foo.py --disable-isolation

4.2 TestCommand 完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
class TestCommand(CommandExtension):
def add_arguments(self, parser, cli_name):
launch_testing.launch_test.add_arguments(parser)
parser.add_argument('--disable-isolation', ...)

def main(self, *, parser, args):
with contextlib.ExitStack() as stack:
if 'ROS_DOMAIN_ID' not in os.environ and not args.disable_isolation:
domain_id = stack.enter_context(domain_coordinator.domain_id())
os.environ['ROS_DOMAIN_ID'] = str(domain_id)
return launch_testing.launch_test.run(
parser, args, test_runner_cls=launch_testing_ros.LaunchTestRunner
)

两步增强(相对 python -m launch_testing.launch_test):

  1. ROS_DOMAIN_ID 自动隔离

    • 若环境未设置 ROS_DOMAIN_ID 且未 --disable-isolation,通过 domain_coordinator.domain_id() 上下文管理器选取 未被占用 的 domain id(基于端口协调,避免并行 colcon test 互相发现)。
    • 若用户已设置 ROS_DOMAIN_ID,则 尊重 用户值。
    • --disable-isolation 关闭自动选取(与 CI 固定 domain 场景兼容)。
  2. 指定 LaunchTestRunner

    • Humble 上 launch_testing_ros.LaunchTestRunner 继承基类且 无额外 override——占位以便将来注入 ROS 专用行为。
    • 实际 ROS 辅助工具(WaitForTopics 等)在测试文件中 显式 import 使用。

4.3 依赖

依赖 用途
ros2cli CommandExtension 基类
launch_testing add_arguments / run
launch_testing_ros LaunchTestRunner
launch / launch_ros 间接(launch 文件常用)
domain_coordinator 并行测试 domain 协调

4.4 历史变更(CHANGELOG)

  • 0.1.0:引入 ros_testing metapackage + ros2test
  • --disable-isolationlaunch_testing 迁至 ros2test(避免非 ROS 场景误用)
  • domain_coordinator API 改为 context manager(with domain_coordinator.domain_id()

5. 端到端执行流程

5.1 从 colcon test 到断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
colcon test --packages-select lifecycle
└── ctest → add_ros_test 注册的目标
└── ros2 test /path/to/test_lifecycle.py --junit-xml=... --package-name=lifecycle
├── [可选] domain_coordinator 分配 ROS_DOMAIN_ID
└── launch_testing.launch_test.run(..., LaunchTestRunner)
├── importlib 加载测试 Python 文件
├── LoadTestsFromPythonModule → 解析 generate_test_description / TestCase
├── LaunchTestRunner.validate()
└── LaunchTestRunner.run()
├── LaunchService 启动 LaunchDescription 中的 Node/Process
├── ReadyToTest → 启动 pre-shutdown unittest(后台线程)
├── 测试可通过 proc_info / proc_output / rclpy 与运行中进程交互
├── 关闭 launch
└── post_shutdown_test 类运行(检查 exit code 等)
└── 写 xUnit XML,返回 exit code

5.2 Launch 测试文件契约

测试文件必须提供:

元素 要求
generate_test_description() 返回 LaunchDescription(LaunchDescription, dict)
launch_testing.actions.ReadyToTest() 必须出现在 LD 中,通知框架可开始 active 测试
unittest.TestCase 子类 pre-shutdown 测试(与 launch 并发)
@launch_testing.post_shutdown_test() 可选,shutdown 后测试

可选第二返回值(context dict)将 action 对象注入测试方法参数,例如:

1
2
return launch.LaunchDescription([...]), {'talker_node': talker_node}
# 测试方法签名:def test_foo(self, proc_output, talker_node): ...

5.3 测试阶段划分

Post-shutdown TestsPre-shutdown TestsLaunchServicePost-shutdown TestsPre-shutdown TestsLaunchServicepar[并发]启动 Node / ExecuteProcessReadyToTest 触发assertWaitFor / rclpy 订阅进程运行关闭所有进程assertExitCodes 等

注入对象launch_testing 自动绑定):

  • proc_info — 进程 exit code、运行状态
  • proc_output — stdout/stderr 捕获
  • launch_service — 运行中可动态发 Event(仅 pre-shutdown)
  • test_args — CLI 传入的 key:=value launch 参数

6. 下游使用示例(lifecycle demo)

demos/lifecycle/CMakeLists.txt

1
2
3
4
5
find_package(ros_testing REQUIRED)
add_ros_test(
test/test_lifecycle.py
TIMEOUT 60
)

test/test_lifecycle.py 要点:

  1. generate_test_description()

    • 启动 LifecycleNode + 普通 Node
    • RegisterEventHandler + OnStateTransition 驱动 lifecycle 状态机
    • 末尾 ReadyToTest()
  2. TestLifecyclePubSub(pre-shutdown)

    • proc_output.assertWaitFor('on_configure() is called', ...)
    • 正则匹配 listener 收到的消息
  3. TestLifecyclePubSubAfterShutdown@post_shutdown_test

    • launch_testing.asserts.assertExitCodes(proc_info, process=talker_node)

手动运行等价命令:

1
2
3
4
ros2 test src/ros2/demos/lifecycle/test/test_lifecycle.py
# 或
launch_test src/ros2/demos/lifecycle/test/test_lifecycle.py
# (后者无自动 ROS_DOMAIN_ID 隔离)

7. 关联包详解(框架本体,非本仓库)

理解 ros_testing 必须熟悉以下包:

7.1 launch_testinglaunch 仓库)

模块 职责
launch_test.py CLI:run() 加载模块、写 JUnit
test_runner.py LaunchTestRunner:线程模型、LaunchService 生命周期
loader.py 从 Python 模块加载 TestRun
asserts.py assertExitCodesassertInStdout
proc_info_handler.py / io_handler.py 进程信息与 I/O 捕获

线程约束LaunchService.run() 必须在主线程;pre-shutdown 测试在 后台线程 与 launch 并发(见 launch/issues/126)。

7.2 launch_testing_roslaunch_ros 仓库)

工具 用途
WaitForTopics 阻塞直到指定 topic 收到消息
DataRepublisher 订阅→修改→再发布(fuzz 测试)
MessagePump 异步消息泵
launch_testing_ros/tools 输出解析等
pytest/hooks.py @pytest.mark.rostest 与 pytest 集成

Pytest 集成launch_testing_ros_pytest_entrypoint.py 注册 rostest marker,使 pytest 收集 launch test 模块。

7.3 launch_testing_ament_cmake

提供 add_launch_test()parse_launch_test_arguments。大量系统测试包 直接依赖 此包而非 ros_testing,例如:

  • system_tests/test_communication
  • demos/demo_nodes_cpp
  • rcl/rcl/test

选择建议:

场景 推荐
测试涉及 ROS Node、需并行 domain 隔离 test_depend ros_testing + add_ros_test
纯 launch/process 测试、CI 固定 domain launch_testing_ament_cmake + add_launch_test
本地快速调试 ros2 testlaunch_test

7.4 domain_coordinatorament_cmake_ros 仓库)

1
2
with domain_coordinator.domain_id() as domain_id:
os.environ['ROS_DOMAIN_ID'] = str(domain_id)

通过端口锁协调,保证多个测试进程不共用同一 domain。ros2 test 默认启用


8. add_ros_test vs add_launch_test 对照

1
2
3
4
5
6
7
# ros_testing — ROS 场景推荐
find_package(ros_testing REQUIRED)
add_ros_test(test/my_test.py TIMEOUT 120 ARGS "param:=value")

# launch_testing_ament_cmake — 通用
find_package(launch_testing_ament_cmake REQUIRED)
add_launch_test(test/my_test.py TIMEOUT 120)
维度 add_ros_test add_launch_test
命令 ros2 test python -m launch_testing.launch_test
Domain 隔离 默认有
输出目录 build/ros_test/ build/launch_test/
xUnit 相同路径规则 相同
依赖包 ros_testing(更重) launch_testing_ament_cmake(更轻)

9. 常见断言与 ROS 交互模式

9.1 仅看进程输出(无 rclpy)

1
2
proc_output.assertWaitFor('expected string', process=node_action, timeout=5)
launch_testing.asserts.assertExitCodes(proc_info, process=node_action)

9.2 测试中启动 rclpy 节点

1
2
3
4
5
6
7
8
9
10
11
class TestFoo(unittest.TestCase):
@classmethod
def setUpClass(cls):
rclpy.init()
@classmethod
def tearDownClass(cls):
rclpy.shutdown()

def test_sub(self):
node = rclpy.create_node('test')
# create_subscription / spin_until_future_complete

launch_testing_ros/test/examples/talker_listener_launch_test.py

9.3 WaitForTopics

1
2
3
from launch_testing_ros import WaitForTopics
with WaitForTopics([('/chatter', String)], timeout=5.0):
...

10. 与 ros2cli 的循环依赖说明

CHANGELOG 记录:ros2topic 等包曾依赖 ros_testing,后改为 直接依赖 launch 包 以避免与 ros2cliros_testingros2cli 的循环。

结论

  • ros2test 依赖 ros2cli,但 ros2cli 各插件不应 test_depend ros_testing
  • 需要 launch 测试的 库/节点包test_depend ros_testing

11. 限制与注意事项

topic 说明
LaunchService 线程 pre-shutdown 测试中勿阻塞主线程式调用 LaunchService.run
Domain 并行测试务必用 ros2 test 或自行协调 ROS_DOMAIN_ID
超时 默认 60s,复杂 lifecycle/导航测试需显式 TIMEOUT
Keyed topic discovery 等内部 topic 与 RMW 能力相关;测试文件需自洽
Windows parse_launch_test_arguments 含 Debug Python 可执行文件分支
框架代码位置 调试断言/ runner 问题应查 launch_testing,不是 ros_testing

12. 调试建议

  1. 单测文件手动跑
    ros2 test path/to/test.py --verbose

  2. 查看 launch 参数
    ros2 test --show-args path/to/test.py

  3. 隔离 domain 问题
    对比 ros2 testlaunch_test(后者不自动分配 domain)

  4. 看 stdout 日志
    build/ros_test/<target>.txtadd_ros_test)或 build/launch_test/add_launch_test

  5. JUnit
    build/test_results/<package>/<target>.xunit.xml

  6. 并行 ctest 失败且表现为 discovery 串扰
    确认未 --disable-isolation 且未多个测试强制同一 ROS_DOMAIN_ID


13. 推荐阅读顺序

  1. 本仓库 add_ros_test.cmake — 理解入口仅一行命令差异
  2. ros2test/command/test.py — domain 隔离 + runner 选择
  3. launch_testing/README.md — launch test 文件契约与断言 API
  4. launch_testing/launch_test.pyrun() 加载与 JUnit
  5. launch_testing/test_runner.py — 线程与 LaunchService 生命周期
  6. demos/lifecycle/test/test_lifecycle.py — 真实 ROS 集成测试样例
  7. launch_testing_ros/test/examples/ — talker/listener、WaitForTopics
  8. launch_testing_ament_cmake/cmake/add_launch_test.cmake — CMake 宏细节

14. 小结

ros_testing 仓库是 ROS 2 集成测试的薄入口层,价值在于 聚合依赖统一 CLI

  • ros_testing:导出 add_ros_test(),把 ctest 接到 ros2 test
  • ros2test:在 launch_testing.launch_test.run 外包一层 ROS_DOMAIN_ID 隔离
  • 测试框架本体launch_testing / launch_testing_ros,Launch 文件 + unittest 两阶段(running / post-shutdown)模型与 ROS 1 rostest 哲学相似,但实现完全基于 launch 系统。

编写涉及 Node 的包测试时,推荐 test_depend ros_testing + add_ros_test();若仅需 launch 进程测试或要避免引入 ros2cli 链,可直接使用 launch_testing_ament_cmakeadd_launch_test()。两种方式的 测试 Python 文件格式相同,差异几乎只在 谁执行命令是否自动隔离 DDS domain