首页/目录/全部文章

全部文章

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

笔记列表

python_cmake_module 源码详细分析

python_cmake_module 源码详细分析

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


1. 定位

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


2. 子包列表

包名 版本 说明
python_cmake_module 0.10.0 Provide CMake module with extra functionality for Python.

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

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

5. 小结

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

rcl_interfaces 源码详细分析

rcl_interfaces 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rcl_interfaces
版本:1.2.2(各子包统一),构建类型 ament_cmake + rosidl,许可证 Apache 2.0

rcl_interfaces 仓库不是运行时逻辑库,而是一组 ROS 2 接口定义包.msg / .srv / .action)。源码经 rosidl_generate_interfaces 生成 C/C++/Python 等语言的 typesupport 代码,供 rclrclcpprclpyrcl_actioncomponent_manager 等上层库使用。可以把它理解为 ROS 2 客户端栈的**“协议契约层”**——定义参数、Action、Lifecycle、组件化、时钟等核心概念在线上如何表达。


1. 总体认识

1.1 仓库定位

特点 说明
纯接口 无业务 .c/.cpp 实现,只有 IDL 定义 + CMake 构建
QL1 test_msgs 外,各包均声明 Quality Level 1
被广泛依赖 几乎所有 ROS 2 核心包间接或直接依赖
设计文档驱动 接口语义来自 ROS 2 Design Articles

1.2 在 ROS 2 栈中的位置

flowchart TB
  subgraph apps [应用 / CLI]
    NODE[rclcpp Node]
    ROS2CLI[ros2 param / component]
    GAZEBO[Gazebo / 仿真 clock]
  end
  subgraph clients [客户端库]
    RCLCPP[rclcpp]
    RCLPY[rclpy]
    RCL[rcl]
    RCLACT[rcl_action]
    RCLLC[rcl_lifecycle]
  end
  subgraph iface [rcl_interfaces 仓库]
    RCLI[rcl_interfaces]
    ACT[action_msgs]
    LIFE[lifecycle_msgs]
    COMP[composition_interfaces]
    BUILTIN[builtin_interfaces]
    ROSGRAPH[rosgraph_msgs]
    STATS[statistics_msgs]
  end
  subgraph gen [代码生成]
    ROSIDL[rosidl_generate_interfaces]
    TS[typesupport C/C++/Python]
  end

  NODE --> RCLCPP --> RCLI & ACT & LIFE
  ROS2CLI --> COMP & RCLI
  GAZEBO --> ROSGRAPH
  RCL --> RCLI
  RCLACT --> ACT
  iface --> ROSIDL --> TS
  TS --> clients

2. 子包一览

仓库含 8 个独立 ROS 包(同一 git 仓库,colcon 分别编译):

包名 版本 接口数量 职责
builtin_interfaces 1.2.2 2 msg OMG IDL 基础时间类型 Time / Duration
rcl_interfaces 1.2.2 11 msg + 6 srv 参数系统、日志 /rosout
action_msgs 1.2.2 3 msg + 1 srv Action 通用状态与 CancelGoal
lifecycle_msgs 1.2.2 4 msg + 4 srv Lifecycle 节点状态机
composition_interfaces 1.2.2 3 srv Composable Node 动态加载
rosgraph_msgs 1.2.2 1 msg 仿真时钟 /clock
statistics_msgs 1.2.2 3 msg Topic 统计指标
test_msgs 1.2.2 测试用 rosidl/typesupport 测试 fixture

目录结构:

1
2
3
4
5
6
7
8
9
10
11
rcl_interfaces/
├── README.md
├── LICENSE
├── builtin_interfaces/ # Time, Duration
├── rcl_interfaces/ # 参数 + Log ★ 核心
├── action_msgs/ # GoalInfo, GoalStatus, CancelGoal
├── lifecycle_msgs/ # State, Transition, ChangeState...
├── composition_interfaces/ # LoadNode, UnloadNode, ListNodes
├── rosgraph_msgs/ # Clock
├── statistics_msgs/ # MetricsMessage
└── test_msgs/ # 测试专用 msg/action

3. 构建机制

每个子包结构相同,以 rcl_interfaces 为例:

1
2
3
4
5
6
7
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/FloatingPointRange.msg"
"msg/IntegerRange.msg"
...
"srv/SetParameters.srv"
DEPENDENCIES builtin_interfaces
)

构建流程:

1
2
3
4
5
.msg / .srv / .action
→ rosidl_adapter(转 IDL)
→ rosidl_generator_c / _cpp / _py ...
→ rosidl_typesupport_c / _cpp / _fastrtps / _introspection ...
→ 安装到 install/rcl_interfaces/{include,lib,share}

所有包均声明 <member_of_group>rosidl_interface_packages</member_of_group>,表示属于 ROS 接口包生态。

3.1 包间依赖

builtin_interfacesrcl_interfacesaction_msgslifecycle_msgscomposition_interfacesrosgraph_msgsstatistics_msgsunique_identifier_msgs
直接依赖
builtin_interfaces
rcl_interfaces builtin_interfaces
action_msgs builtin_interfaces, unique_identifier_msgs
lifecycle_msgs 无(自包含 enum)
composition_interfaces rcl_interfaces
rosgraph_msgs builtin_interfaces
statistics_msgs builtin_interfaces

4. builtin_interfaces — 基础时间类型

路径:builtin_interfaces/msg/

ROS 2 全栈最底层的时间/msg 类型,对应 OMG IDL PSM 中的 TimeDuration

4.1 Time.msg

1
2
3
4
5
# The seconds component, valid over all int32 values.
int32 sec

# The nanoseconds component, valid in the range [0, 1e9)
uint32 nanosec
  • 可表示负时间(如 {sec: -2, nanosec: 300000000} = -1.7s)
  • 用于:std_msgs/Header.stampaction_msgs/GoalInfo.stamp、参数事件时间戳等

4.2 Duration.msg

结构与 Time 相同,语义为时间间隔而非时间点。

4.3 消费者

消费者 用途
所有带 Header 的消息 std_msgs/Header.stamp
rcl/time.h ROS Time 与 RMW 时间转换
TF2 / 传感器消息 时间戳字段

builtin_interfaces 是 ROS 2 中使用最广泛的接口包之一,几乎所有消息类型都间接依赖它。


5. rcl_interfaces — 参数与日志

路径:rcl_interfaces/
这是仓库的核心包,定义 ROS 2 参数服务协议/rosout 日志消息

5.1 参数系统架构

节点 namespace 下消息类型get_parametersset_parameterslist_parametersdescribe_parametersget_parameter_typesset_parameters_atomicallyparameter_eventsParameterValueParameterParameterEvent

每个节点在自身 namespace 下暴露标准服务(由 rclcpp/rclpy 自动创建):

服务名 类型 功能
~/get_parameters GetParameters 按名读取参数值
~/set_parameters SetParameters 逐个设置,返回每项成败
~/set_parameters_atomically SetParametersAtomically 全部成功或全部失败
~/list_parameters ListParameters 按前缀递归列出
~/describe_parameters DescribeParameters 返回描述符(类型、范围、只读)
~/get_parameter_types GetParameterTypes 返回参数类型 enum

标准 Topic:

Topic 类型 功能
~/parameter_events ParameterEvent 参数增删改事件广播
~/parameter_event_descriptors ParameterEventDescriptors 仅描述符版(大参数场景)

README 中提到的 has_parameters 服务在 Humble 版接口文件中已不存在,以实际 .srv 文件为准。

5.2 参数值 — 变体类型设计

ParameterValue.msg 采用 tagged union(标记联合体) 模式:

1
2
3
4
5
6
7
8
9
10
11
uint8 type

bool bool_value
int64 integer_value
float64 double_value
string string_value
byte[] byte_array_value
bool[] bool_array_value
int64[] integer_array_value
float64[] double_array_value
string[] string_array_value

ParameterType.msg 定义 type 枚举:

常量 对应字段
PARAMETER_NOT_SET 0 未设置
PARAMETER_BOOL 1 bool_value
PARAMETER_INTEGER 2 integer_value
PARAMETER_DOUBLE 3 double_value
PARAMETER_STRING 4 string_value
PARAMETER_BYTE_ARRAY 5 byte_array_value
PARAMETER_BOOL_ARRAY 6 bool_array_value
PARAMETER_INTEGER_ARRAY 7 integer_array_value
PARAMETER_DOUBLE_ARRAY 8 double_array_value
PARAMETER_STRING_ARRAY 9 string_array_value

只有 type 对应的一个字段有效——这是 ROS 2 参数在 DDS 线上无 union 类型时的惯用设计。

5.3 参数描述符与约束

ParameterDescriptor.msg 携带元数据:

  • description / additional_constraints:人类可读说明
  • read_only:初始化后不可改
  • dynamic_typing:允许运行时改类型
  • floating_point_range / integer_range:取值范围与步长

FloatingPointRange.msg 示例:from_valueto_valuestep 三者定义合法浮点参数空间。

5.4 参数事件

ParameterEvent.msg 在一次原子更新中,每个参数名只出现在三个列表之一

1
2
3
4
5
builtin_interfaces/Time stamp
string node
Parameter[] new_parameters
Parameter[] changed_parameters
Parameter[] deleted_parameters

rclcppParameterEventHandlerros2 param listen 订阅此 topic 跟踪参数变化。

5.5 关键 Service 定义

GetParameters.srv

1
2
3
string[] names          # 请求
---
ParameterValue[] values # 响应,顺序与 names 对应

SetParameters.srv

1
2
3
Parameter[] parameters
---
SetParametersResult[] results # 每项含 successful + reason

ListParameters.srv

1
2
3
4
string[] prefixes
uint64 depth # 0 = DEPTH_RECURSIVE 无限递归
---
ListParametersResult result # names[] + prefixes[]

SetParametersAtomically.srv:与 SetParameters 类似,但返回单个 SetParametersResult,任一失败则全部回滚。

5.6 Log.msg — /rosout

1
2
3
4
5
6
7
8
9
10
11
12
13
byte DEBUG=10
byte INFO=20
byte WARN=30
byte ERROR=40
byte FATAL=50

builtin_interfaces/Time stamp
uint8 level
string name
string msg
string file
string function
uint32 line
  • 日志级别与 Python logging / rcutils/logging.h 对齐
  • rcl/logging_rosout.c 将 RCUTILS 日志发布到 rosout topic,类型即此消息
  • 支持 ros2 run rqt_console 等工具订阅

5.7 ROS 1 桥接

mapping_rules.yaml 定义 rosgraph_msgs/Logrcl_interfaces/Log 的字段映射,供 ros1_bridge 使用:

1
2
3
4
5
6
7
8
ros1_package_name: 'rosgraph_msgs'
ros1_message_name: 'Log'
ros2_package_name: 'rcl_interfaces'
ros2_message_name: 'Log'
fields_1_to_2:
header.stamp: 'stamp'
level: 'level'
...

6. action_msgs — Action 通用类型

路径:action_msgs/
定义所有 Action 共享的消息与服务,具体 Action(如 Fibonacci.action)由各功能包自行定义。

6.1 GoalInfo.msg

1
2
unique_identifier_msgs/UUID goal_id
builtin_interfaces/Time stamp
  • goal_id:128 位 UUID,全局唯一标识一个 goal
  • stamp:goal 被 server 接受的时间

6.2 GoalStatus.msg — 状态机

状态常量 含义
STATUS_UNKNOWN 0 未初始化
STATUS_ACCEPTED 1 已接受,等待执行
STATUS_EXECUTING 2 正在执行
STATUS_CANCELING 3 取消中
STATUS_SUCCEEDED 4 成功完成
STATUS_CANCELED 5 已取消
STATUS_ABORTED 6 server 中止

rcl_action 的 goal 状态机与此 enum 一一对应。

6.3 GoalStatusArray.msg

Action server 在 ~/action_name/_action/status topic 上发布,包含所有活跃 goal 的状态列表。

6.4 CancelGoal.srv

支持四种取消策略(goal_id + timestamp 组合):

goal_id timestamp 行为
zero zero 取消所有 goal
zero 非 zero 取消该时间点之前接受的所有 goal
非 zero zero 取消指定 ID 的 goal
非 zero 非 zero 取消指定 ID + 该时间前的 goal

返回码:ERROR_NONEERROR_REJECTEDERROR_UNKNOWN_GOAL_IDERROR_GOAL_TERMINATED

6.5 与 Action 协议的关系

用户定义的 My.action 在底层会拆成多个 topic/service,action_msgs 提供元协议部分:

1
2
3
4
5
~/my_action/_action/send_goal      (service)
~/my_action/_action/cancel_goal (CancelGoal.srv) ← action_msgs
~/my_action/_action/get_result (service)
~/my_action/_action/feedback (topic)
~/my_action/_action/status (GoalStatusArray) ← action_msgs

7. lifecycle_msgs — 生命周期节点

路径:lifecycle_msgs/
定义 Managed Node Lifecycle 的标准接口。

7.1 状态机

State.msg 定义 primary states 与 transition states:

Primary States(稳定态)

ID 名称 含义
0 UNKNOWN 未设置
1 UNCONFIGURED 刚创建,未配置
2 INACTIVE 已配置,未激活
3 ACTIVE 正常运行
4 FINALIZED 即将销毁

Transition States(中间态):CONFIGURING(10)、CLEANINGUP(11)、ACTIVATING(13)、DEACTIVATING(14) 等。

7.2 Transition.msg

定义标准转移 ID(0–8 为公开转移,10–62 为内部/回调结果):

转移 ID 触发回调
CREATE 0 实例化
CONFIGURE 1 on_configure
CLEANUP 2 on_cleanup
ACTIVATE 3 on_activate
DEACTIVATE 4 on_deactivate
UNCONFIGURED_SHUTDOWN 5 on_shutdown

7.3 服务

服务 功能
ChangeState 请求状态转移
GetState 查询当前 primary state
GetAvailableStates 列出可达状态
GetAvailableTransitions 列出当前可用转移

7.4 TransitionEvent.msg

Lifecycle 节点在 ~/transition_event topic 发布状态变化:

1
2
3
4
uint64 timestamp
Transition transition
State start_state
State goal_state

rclcpp_lifecycle::LifecycleNoderos2 lifecycle CLI 依赖这些接口。


8. composition_interfaces — 组件化节点

路径:composition_interfaces/
component_managerros2 component 动态加载/卸载 composable node。

8.1 LoadNode.srv

1
2
3
4
5
6
7
8
9
10
11
12
13
string package_name
string plugin_name
string node_name
string node_namespace
uint8 log_level
string[] remap_rules
rcl_interfaces/Parameter[] parameters
rcl_interfaces/Parameter[] extra_arguments
---
bool success
string error_message
string full_node_name
uint64 unique_id
  • plugin_name:如 rclcpp_components 注册的 TalkerComponent
  • 可携带 remap 规则和初始参数(复用 rcl_interfaces/Parameter
  • 返回 unique_id 供后续 UnloadNode 使用

8.2 UnloadNode.srv / ListNodes.srv

服务 请求 响应
UnloadNode unique_id success, error_message
ListNodes full_node_names[], unique_ids[]

8.3 消费者

  • rclcpp_components::ComponentManager
  • ros2 component load/unload/list
  • launch_ros 的 ComposableNodeContainer

9. rosgraph_msgs — 计算图时钟

路径:rosgraph_msgs/msg/Clock.msg

1
builtin_interfaces/Time clock
  • 仿真环境(Gazebo 等)在 /clock topic 发布此消息
  • rclcpp::TimeSource 订阅 /clock 驱动 ROS Time(sim time)
  • 与 wall time(系统时钟)相对,enable use_sim_time 时节点 timer 跟随仿真时间

关键消费者:rclcpp/src/rclcpp/time_source.cpp


10. statistics_msgs — Topic 统计

路径:statistics_msgs/

消息 职责
StatisticDataType.msg 统计类型 enum(mean、max、min、std_dev 等)
StatisticDataPoint.msg 单个统计值(type + float64 data)
MetricsMessage.msg 完整指标报文(来源、窗口、统计点数组)

MetricsMessage 结构:

  • measurement_source_name:节点/topic/进程名
  • metrics_source:指标名(如 message_agesubscription_period
  • window_start / window_stop:统计窗口
  • statistics[]:数据点列表

消费者:rclcpp topic statistics 功能(libstatistics_collector),可选开启。


11. test_msgs — 测试专用

路径:test_msgs/

内容 用途
msg/Builtins.msg 覆盖各种 builtin 类型组合
action/NestedMessage.action 嵌套消息 Action 测试
include/test_msgs/*_fixtures.hpp C++ 测试 fixture
src/test_msgs/*_fixtures.py Python 测试 fixture

不应在应用代码中依赖——仅供 rosidlrmwrcl 测试使用。


12. 主要消费者映射

接口包 主要消费者 使用场景
builtin_interfaces 全栈 时间戳、Duration
rcl_interfaces rclcpprclpyrcl/logging_rosout.c 参数 API、/rosout
action_msgs rcl_actionrclcpp_action Action cancel/status
lifecycle_msgs rcl_lifecyclerclcpp_lifecycle 生命周期管理
composition_interfaces rclcpp_componentsros2component 动态组件
rosgraph_msgs rclcpp::TimeSource 仿真时间
statistics_msgs rclcpp topic statistics 性能监控

12.1 rcl 层直接使用

rcl 包在 package.xml 中依赖 rcl_interfaces,直接使用生成的 C 类型:

1
#include "rcl_interfaces/msg/log.h"

/rosout publisher 创建时使用 rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__Log()

参数服务的实现不在 rcl 层,而在 rclcpp/rclpy 的 node_interfaces 中,但 wire 格式即本仓库定义的 srv/msg。


13. 参数分组规则

参数名采用类文件路径的分组语义(来自 README):

  • 默认组:/
  • 嵌套组:/my_group/sub_group/param_name
  • 与节点 namespace 独立——参数名本身是全局字符串

ListParametersprefixes + depth 控制递归深度,DEPTH_RECURSIVE=0 表示无限深度。


14. 设计特点小结

特点 说明
契约优先 接口即规范,改接口需跨版本协调
变体参数 ParameterValue 用 type tag + 多字段模拟 union
原子参数更新 ParameterEvent 三列表互斥;SetParametersAtomically 全成全败
Action 元协议 通用 status/cancel 与具体 action 类型分离
Lifecycle 标准化 状态/转移 ID 全局一致,跨语言互操作
组件化接口 LoadNode 直接复用 Parameter 类型
最小 rosgraph 仅 Clock 一条消息,保持计算图接口精简
无运行时代码 全部逻辑在消费者侧,本仓库只有 IDL

15. 与相邻包对比

类型 内容
rcl_interfaces 接口定义 ROS 客户端库内部协议
rcl C 运行时库 封装 rmw,使用 rcl_interfaces 类型
common_interfaces 接口定义 通用传感器/几何消息(sensor_msgs 等)
unique_identifier_msgs 接口定义 UUID(action_msgs 依赖)

16. 推荐阅读顺序

  1. rcl_interfaces/README.md — 参数 topic/service 命名约定
  2. ParameterValue.msg + ParameterType.msg — 理解参数类型系统
  3. GetParameters.srv / SetParameters.srv — 参数 RPC 协议
  4. action_msgs/GoalStatus.msg — Action 状态机
  5. lifecycle_msgs/State.msg + Transition.msg — 生命周期设计
  6. 消费者代码
    • rclcpp/src/rclcpp/node_interfaces/parameters*.cpp — 参数服务实现
    • rcl/src/rcl/logging_rosout.c — Log 消息发布
    • rclcpp/src/rclcpp/time_source.cpp — Clock 订阅
  7. ROS 2 Design Articles — 参数、Action、Lifecycle 设计原文

17. 接口清单速查

17.1 rcl_interfaces 全部接口

Messages (11)Parameter, ParameterValue, ParameterType, ParameterDescriptor, ParameterEvent, ParameterEventDescriptors, SetParametersResult, ListParametersResult, FloatingPointRange, IntegerRange, Log

Services (6)GetParameters, SetParameters, SetParametersAtomically, ListParameters, DescribeParameters, GetParameterTypes

17.2 其他包

msg srv action
builtin_interfaces Time, Duration
action_msgs GoalInfo, GoalStatus, GoalStatusArray CancelGoal
lifecycle_msgs State, Transition, TransitionDescription, TransitionEvent ChangeState, GetState, GetAvailableStates, GetAvailableTransitions
composition_interfaces LoadNode, UnloadNode, ListNodes
rosgraph_msgs Clock
statistics_msgs MetricsMessage, StatisticDataPoint, StatisticDataType

文档基于 ROS 2 Humble 工作区中的 rcl_interfaces 1.2.2 源码(接口定义)分析生成。

rcl_logging 源码详细分析

rcl_logging 源码详细分析

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


1. 定位

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


2. 子包列表

包名 版本 说明
rcl_logging_interface 2.3.2 Interface that rcl_logging backends needs to implement.
rcl_logging_noop 2.3.2 An rcl logger implementation that doesn’t do anything with l…
rcl_logging_spdlog 2.3.2 Implementation of rcl_logging API for an spdlog backend.

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

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

5. 小结

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

rclcpp 源码详细分析

rclcpp 源码详细分析

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

rclcpp(ROS Client Library for C++)是 ROS 2 C++ 客户端库,几乎所有 C++ 节点、rviz2、Nav2、MoveIt 2 等均构建于此。它在 rcl(C API)之上提供类型安全的 C++ 抽象,并引入 Executorcallback_groupintra-process参数服务 等 ROS 2 特有机制。理解 rclcpp 是掌握 ROS 2 C++ 应用并发模型与通信路径的关键。


1. 总体认识

1.1 核心职责

能力 说明
生命周期 rclcpp::init() / rclcpp::shutdown()Context 封装 rcl_init/rcl_shutdown
Node 创建 pub/sub/service/client/timer,namespace 与 remap
Executor 调度 ready 回调(subscription/timer/service/client/waitable)
CallbackGroup 互斥/可重入分组,控制并发语义
Intra-process 同进程零拷贝/共享指针消息传递
参数 declare/get/set + 标准参数服务(rcl_interfaces
时间 Clock / TimeSource,订阅 /clock 实现 sim time
QoS C++ 封装 rmw_qos_profile_t,支持 qos_overrides
Action / Lifecycle / Components 独立子包扩展

1.2 在 ROS 2 栈中的位置

用户 / 上层框架rclcpp 仓库rcl 层更下层Nav2 / MoveIt2rviz2examples / demosrclcpp 核心rclcpp_actionrclcpp_componentsrclcpp_lifecyclerclrcl_actionrcl_lifecyclermw_implementationrosidl_typesupport_cpprcutils / rcpputilsFast-DDS / CycloneDDS
下层依赖 rclcpp 如何使用
rcl 所有 pub/sub/service/client/timer/wait 均最终调用 rcl_*
rcl_action rclcpp_action 封装 action client/server
rcl_lifecycle rclcpp_lifecycle 封装 lifecycle 状态机
rcl_interfaces 参数服务、parameter_events 消息类型
rosgraph_msgs /clock 话题类型
libstatistics_collector 可选 topic 统计

与 rcl 的分工rcl 提供语言无关 C API 与 wait set;参数服务的实现主体在 rclcppParameterService),rcl 只负责 CLI 参数/YAML 解析。Executor、callback_group、intra-process 均为 rclcpp 独有,不在 rcl 层。


2. 子包结构

1
2
3
4
5
6
7
8
9
rclcpp/
├── rclcpp/ # 核心库 ★
│ ├── include/rclcpp/ # ~85 个顶层头文件 + node_interfaces/ 等
│ ├── include/rclcpp/node_interfaces/ # 11 组 interface
│ ├── src/rclcpp/ # 73 个 .cpp(~11.8K 行)
│ └── src/rclcpp/executors/ # 4 种 Executor 实现
├── rclcpp_action/ # Action C++ API
├── rclcpp_components/ # Composable Node + ComponentManager
└── rclcpp_lifecycle/ # LifecycleNode
版本 职责
rclcpp 16.0.19 Node、Executor、pub/sub/service/timer、参数、QoS、intra-process
rclcpp_action 16.0.19 Client/Server/ClientGoalHandle,封装 rcl_action
rclcpp_components 16.0.19 动态加载 .so 组件,ComponentManager 提供 load/unload/list
rclcpp_lifecycle 16.0.19 LifecycleNode、状态/转移、LifecyclePublisher

2.1 源码规模(核心包 rclcpp/rclcpp

指标 数量
公开头文件(.hpp ~148
实现文件(.cpp ~73
核心 .cpp 总行数 ~11,794
最大单文件 executor.cpp(947 行)、node.cpp(607 行)、time_source.cpp(554 行)

3. 依赖关系

3.1 rclcpp/package.xml

1
2
3
4
5
6
7
8
9
10
11
rclcpp
├── rcl # C 客户端库
├── rcl_yaml_param_parser # YAML 参数文件(经 rcl 间接使用)
├── rmw # QoS 类型、GID 等
├── rcutils / rcpputils # 日志、ScopeExit、文件系统
├── rosidl_runtime_cpp # 消息 C++ 运行时
├── rosidl_typesupport_cpp # typesupport 查找
├── rcl_interfaces # 参数 srv/msg
├── rosgraph_msgs # Clock 消息
├── libstatistics_collector # topic 统计
└── tracetools # LTTng 追踪点

3.2 子包额外依赖

子包 关键依赖
rclcpp_action rcl_action, action_msgs, rosidl_runtime_c
rclcpp_components class_loader, composition_interfaces, ament_index_cpp
rclcpp_lifecycle rcl_lifecycle, lifecycle_msgs

4. 核心设计:Node + node_interfaces

4.1 Node 是用户 API 入口

1
2
/// Node is the single point of entry for creating publishers and subscribers.
class Node : public std::enable_shared_from_this<Node>

用户通过 Node::create_publisher()create_subscription() 等创建通信实体;内部不直接持有 rcl_node_t,而是通过 组合式 interface 拆分职责。

4.2 Interface 组合(构造顺序)

Node 构造函数按固定顺序实例化各 interface,并相互注入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Node::Node(
const std::string & node_name,
const std::string & namespace_,
const NodeOptions & options)
: node_base_(new rclcpp::node_interfaces::NodeBase(...)),
node_graph_(new rclcpp::node_interfaces::NodeGraph(node_base_.get())),
node_logging_(new rclcpp::node_interfaces::NodeLogging(node_base_.get())),
node_timers_(new rclcpp::node_interfaces::NodeTimers(node_base_.get())),
node_topics_(new rclcpp::node_interfaces::NodeTopics(node_base_.get(), node_timers_.get())),
node_services_(new rclcpp::node_interfaces::NodeServices(node_base_.get())),
node_clock_(new rclcpp::node_interfaces::NodeClock(...)),
node_parameters_(new rclcpp::node_interfaces::NodeParameters(...)),
node_time_source_(new rclcpp::node_interfaces::NodeTimeSource(...)),
node_waitables_(new rclcpp::node_interfaces::NodeWaitables(node_base_.get())),
...
Interface 实现类 职责
NodeBaseInterface NodeBase rcl_node_t 句柄、FQN、context、intra-process 开关
NodeGraphInterface NodeGraph topic/service 图 introspection
NodeLoggingInterface NodeLogging 节点 logger
NodeTimersInterface NodeTimers 创建/管理 timer
NodeTopicsInterface NodeTopics create_publisher / create_subscription
NodeServicesInterface NodeServices create_service / create_client
NodeClockInterface NodeClock 节点 clock
NodeParametersInterface NodeParameters declare/get/set 参数、回调
NodeTimeSourceInterface NodeTimeSource 订阅 /clock、驱动 sim time
NodeWaitablesInterface NodeWaitables 注册 waitable(含 action client 等)

设计动机

  • Component 友好ComponentManager 只需 NodeBaseInterface 等指针即可把节点挂到 Executor
  • 可测试:mock 单个 interface 而不构造完整 Node
  • LifecycleNode 复用rclcpp_lifecycle::LifecycleNode 同样组合这些 interface

4.3 NodeOptions

NodeOptions 集中配置:

  • context()use_intra_process_comms()enable_topic_statistics()
  • start_parameter_services()parameter_overrides()allow_undeclared_parameters()
  • get_rcl_node_options() → 底层 rcl_node_options_t(含 remap、use_global_arguments)
  • QoS 预设:parameter_event_qos()clock_qos()

5. Context 与 init

5.1 全局 init 流程

1
2
3
4
5
6
7
8
9
10
11
void
init(
int argc,
char const * const * argv,
const InitOptions & init_options,
SignalHandlerOptions signal_handler_options)
{
using rclcpp::contexts::get_global_default_context;
get_global_default_context()->init(argc, argv, init_options);
install_signal_handlers(signal_handler_options);
}

rclcpp::init() → 默认 Context::init()rcl_init(),并安装 SIGINT/SIGTERM 处理器(触发 shutdown)。

5.2 Context 封装 rcl_context

1
2
3
4
5
6
7
8
9
10
11
12
13
Context::init(
int argc,
char const * const * argv,
const rclcpp::InitOptions & init_options)
{
...
rcl_ret_t ret = rcl_init(argc, argv, init_options.get_rcl_init_options(), context);
...
rcl_context_.reset(context, __delete_context);
if (init_options.auto_initialize_logging()) {
rcl_logging_configure_with_output_handler(...);
}
}
特性 说明
多 Context WeakContextsWrapper 跟踪所有已创建 context,支持多 init 场景
shutdown 回调 add_on_shutdown_callback() 注册清理逻辑
有效性 context->is_valid() 对应 rcl_context_is_valid()
默认 context contexts/default_context.hpp 提供进程级单例

5.3 辅助 API

API 作用
rclcpp::ok() 检查默认 context 是否仍有效
rclcpp::shutdown() 关闭默认 context
rclcpp::remove_ros_arguments() 剥离 ROS 特有 CLI 参数
rclcpp::spin(node) 便捷函数:SingleThreadedExecutor + add_node + spin

6. Executor 架构

Executor 是 rclcpp 并发与调度核心:将「通信图」与「执行模型」解耦——节点创建实体,Executor 决定何时执行回调。

6.1 类层次

文件 特点
Executor executor.hpp / executor.cpp 基类:wait set、实体收集、execute
SingleThreadedExecutor executors/single_threaded_executor.cpp 单线程 spin 循环
MultiThreadedExecutor executors/multi_threaded_executor.cpp N 线程并行 execute
StaticSingleThreadedExecutor executors/static_single_threaded_executor.hpp 静态实体列表,减少每轮重建开销

6.2 spin 主循环(SingleThreadedExecutor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void
SingleThreadedExecutor::spin()
{
if (spinning.exchange(true)) {
throw std::runtime_error("spin() called while already spinning");
}
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
while (rclcpp::ok(this->context_) && spinning.load()) {
rclcpp::AnyExecutable any_executable;
if (get_next_executable(any_executable)) {
execute_any_executable(any_executable);
}
}
}

6.3 get_next_executable 两阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
bool
Executor::get_next_executable(AnyExecutable & any_executable, std::chrono::nanoseconds timeout)
{
bool success = get_next_ready_executable(any_executable);
if (!success) {
wait_for_work(timeout);
if (!spinning.load()) {
return false;
}
success = get_next_ready_executable(any_executable);
}
return success;
}
  1. get_next_ready_executable:按优先级扫描已 ready 实体(timer → subscription → service → client → waitable)
  2. wait_for_work:若无 ready 实体,则 collect_entitiesrcl_wait_set_resizercl_wait()

6.4 wait_for_work 与 rcl wait set

核心步骤(executor.cpp):

  1. memory_strategy_->collect_entities(weak_groups_to_nodes_) — 收集本 Executor 管理的 callback group 内实体
  2. rcl_wait_set_clear / rcl_wait_set_resize — 按实体数量调整 wait set
  3. memory_strategy_->add_handles_to_wait_set — 填入 subscription/timer/service/client/guard_condition 句柄
  4. rcl_wait(&wait_set_, timeout) — 阻塞直至有事件或超时
  5. remove_null_handles — 清理 middleware 标记为 invalid 的句柄

这与 rcl 源码分析 中的 wait set 机制直接对应,rclcpp 在其上增加了 callback group 过滤memory strategy 抽象。

6.5 execute_any_executable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void
Executor::execute_any_executable(AnyExecutable & any_exec)
{
if (!spinning.load()) {
return;
}
if (any_exec.timer) {
execute_timer(any_exec.timer);
}
if (any_exec.subscription) {
execute_subscription(any_exec.subscription);
}
if (any_exec.service) {
execute_service(any_exec.service);
}
if (any_exec.client) {
execute_client(any_exec.client);
}
if (any_exec.waitable) {
any_exec.waitable->execute(any_exec.data);
}
any_exec.callback_group->can_be_taken_from().store(true);
interrupt_guard_condition_.trigger();
}

执行完毕后:

  • 重置 can_be_taken_from(MutuallyExclusive group 在执行前会被置 false)
  • 触发 interrupt_guard_condition_,唤醒可能在 rcl_wait 中阻塞的其他线程

6.6 MultiThreadedExecutor

  • 默认线程数 = hardware_concurrency()(至少 1)
  • 各线程在 wait_mutex_ 保护下调用 get_next_executable
  • 可选 yield_before_execute 减少锁竞争
  • any_exec.callback_group.reset() 避免析构时错误重置 group 状态

6.7 StaticSingleThreadedExecutor

实体列表在 spin() 前通过 StaticExecutorEntitiesCollector 一次性收集,仅在 add/remove node 时更新。适合实体集合固定的生产节点,降低 collect_entities 开销。

6.8 MemoryStrategy

默认 AllocatorMemoryStrategymemory_strategies/allocator_memory_strategy.hpp)负责:

  • 从 callback group → node 映射中收集 handles
  • 在 wait 返回后查找 next ready 实体
  • 可替换为自定义 strategy(测试或特殊调度)

7. CallbackGroup 并发模型

7.1 两种类型

1
2
3
4
5
enum class CallbackGroupType
{
MutuallyExclusive,
Reentrant
};
类型 行为
MutuallyExclusive 同 group 内同一时刻最多一个回调执行;can_be_taken_from_ 原子标志 gate
Reentrant 同 group 内回调可并行(MultiThreadedExecutor 下)

7.2 与 Executor 的关系

  • 每个 subscription/timer/service/client 创建时绑定一个 CallbackGroup
  • 默认 group 由 automatically_add_to_executor_with_node 控制是否随 add_node() 自动注册
  • Executor 维护 weak_groups_to_nodes_ 映射,只调度已 add_callback_group() 的 group
  • MutuallyExclusiveget_next_ready_executable_from_map 选中实体后将 can_be_taken_from 置 false,直到 execute 完成

7.3 典型用法

1
2
3
4
5
6
7
// 互斥:回调不会重叠
auto group = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
rclcpp::SubscriptionOptions opts;
opts.callback_group = group;

// 可重入:多线程 Executor 下可并行
auto reentrant = node->create_callback_group(rclcpp::CallbackGroupType::Reentrant);

8. Publisher / Subscription

8.1 模板层次

说明
PublisherBase 非模板基类,持有 rcl_publisher_t,topic 名、QoS、intra-process id
Publisher<MessageT, AllocatorT> 模板发布者,publish() 多重重载
SubscriptionBase 非模板基类,type-erased take/handle
Subscription<MessageT, AllocatorT> 模板订阅者,用户回调

还支持 TypeAdapter(自定义类型适配 ROS 消息)、GenericPublisher/Subscription(运行时类型)。

8.2 发布路径(inter-process)

1
2
3
4
5
Publisher::publish(msg)
→ do_inter_process_publish()
→ rcl_publish()
→ rmw_publish()
→ DDS

8.3 发布路径(intra-process 开启)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publish(std::unique_ptr<T, ROSMessageTypeDeleter> msg)
{
if (!intra_process_is_enabled_) {
this->do_inter_process_publish(*msg);
return;
}
bool inter_process_publish_needed =
get_subscription_count() > get_intra_process_subscription_count();

if (inter_process_publish_needed) {
auto shared_msg =
this->do_intra_process_ros_message_publish_and_return_shared(std::move(msg));
this->do_inter_process_publish(*shared_msg);
} else {
this->do_intra_process_ros_message_publish(std::move(msg));
}
}

策略要点:

  • Volatile durability 允许 intra-process
  • 若存在跨进程订阅者,先 intra 再 inter(降低端到端延迟)
  • unique_ptr 路径优先零拷贝移交所有权

8.4 订阅执行路径(Executor)

execute_subscription() 三条分支(executor.cpp):

模式 方法
Serialized take_serializedhandle_serialized_message
Loaned rcl_take_loaned_message → callback → rcl_return_loaned_message_from_subscription
默认 copy take_type_erasedhandle_messagereturn_message

Intra-process 消息在 handle_message 内通过 MessageInfo.from_intra_process 区分,不经过 DDS take。


9. Intra-process 通信

9.1 IntraProcessManager

位于 rclcpp/experimental/intra_process_manager.hpp,由 Context 持有单例(每 context 一个)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint64_t
IntraProcessManager::add_publisher(rclcpp::PublisherBase::SharedPtr publisher)
{
uint64_t pub_id = IntraProcessManager::get_next_unique_id();
publishers_[pub_id] = publisher;
pub_to_subs_[pub_id] = SplittedSubscriptions();
for (auto & pair : subscriptions_) {
...
if (can_communicate(publisher, subscription)) {
insert_sub_id_for_pub(sub_id, pub_id, subscription->use_take_shared_method());
}
}
return pub_id;
}

9.2 匹配规则 can_communicate

1
2
3
4
5
6
7
8
9
10
11
12
bool
IntraProcessManager::can_communicate(...) const
{
if (strcmp(pub->get_topic_name(), sub->get_topic_name()) != 0) {
return false;
}
auto check_result = rclcpp::qos_check_compatible(pub->get_actual_qos(), sub->get_actual_qos());
if (check_result.compatibility == rclcpp::QoSCompatibility::Error) {
return false;
}
return true;
}

9.3 数据结构

成员 含义
publishers_ pub_id → weak_ptr PublisherBase
subscriptions_ sub_id → weak_ptr SubscriptionIntraProcessBase
pub_to_subs_ pub_id → {take_shared_subscriptions, take_ownership_subscriptions}

两种 intra subscription 策略:共享指针(多订阅者共享同一份数据)与 所有权转移(unique_ptr 零拷贝)。


10. Service / Client / Timer / Waitable

实体 创建入口 Executor 执行
Service Node::create_service() execute_service → take request → 用户回调 → send response
Client Node::create_client() execute_client → 处理 pending response
Timer Node::create_wall_timer() / create_timer() execute_timertimer->execute_callback()
Waitable Node::create_waitable() 或 action client 内部 waitable->execute(data)

Timer 基于 rcl timer + guard condition:到期时在 wait set 中触发,优先级高于 subscription(get_next_ready_executable_from_map 先查 timer)。

Waitable 是可扩展钩子:rclcpp_action::Client 实现 Waitable 接口,将 action 相关 waitable 实体纳入 Executor 调度,而无需独立 spin 线程。


11. 参数系统

11.1 NodeParameters

NodeParameters 负责:

  • declare_parameter() / get_parameter() / set_parameter()
  • 参数覆盖(CLI、--params-file、节点级 overrides)
  • on_parameters_set 回调链
  • 可选启动 ParameterService/parameter_events 发布者

11.2 ParameterService(标准服务)

1
2
3
4
5
6
7
8
9
10
get_parameters_service_ = create_service<rcl_interfaces::srv::GetParameters>(
node_base, node_services,
node_name + "/" + parameter_service_names::get_parameters,
[node_params](..., Request request, Response response)
{
auto parameters = node_params->get_parameters(request->names);
for (const auto & param : parameters) {
response->values.push_back(param.get_value_message());
}
}, ...);

注册的服务(与 rcl_interfaces 分析 对应):

服务名 类型
get_parameters rcl_interfaces/srv/GetParameters
get_parameter_types GetParameterTypes
set_parameters SetParameters
set_parameters_atomically SetParametersAtomically
describe_parameters DescribeParameters
list_parameters ListParameters

11.3 ParameterClient

远程节点参数访问的客户端封装,内部创建对应 service client,供 ros2 param 等工具链使用。


12. 时间与 Clock

12.1 TimeSource

TimeSourcetime_source.cpp)订阅全局 /clockrosgraph_msgs/msg/Clock):

  • 收到 sim time 时启用 ros_time_active_,更新所有关联 Clock
  • use_sim_time 参数联动(经 NodeTimeSource wiring)
  • 支持独立 clock 线程(use_clock_thread NodeOption)

12.2 Clock 类型

类型 说明
RCL_SYSTEM_TIME 系统 wall clock
RCL_ROS_TIME 仿真时间(由 /clock 驱动)
RCL_STEADY_TIME 单调时钟

Timer 可选择 clock:create_timer(clock, period, callback) vs create_wall_timer()


13. QoS

rclcpp::QoS 封装 rmw_qos_profile_t,提供流式 API:

1
2
3
rclcpp::QoS(10).reliable().transient_local();
rclcpp::SensorDataQoS();
rclcpp::ParametersQoS();

13.1 qos_overrides

节点构造时通过 declare_qos_parameters() 暴露 qos_overrides.<topic>.<policy> 参数,允许运行时覆盖 depth/reliability/durability/history(见 node.cppget_parameter_events_qos)。

13.2 兼容性检查

qos_check_compatible() 在 intra-process 匹配与 rclcpp::QoS 警告中使用,对应 RMW 的 rmw_qos_profile_check_compatible


14. rclcpp_action

依赖 rcl_action C 库,提供类型安全的 C++ Action API。

14.1 主要类型

类型 职责
rclcpp_action::Client<ActionT> 发送 goal、cancel、接收 feedback/result
rclcpp_action::Server<ActionT> 接受 goal、执行、发布 feedback/result
ClientGoalHandle / ServerGoalHandle 单个 goal 的生命周期与状态
create_client() / create_server() 工厂函数

14.2 与 Executor 集成

Action client/server 内部创建多个 pub/sub/service 及 Waitable 实体,注册到 callback group 后由 Executor 统一调度,无需用户手动 spin action 专用线程。

14.3 依赖链

1
2
3
rclcpp_action → rclcpp → rcl
→ rcl_action → rcl
→ action_msgs(UUID 等)

15. rclcpp_components

15.1 动机

Composable Node 允许在 单进程 内加载多个节点组件,配合 intra-process 减少序列化与 DDS hop。

15.2 ComponentManager

1
2
3
4
5
6
7
8
ComponentManager::ComponentManager(...)
: Node(std::move(node_name), node_options),
executor_(executor)
{
loadNode_srv_ = create_service<LoadNode>("~/_container/load_node", ...);
unloadNode_srv_ = create_service<UnloadNode>("~/_container/unload_node", ...);
listNodes_srv_ = create_service<ListNodes>("~/_container/list_nodes", ...);
}
服务 接口包
~/_container/load_node composition_interfaces/srv/LoadNode
~/_container/unload_node UnloadNode
~/_container/list_nodes ListNodes

15.3 加载流程

  1. ament_index 查找包内注册的组件资源(rclcpp_components_register_nodes CMake 宏)
  2. class_loader::ClassLoader 动态加载 .so
  3. NodeFactory 实例化组件(RCLCPP_COMPONENTS_REGISTER_NODE 宏)
  4. executor->add_node() 将新节点纳入调度

15.4 容器可执行文件

可执行文件 Executor
component_container SingleThreadedExecutor
component_container_mt MultiThreadedExecutor
component_container_isolated 每组件独立 Executor

16. rclcpp_lifecycle

16.1 LifecycleNode

继承/组合与 Node 相同的 interface,额外实现 LifecycleNodeInterface

主状态 说明
Unconfigured 初始
Inactive 已 configure,未 activate
Active 正常运行
Finalized 已 cleanup/shutdown

转移:configureactivatedeactivatecleanupshutdown 等,底层调用 rcl_lifecycle

16.2 LifecyclePublisher

仅在 Active 状态下真正 publish;Inactive 时 publish 被忽略或缓存(取决于配置),便于安全切换。

16.3 与 Nav2 / 工业场景

生命周期节点是托管节点(managed node)模式的基础,配合 lifecycle_manager 统一拉起/关闭。


17. 关键数据路径

17.1 订阅回调端到端

User callbackExecutorrcl wait setrclDDS / RMWUser callbackExecutorrcl wait setrclDDS / RMW数据到达 subscriptionsubscription 就绪rcl_wait()返回 readyget_next_ready_executable()rcl_take / take_loanedmessagesubscription->handle_message()can_be_taken_from = true

17.2 发布端到端(含 intra-process)

Publisher::publish()IntraProcessManagerIntra subscription callbacksrcl_publishDDS

17.3 进程启动典型顺序

1
2
3
4
5
6
7
8
9
10
11
main()
→ rclcpp::init(argc, argv)
→ Context::init → rcl_init
→ auto node = std::make_shared<Node>(...)
→ NodeBase → rcl_node_init
→ NodeParameters → 声明参数 / 启动 ParameterService
→ NodeTimeSource → 订阅 /clock
→ rclcpp::spin(node)
→ SingleThreadedExecutor::add_node
→ spin loop (wait + execute)
→ rclcpp::shutdown()

18. 目录与模块索引

18.1 include/rclcpp/ 主要头文件

模块 头文件
节点 node.hpp, node_options.hpp
执行器 executor.hpp, executors/*.hpp, any_executable.hpp
通信 publisher.hpp, subscription.hpp, service.hpp, client.hpp, timer.hpp
并发 callback_group.hpp, waitable.hpp
实验特性 experimental/intra_process_manager.hpp
参数 parameter.hpp, parameter_service.hpp, parameter_client.hpp
时间 clock.hpp, time_source.hpp, duration.hpp
QoS qos.hpp, qos_event.hpp
上下文 context.hpp, utilities.hpp
内存 memory_strategy.hpp, message_memory_strategy.hpp

18.2 src/rclcpp/ 核心实现

文件 行数 职责
executor.cpp 947 wait/execute/spin 核心
node.cpp 607 Node 构造、create_* 委托
time_source.cpp 554 /clock 与 sim time
parameter_client.cpp 545 远程参数
context.cpp 527 Context 生命周期
subscription_base.cpp 459 take/handle 基础设施
intra_process_manager.cpp 230 进程内路由
parameter_service.cpp 158 标准参数服务

19. 与 rcl / rcl_interfaces 对照

功能 rcl rclcpp
init/shutdown rcl_init Context::init, rclcpp::init
wait rcl_wait, rcl_wait_set_t Executor::wait_for_work
publish rcl_publish PublisherBase::do_inter_process_publish
take rcl_take SubscriptionBase::take_type_erased
参数服务 ParameterService + rcl_interfaces srv
Executor 完整调度栈
intra-process IntraProcessManager

20. 调试与追踪

  • 日志RCLCPP_* 宏 → rcutils logging;节点 logger 名来自 NodeLogging
  • 追踪TRACEPOINT(rclcpp_executor_*) 等,依赖 tracetools
  • Topic 统计libstatistics_collector 可选编译,经 subscription 回调统计
  • 常见问题
    • 回调不触发 → 检查 Executor 是否 add_node、callback group 是否注册
    • 死锁 → MutuallyExclusive group 内回调再次 spin 或阻塞同 group 实体
    • intra-process 不生效 → QoS durability 非 Volatile、topic 名/QoS 不匹配

21. 推荐阅读顺序

  1. utilities.cpp + context.cpp — 理解 init/shutdown 与默认 context
  2. node.cpp + node_interfaces/node_topics.cpp — Node 如何创建 pub/sub
  3. executors/single_threaded_executor.cpp + executor.cpp — spin / wait / execute 全流程
  4. callback_group.hpp + executor.cpp(get_next_ready_executable_from_map) — 并发语义
  5. publisher.hpp + intra_process_manager.cpp — 发布与进程内优化
  6. parameter_service.cpp + node_interfaces/node_parameters.cpp — 参数栈
  7. time_source.cpp — sim time
  8. rclcpp_action/client.hpp — action 如何挂到 Executor
  9. rclcpp_components/component_manager.cpp — 动态组件
  10. rclcpp_lifecycle/lifecycle_node.hpp — 生命周期
  11. 对照 examples/rclcpp_*rcl 源码分析 下层行为

22. 小结

rclcpp 是 ROS 2 C++ 应用的主 API 层,核心模式为:

  • Node + node_interfaces 创建与管理通信实体
  • Executor 通过 rcl_wait 驱动 callback_group 内的回调
  • IntraProcessManager 在同进程内绕过 DDS 实现低延迟数据路径
  • ParameterService / TimeSource 等将 ROS 2 系统服务集成进节点生命周期

掌握 Executor + callback_group 的交互,是理解 ROS 2 C++ 并发模型的关键;排查通信问题时,应沿 publish/take → rcl → rmw 链向下追踪,并区分 inter-process 与 intra-process 路径。

rclpy 源码详细分析

rclpy 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rclpy
版本:3.3.21(Humble),单包仓库,构建类型 ament_cmake(Python + C++ 扩展),许可证 Apache 2.0

rclpy 是 ROS 2 Python 客户端库,为 ros2cli、Launch 测试节点、大量 Python 应用提供主 API。它在 rcl(C API)之上通过 pybind11 扩展 _rclpy_pybind11 封装底层句柄,在纯 Python 层实现 Executorcallback_group参数服务async 回调 等机制。与 rclcpp 功能对齐,但架构为「Python 调度 + C 绑定」双层设计。


1. 总体认识

1.1 核心职责

能力 说明
生命周期 rclpy.init() / rclpy.shutdown()Context 封装 rcl_init/rcl_shutdown
Node 创建 pub/sub/service/client/timer,维护实体列表
Executor 基于 rcl_wait_set 调度 ready 回调,支持 sync/async 协程
CallbackGroup MutuallyExclusive / Reentrant 并发控制
参数 declare/get/set + ParameterServicercl_interfaces srv)
时间 Clock / TimeSource,订阅 /clock 实现 sim time
QoS Python 封装 rmw_qos_profile_t,支持 qos_overrides
Action rclpy.action 子模块,封装 rcl_action
Lifecycle rclpy.lifecycle 子模块,封装 rcl_lifecycle

1.2 在 ROS 2 栈中的位置

用户 / 工具rclpy 仓库rcl 层更下层ros2clilaunch / 测试节点Python 应用Python 层\nnode / executors / parameter_rclpy_pybind11\npybind11 C++ 扩展rclrcl_actionrcl_lifecyclermw_implementationrosidl_generator_py / typesupportrcutils / rcpputilsFast-DDS / CycloneDDS
对比项 rclpy rclcpp
语言绑定 pybind11 → Python 原生 C++
Executor 回调 Task + async/await 支持 直接函数调用
Intra-process Humble 未实现 IntraProcessManager
Action/Lifecycle 同仓库 Python 子模块 独立子包
句柄生命周期 Destroyable 上下文管理器 RAII + shared_ptr

与 rcl 的分工:与 rclcpp 相同,参数服务实现主体在 rclpyrcl 负责 CLI/YAML 参数解析。Executor、callback_group 为 rclpy 独有 Python 层逻辑。


2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rclpy/
├── rclpy/ # 主包 ★
│ ├── rclpy/ # Python 源码(~50 个 .py)
│ │ ├── node.py # 最大(~1957 行)
│ │ ├── executors.py # Executor(~870 行)
│ │ ├── qos.py # QoS(~499 行)
│ │ ├── action/ # Action client/server
│ │ ├── lifecycle/ # LifecycleNode
│ │ └── impl/ # C 扩展懒加载
│ ├── src/rclpy/ # C++ 绑定(30 个 .cpp)
│ │ ├── _rclpy_pybind11.cpp # 模块入口
│ │ ├── wait_set.cpp # rcl_wait_set 封装
│ │ ├── node.cpp / publisher.cpp / subscription.cpp ...
│ │ └── action_*.cpp / lifecycle.cpp
│ ├── test/ # pytest + gtest
│ └── CMakeLists.txt # 构建 Python 包 + 扩展
└── (无独立子 package.xml)

2.1 源码规模

层级 文件数 规模
Python(rclpy/rclpy/ ~50 最大单文件 node.py(1957 行)
C++ 扩展(src/rclpy/ 30 最大单文件 node.cpp(584 行)、signal_handler.cpp(641 行)
Python + C++ 合计 ~14K 行(不含测试)

3. 依赖关系(package.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rclpy
├── rcl # C 客户端库
├── rcl_action # Action C API(扩展层直接调用)
├── rcl_lifecycle # Lifecycle C API
├── rcl_yaml_param_parser # YAML 参数(经 Node C++ 层)
├── rcl_logging_interface # 日志接口
├── rmw / rmw_implementation # 中间件
├── rcutils / rcpputils # 工具库
├── rosidl_runtime_c # 消息 C 运行时(扩展层序列化)
├── pybind11_vendor # Python 绑定
├── rcl_interfaces # 参数 srv/msg(exec_depend)
├── rosgraph_msgs # /clock
├── builtin_interfaces # Time 等
├── unique_identifier_msgs # Action goal UUID
└── rpyutils # import_c_library 工具

4. 双层架构:Python ↔ _rclpy_pybind11

4.1 懒加载 C 扩展

为避免 import rclpy 时立即加载 C 库,扩展通过单例延迟导入:

1
2
3
4
from rpyutils import import_c_library
package = 'rclpy'

rclpy_implementation = import_c_library('._rclpy_pybind11', package)

各模块在需要时 from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy

4.2 pybind11 模块导出

_rclpy_pybind11.cpp 注册所有 C 层类型与自由函数:

类别 绑定内容
生命周期 Context
Node Nodercl_node_t
通信 Publisher, Subscription, Service, Client, Timer
同步 GuardCondition, WaitSet, Clock, Duration
Action ActionClient, ActionServer, ActionGoalHandle
Lifecycle lifecycle 状态机 C 封装
Graph rclpy_get_topic_names_and_types
工具 topic/namespace 校验、remap、序列化、QoS 兼容性检查
异常 RCLError, InvalidHandle, TimerCancelledError

4.3 Destroyable:句柄生命周期

C++ 层所有 rcl 句柄继承 Destroyable,实现 Python 上下文管理器:

1
2
3
4
5
6
7
8
9
10
11
class Destroyable
{
public:
void enter(); // __enter__ — 阻止销毁
void exit(...); // __exit__ — 允许销毁
void destroy_when_not_in_use();
virtual void destroy() = 0;
private:
size_t use_count = 0u;
bool please_destroy_ = false;
};

Python 侧统一使用 with handle: 保护 rcl 调用,避免 wait 期间句柄被 GC 销毁:

1
2
with self.handle:
self.__publisher.publish(msg)

5. Context 与 init

5.1 Python Context

1
2
3
4
5
6
7
8
9
class Context:
def init(self, args=None, *, initialize_logging=True, domain_id=None):
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
...
self.__context = _rclpy.Context(
args if args is not None else sys.argv,
domain_id if domain_id is not None else _rclpy.RCL_DEFAULT_DOMAIN_ID)
if initialize_logging and not self._logging_initialized:
_rclpy.rclpy_logging_configure(self.__context)

5.2 C++ Context → rcl_init

1
2
3
4
5
6
Context::Context(py::list pyargs, size_t domain_id)
{
rcl_context_ = std::shared_ptr<rcl_context_t>(...);
*rcl_context_ = rcl_get_zero_initialized_context();
// rcl_init_options + rcl_init ...
}

全局 g_contexts 向量跟踪所有有效 context,进程退出时 shutdown_contexts() 统一 shutdown。

5.3 便捷 API(rclpy/__init__.py + utilities.py

API 作用
rclpy.init() 默认 context init + 安装信号处理器
rclpy.ok() 检查 context 是否有效
rclpy.shutdown() shutdown + 销毁全局 Executor
rclpy.spin(node) 全局 SingleThreadedExecutor
get_default_context() 进程级 context 单例
remove_ros_args() 剥离 ROS CLI 参数

6. Node

6.1 实体容器

Node 在 Python 层维护所有通信实体的列表(rclcpp 则分散在 node_interfaces 中):

1
2
3
4
5
6
7
8
self._publishers: List[Publisher] = []
self._subscriptions: List[Subscription] = []
self._clients: List[Client] = []
self._services: List[Service] = []
self._timers: List[Timer] = []
self._guards: List[GuardCondition] = []
self.__waitables: List[Waitable] = []
self._default_callback_group = MutuallyExclusiveCallbackGroup()

底层 rcl_node_t 由 C++ rclpy::Node 持有;Python Node 通过 property 暴露 handle

6.2 构造流程

  1. 校验 context 已 init
  2. _rclpy.Node(name, namespace, context, cli_args, use_global_arguments, enable_rosout)
  3. 创建 logger、TimeSourceParameterService(可选)
  4. 处理 parameter_overrides、qos_overrides 声明

6.3 create_* 方法

方法 Python 包装 C 绑定
create_publisher Publisher rcl_publisher_init
create_subscription Subscription rcl_subscription_init
create_service Service rcl_service_init
create_client Client rcl_client_init
create_timer Timer rcl_timer_init
create_guard_condition GuardCondition rcl_guard_condition_init

创建时实体加入指定 callback_group(默认 MutuallyExclusiveCallbackGroup),并 append 到 Node 对应列表。


7. Publisher / Subscription

7.1 Publisher

Python 薄包装,核心 publish 在 C++:

1
2
3
4
5
6
def publish(self, msg: Union[MsgType, bytes]) -> None:
with self.handle:
if isinstance(msg, self.msg_type):
self.__publisher.publish(msg)
elif isinstance(msg, bytes):
self.__publisher.publish_raw(msg)

C++ 层将 Python 消息对象转为 C 结构后调用 rcl_publish

1
2
3
4
5
6
void Publisher::publish(py::object pymsg)
{
auto raw_ros_message = convert_from_py(pymsg);
rcl_ret_t ret = rcl_publish(rcl_publisher_.get(), raw_ros_message.get(), NULL);
...
}

消息转换依赖 rosidl_generator_py 生成的 C 绑定与 common_get_type_support() 查找 typesupport。

7.2 Subscription

  • take_message() 在 C++ 层完成(rcl_take + Python 对象构造)
  • 支持 raw=True 返回 bytes
  • _executor_event 标志防止同一 subscription 被重复加入 wait set

7.3 数据路径

1
2
publish:  Python msg → convert_from_py → rcl_publish → rmw → DDS
take: DDS → rmw → rcl_take → Python msg → user callback

注意:Humble 版 rclpy 无 intra-process 优化,所有消息均走完整 rcl/rmw 路径。同进程 Python 节点间通信仍经 DDS(与 rclcpp intra-process 不同)。


8. Executor 架构

Executor 是 rclpy 最复杂的纯 Python 模块(870 行),负责 wait → take → execute 全流程。

8.1 类层次

特点
Executor 基类:wait set 构建、Task 调度、实体管理
SingleThreadedExecutor 调用线程同步执行 handler()
MultiThreadedExecutor ThreadPoolExecutor 并行提交 handler

8.2 spin 流程

1
2
3
4
# SingleThreadedExecutor
while rclpy.ok() and not self._is_shutdown:
handler, entity, node = wait_for_ready_callbacks(timeout)
handler() # 同步执行 Task
1
2
3
# MultiThreadedExecutor
handler, entity, node = wait_for_ready_callbacks(...)
self._executor.submit(handler) # 线程池异步执行

8.3 _wait_for_ready_callbacks 核心逻辑

timersubscriptionservice/client/guard/waitable收集 nodes 实体filter can_execute构建 WaitSetrcl_wait via WaitSet.waitget_ready_entities实体类型_make_handler → execute_timertake_message → execute_subscription对应 take/execute

关键步骤(executors.py):

  1. 遍历节点,收集 can_execute 过滤后的 subscriptions/timers/clients/services/guards/waitables
  2. _rclpy.WaitSet 封装 rcl_wait_set_init/clear/add/wait
  3. wait_set.wait(timeout_nsec) 阻塞
  4. 按 ready 索引匹配实体,调用 _make_handler 生成 Task
  5. 通过 generator yield 返回 (handler, entity, node)

8.4 Task 与 async 回调

rclpy 独有:回调可以是 coroutine

1
2
3
4
5
async def await_or_execute(callback, *args):
if inspect.iscoroutinefunction(callback):
return await callback(*args)
else:
return callback(*args)

_make_handler 创建 async Task,流程:

  1. callback_group.beginning_execution(entity) — MutuallyExclusive 加锁
  2. take_from_wait_list(entity) — take 消息/请求
  3. await call_coroutine(entity, arg) — 执行用户回调
  4. callback_group.ending_execution(entity) — 释放锁
  5. 触发 guard condition 唤醒 wait

8.5 WaitSet C++ 封装

1
2
3
4
5
6
7
WaitSet::WaitSet(..., Context & context)
{
rcl_wait_set_init(..., context.rcl_ptr(), ...);
}
// add_subscription / add_timer / add_service / add_client / add_guard_condition
// wait() → rcl_wait()
// get_ready_entities() → 返回 ready 句柄 pointer 集合

与 rclcpp Executor::wait_for_work 直接对应,但 rclpy 在 Python 层 构建 wait set(每轮循环重建),而非 rclcpp 的 MemoryStrategy。


9. CallbackGroup 并发模型

1
2
# ReentrantCallbackGroup — can_execute 恒 True
# MutuallyExclusiveCallbackGroup — _active_entity 锁,同时仅一个回调
类型 实现
MutuallyExclusiveCallbackGroup threading.Lock + _active_entity,默认 group
ReentrantCallbackGroup 无限制,用于 Rate

与 rclcpp 的 can_be_taken_from 原子标志语义等价,但 rclpy 用 Python Lock 实现。

Executor.can_execute 额外检查 entity._executor_event:已生成 handler 但未执行完毕的实体不再进入 wait set。


10. 参数系统

10.1 Node 内参数存储

  • _parameters: dict — 参数名 → Parameter
  • _descriptors — 参数描述符
  • _parameters_callbackson_parameters_set
  • 支持 allow_undeclared_parametersautomatically_declare_parameters_from_overrides

10.2 ParameterService

1
2
3
4
5
6
7
8
class ParameterService:
def __init__(self, node):
node.create_service(DescribeParameters, nodename + '/describe_parameters', ...)
node.create_service(GetParameters, nodename + '/get_parameters', ...)
node.create_service(GetParameterTypes, ...)
node.create_service(ListParameters, ...)
node.create_service(SetParameters, ...)
node.create_service(SetParametersAtomically, ...)

服务名格式为 <node_name>/<service_suffix>(与 rclcpp 的 FQN 路径等价,表达方式不同)。类型均来自 rcl_interfaces(参见 rcl_interfaces 分析)。

10.3 TimeSource 与 use_sim_time

TimeSource 订阅 /clock,监听 use_sim_time 参数变化,更新关联 ROSClock

1
2
3
4
5
def _subscribe_to_clock_topic(self):
self._clock_sub = node.create_subscription(
rosgraph_msgs.msg.Clock, CLOCK_TOPIC,
self.clock_callback,
QoSProfile(depth=1, reliability=ReliabilityPolicy.BEST_EFFORT))

11. QoS

qos.py(499 行)提供:

  • QoSProfile 类(depth、reliability、durability、history、liveliness 等)
  • 预设:qos_profile_sensor_dataqos_profile_parametersqos_profile_services_default
  • qos_overriding_options.py — 与 rclcpp 对齐的运行时 QoS 覆盖

C 层 rclpy_qos_check_compatible() 暴露 RMW 兼容性检查。


12. Action(rclpy.action

同仓库子模块,非独立 package。

文件 职责
action/client.py ActionClient,继承 Waitable,goal send/cancel/result
action/server.py ActionServer,goal 接受与执行
action/graph.py action 图 introspection

C 绑定:action_client.cpp(322 行)、action_server.cpp(430 行),直接调用 rcl_action_*

Action client 作为 Waitable 注册到 Executor,与 rclcpp action client 设计一致。


13. Lifecycle(rclpy.lifecycle

文件 职责
lifecycle/node.py LifecycleNode(多继承 Node + LifecycleNodeMixin
lifecycle/publisher.py LifecyclePublisher — Active 状态才 publish
lifecycle/managed_entity.py 托管实体基类

C 绑定:lifecycle.cpp(364 行)封装 rcl_lifecycle 状态机。

状态转移回调:on_configureon_activateon_deactivateon_cleanupon_shutdown 等。


14. 其他重要模块

模块 职责
task.py Future / Task — async 回调与 done 链
waitable.py Waitable 基类,action client 等扩展点
guard_condition.py 用户自定义唤醒条件
signals.py SIGINT/SIGTERM → shutdown context
logging.py Python logging 与 rcutils 桥接
serialization.py 消息序列化/反序列化
type_support.py 消息/服务类型校验
clock.py / time.py / duration.py 时间抽象
qos_event.py QoS 事件(incompatible QoS 等)
client.py / service.py 请求-响应 RPC
timer.py 定时器 + Rate
wait_for_message.py 阻塞等待首条消息
validate_*.py 名称校验纯 Python 实现

15. 关键数据路径

15.1 订阅回调端到端

User callbackExecutor PythonWaitSet C++rclDDS / RMWUser callbackExecutor PythonWaitSet C++rclDDS / RMW数据到达WaitSet.wait()ready subscription indicestake_message()Python msgawait_or_execute(callback, msg)

15.2 典型程序结构

1
2
3
4
5
6
7
8
9
10
11
import rclpy
from rclpy.node import Node

def main():
rclpy.init()
node = Node('my_node')
pub = node.create_publisher(String, 'topic', 10)
sub = node.create_subscription(String, 'topic', callback, 10)
rclpy.spin(node) # 全局 SingleThreadedExecutor
node.destroy_node()
rclpy.shutdown()

等价于 rclcpp 的 init → Node → spin → shutdown,但 rclpy.spin 使用模块级全局 Executor(__init__.pyget_global_executor())。


16. C++ 扩展文件索引

文件 行数 职责
node.cpp 584 Node 创建、参数 YAML、graph
signal_handler.cpp 641 信号处理
action_server.cpp 430 Action server 绑定
utils.cpp 368 消息转换、通用工具
lifecycle.cpp 364 Lifecycle 状态机
action_client.cpp 322 Action client 绑定
wait_set.cpp 295 Wait set
graph.cpp 280 图 introspection
publisher.cpp 192 publish / publish_raw
subscription.cpp take_message
context.cpp 188 rcl_init/shutdown
_rclpy_pybind11.cpp 241 模块注册入口

17. 与 rclcpp 对照

功能 rclcpp rclpy
Node 组织 node_interfaces 组合 单类 + 实体列表
Executor wait C++ MemoryStrategy + rcl_wait Python 构建 WaitSet + rcl_wait
回调执行 同步函数 Task + 可选 async
句柄保护 shared_ptr RAII Destroyable + with handle
Intra-process 无(Humble)
参数服务 ParameterService C++ ParameterService Python
Composable Node rclcpp_components 无等价物
Static Executor

两者均调用相同 rcl_* API,调试通信问题时应沿 rcl → rmw 链向下追踪。


18. 调试与测试

  • 日志RCLCPP_* 对应 rclpy 的 node.get_logger().info()
  • 异常:C 层错误转为 RCLError/RMWError/InvalidHandle
  • 测试test/ 下 ~40 个 pytest 文件 + C++ gtest(test_python_allocator.cpp
  • 常见问题
    • NotInitializedException — 未调用 rclpy.init()
    • 回调不触发 — 未 spin 或实体不在 Executor 管理的节点中
    • InvalidHandle — 在 with handle 外使用已销毁句柄
    • async 回调在 SingleThreadedExecutor 中需 Task 驱动(不支持裸 event loop)

19. 推荐阅读顺序

  1. impl/implementation_singleton.py + _rclpy_pybind11.cpp — 理解绑定入口
  2. context.py + context.cpp — init/shutdown
  3. node.py(构造 + create_publisher/subscription) — 用户 API
  4. publisher.py + publisher.cpp — publish 数据路径
  5. executors.py(_wait_for_ready_callbacks + _make_handler) — 调度核心
  6. callback_groups.py — 并发语义
  7. wait_set.cpp — 底层 wait
  8. parameter_service.py + parameter.py — 参数栈
  9. time_source.py — sim time
  10. action/client.py + action_client.cpp — Action 与 Waitable
  11. lifecycle/node.py — 生命周期
  12. 对照 rcl 源码分析rclcpp 源码分析

20. 小结

rclpy 是 ROS 2 Python 应用的主 API 层,采用 Python 调度 + pybind11 C 绑定 双层架构:

  • Python 层node.pyexecutors.py)负责 Entity 管理、Executor 调度、async 回调、参数服务
  • C 扩展_rclpy_pybind11)负责 rcl_* 调用、消息转换、WaitSet、Action/Lifecycle 绑定
  • Destroyable + with handle 保证多线程 wait 期间句柄安全
  • Task/async 是 rclpy 相对 rclcpp 的显著差异,支持 coroutine 风格节点

掌握 _wait_for_ready_callbacks_make_handler → Task 执行 链路,是理解 rclpy 并发模型的关键。

rcl 源码详细分析

rcl 源码详细分析

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

rcl(ROS Client Library)是 ROS 2 客户端库的 C 实现层,封装 rmw(ROS Middleware Interface),为 rclcpprclpy 及其他语言绑定提供语言无关、稳定 ABI 边界的统一 C API。所有 DDS/中间件细节都在 rcl 以下;调试通信问题时常需追到 rcl → rmw 返回值链。


1. 总体认识

1.1 核心职责

能力 说明
生命周期 rcl_init() / rcl_shutdown() / rcl_context_t 管理
节点 创建/销毁 node,namespace 校验,logger 名生成
Pub/Sub Publisher/Subscription 封装,topic 展开与 remap
Service/Client 请求-响应 RPC 封装
Timer 基于 clock + guard_condition 的定时器
Wait Set 聚合 subscription/service/client/timer/event,调用 rmw_wait()
Graph 节点/topic/service introspection(ros2 topic list 底层)
参数解析 解析 --remap--params-file 等 CLI 参数
Action / Lifecycle 独立子包,基于 rcl 的 pub/sub/service 组合实现

1.2 在 ROS 2 栈中的位置

语言客户端rcl 仓库下层rclcpprclpy / _rclpyrcl 核心rcl_actionrcl_lifecyclercl_yaml_param_parserrmw_implementationrcutilsrosidl_runtime_cFast-DDS / CycloneDDS
上层消费者 使用的 rcl 能力
rclcpp 全部核心 API + rcl_action + rcl_lifecycle
rclpy 通过 C 扩展调用相同 API
ros2cli 间接通过 rclcpp/rclpy 使用 graph API
launch 通过节点进程 CLI 参数触发 rcl_parse_arguments()

注意参数服务get_parameters 等)的实现主体在 rclcpp/rclpy,不在 rcl 核心库内。rcl 只负责解析 --params-file YAML(通过 rcl_yaml_param_parser),供上层加载初始参数。


2. 子包结构

1
2
3
4
5
6
7
rcl/
├── rcl/ # 核心库 ★
│ ├── include/rcl/ # 37 个公开头文件
│ └── src/rcl/ # 32 个 .c 实现(~11.5K 行)
├── rcl_action/ # Action C API
├── rcl_lifecycle/ # Lifecycle 状态机
└── rcl_yaml_param_parser/ # YAML 参数文件解析
版本 职责
rcl 5.3.13 init、node、pub/sub、service/client、timer、wait、graph、arguments
rcl_action 5.3.13 Action client/server、goal handle、goal 状态机
rcl_lifecycle 5.3.13 Lifecycle 状态/转移/默认状态机
rcl_yaml_param_parser 5.3.13 解析 --params-file YAML → rcl_params_t

2.1 依赖关系(rcl/package.xml

1
2
3
4
5
6
7
8
rcl
├── rmw_implementation # 运行时加载的 RMW 实现
├── rcutils # 日志、错误、分配器、时间
├── rosidl_runtime_c # 消息 typesupport 接口
├── rcl_interfaces # 内置服务/msg 定义
├── rcl_yaml_param_parser # YAML 参数解析
├── rcl_logging_spdlog # 默认日志后端
└── tracetools # LTTng 追踪点

3. 模块划分(官方文档)

rcl.h 是总入口,按 ROS 概念组织 API:

1
2
3
4
5
6
7
8
9
* - Nodes          → rcl/node.h
* - Publisher → rcl/publisher.h
* - Subscription → rcl/subscription.h
* - Service Client → rcl/client.h
* - Service Server → rcl/service.h
* - Timer → rcl/timer.h
* - Wait sets → rcl/wait.h
* - Graph → rcl/graph.h
* - Init/Shutdown → rcl/init.h

辅助模块:

头文件 职责
context.h rcl_context_t 生命周期
init_options.h init 选项(domain_id、allocator 等)
arguments.h / remap.h CLI 参数、remap 规则
guard_condition.h 异步唤醒 wait set
event.h QoS 事件(deadline、liveliness 等)
time.h ROS Time / Steady Time / System Time
logging.h / logging_rosout.h 日志与 /rosout 发布
security.h 从环境变量加载安全选项
domain_id.h / localhost.h ROS_DOMAIN_IDROS_LOCALHOST_ONLY
validate_topic_name.h / expand_topic_name.h topic 名校验与展开
error_handling.h / types.h 错误码与返回值
allocator.h 可注入的内存分配器

4. 核心数据结构

4.1 rcl_context_t — 进程级上下文

1
2
3
4
5
6
7
typedef struct rcl_context_s
{
/// Global arguments for all nodes which share this context.
rcl_arguments_t global_arguments;

/// Implementation specific pointer.
rcl_context_impl_t * impl;

rcl_context_impl_t 内部持有:

1
2
3
4
5
6
7
8
struct rcl_context_impl_s
{
rcl_allocator_t allocator;
rcl_init_options_t init_options;
int64_t argc;
char ** argv;
rmw_context_t rmw_context;
};

生命周期:

1
zero-init → rcl_init() → [valid] → rcl_shutdown() → [invalid] → 销毁所有 entity → rcl_context_fini()
  • 一个 context 可创建多个 node(共享同一 rmw_context
  • rcl_shutdown() 后 context 仍”已初始化但无效”,entity 可继续 cleanup
  • 支持多 context(多 init 场景,如测试)

4.2 rcl_node_t — 节点

1
2
3
4
5
6
7
8
struct rcl_node_impl_s
{
rcl_node_options_t options;
rmw_node_t * rmw_node_handle;
rcl_guard_condition_t * graph_guard_condition;
const char * logger_name;
const char * fq_name;
};
  • rmw_node_handle:RMW 层节点句柄
  • graph_guard_condition:图变化时唤醒 wait set(新 publisher 出现等)
  • logger_name:如 /a/b 命名空间下节点 c → logger 名 a.b.c
  • fq_name:完全限定名

4.3 rcl_wait_set_t — 等待集合

1
2
3
4
5
6
7
8
typedef struct rcl_wait_set_s
{
const rcl_subscription_t ** subscriptions;
size_t size_of_subscriptions;
const rcl_guard_condition_t ** guard_conditions;
// ... timers, clients, services, events
rcl_wait_set_impl_t * impl;
} rcl_wait_set_t;

内部 rcl_wait_set_impl_t 聚合对应的 rmw_subscriptions_trmw_guard_conditions_t 等,最终调用 rmw_wait()


5. 关键流程

5.1 初始化 — rcl_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
rcl_ret_t rcl_init(int argc, char const * const * argv,
const rcl_init_options_t * options, rcl_context_t * context)
{
// 1. 分配 context->impl
// 2. 复制 argc/argv
// 3. rcl_parse_arguments() — 解析 --remap, --params-file, --enclave 等
// 4. 设置 instance_id(全局唯一)
// 5. 解析 ROS_DOMAIN_ID(若未指定)
// 6. 解析 ROS_LOCALHOST_ONLY
// 7. 设置 enclave 名(默认 "/")
// 8. rcl_get_security_options_from_environment()
// 9. rmw_init() — 初始化中间件
}

要点:

步骤 函数 说明
参数解析 rcl_parse_arguments() 最大文件(2079 行),处理所有 ROS CLI 参数
Domain ID rcl_get_default_domain_id() ROS_DOMAIN_ID 环境变量
安全 rcl_get_security_options_from_environment() SROS2 证书路径
中间件 rmw_init() 进入 Fast-DDS/CycloneDDS

5.2 创建节点 — rcl_node_init()

1
2
3
4
5
6
7
8
9
10
11
12
rcl_ret_t rcl_node_init(rcl_node_t * node, const char * name,
const char * namespace_, rcl_context_t * context,
const rcl_node_options_t * options)
{
// 1. 校验 node name / namespace(rmw_validate_*)
// 2. namespace 规范化(空 → "/",无前缀 → 加 "/")
// 3. remap node name / namespace
// 4. rmw_create_node()
// 5. 创建 graph_guard_condition
// 6. 生成 logger_name, fq_name
// 7. rcl_logging_rosout_init() — 可选 /rosout 发布
}

5.3 发布 — rcl_publisher_init() + rcl_publish()

初始化

1
2
3
4
5
6
rcl_ret_t rcl_publisher_init(...)
{
// 1. rcl_node_resolve_name() — 展开相对名 + remap
// 2. rmw_create_publisher(node, type_support, remapped_topic, qos)
// 3. rmw_publisher_get_actual_qos() — 存储实际 QoS
}

发布

1
2
3
4
5
6
7
8
9
10
11
rcl_ret_t rcl_publish(const rcl_publisher_t * publisher,
const void * ros_message,
rmw_publisher_allocation_t * allocation)
{
TRACEPOINT(rcl_publish, ...);
if (rmw_publish(publisher->impl->rmw_handle, ros_message, allocation) != RMW_RET_OK) {
RCL_SET_ERROR_MSG(rmw_get_error_string().str);
return RCL_RET_ERROR;
}
return RCL_RET_OK;
}

其他 publish 变体:

函数 用途
rcl_publish_serialized_message() 直接发已序列化字节
rcl_publish_loaned_message() 零拷贝 loaned buffer
rcl_borrow_loaned_message() 从 RMW 借出写入 buffer

5.4 订阅 — rcl_subscription_init() + rcl_take()

取消息

1
2
3
4
5
6
7
8
9
rcl_ret_t rcl_take(const rcl_subscription_t * subscription,
void * ros_message, rmw_message_info_t * message_info,
rmw_subscription_allocation_t * allocation)
{
bool taken = false;
rmw_ret_t ret = rmw_take_with_info(
subscription->impl->rmw_handle, ros_message, &taken, message_info_local, allocation);
// taken == false → RCL_RET_SUBSCRIPTION_TAKE_FAILED
}
函数 说明
rcl_take() 取走后从队列移除
rcl_take_sequence() 批量取
rcl_take_serialized_message() 取原始字节
rcl_take_loaned_message() 零拷贝取
rcl_subscription_set_content_filter() DDS ContentFilteredTopic

5.5 等待与调度 — rcl_wait()

rclcpp::Executor::spin() 底层即循环调用 rcl_wait()

1
2
3
4
5
6
7
8
rcl_wait(rcl_wait_set_t * wait_set, int64_t timeout)
{
// 1. 校验 wait_set 非空
// 2. 遍历 timers → 计算最近到期时间 → 合并到 timeout
// 3. 将 timer 的 guard_condition 加入 rmw_guard_conditions
// 4. rmw_wait(subscriptions, guard_conditions, services, clients, events, timeout)
// 5. 返回后,未 ready 的 entity 指针置 NULL
}

Wait set 使用模式:

1
2
3
4
5
6
7
8
9
10
rcl_wait_set_t ws = rcl_get_zero_initialized_wait_set();
rcl_wait_set_init(&ws, n_subs, n_gc, n_timers, n_clients, n_services, n_events, context, allocator);

// 循环:
rcl_wait_set_clear(&ws);
rcl_wait_set_add_subscription(&ws, sub);
rcl_wait_set_add_timer(&ws, timer);
rcl_wait(&ws, timeout); // 阻塞
// 检查 ws.subscriptions[i] 是否仍为 non-NULL → ready
rcl_take(sub, msg, ...);

5.6 Timer

1
2
3
4
5
6
7
8
9
10
11
struct rcl_timer_impl_s
{
rcl_clock_t * clock;
rcl_context_t * context;
rcl_guard_condition_t guard_condition; // 到期时触发
atomic_uintptr_t callback;
atomic_uint_least64_t period;
atomic_int_least64_t next_call_time;
atomic_bool canceled;
rcl_allocator_t allocator;
};
  • Timer 不独立线程,依赖 rcl_wait() 检测 guard_condition 或超时
  • 支持 RCL_ROS_TIME 跳变(sim time 切换时的 credit 机制)
  • rcl_timer_call() 手动触发回调

5.7 Service / Client

与 pub/sub 对称:

操作 Service 端 Client 端
创建 rcl_service_init()rmw_create_service() rcl_client_init()rmw_create_client()
收发 rmw_take_request() / rmw_send_response() rmw_send_request() / rmw_take_response()
Wait 加入 rcl_wait_set 加入 rcl_wait_set

5.8 Graph API

路径:src/rcl/graph.c(744 行)

函数 用途
rcl_get_node_names() 列出所有节点名
rcl_get_topic_names_and_types() 所有 topic 及类型
rcl_get_service_names_and_types() 所有 service
rcl_count_publishers() / rcl_count_subscribers() 某 topic 的 pub/sub 数量
rcl_get_publisher_names_and_types_by_node() 某节点的 publisher 列表
rcl_get_subscriber_names_and_types_by_node() 某节点的 subscription 列表

底层全部转发到 rmw_get_* 系列函数。ROS 2 CLI 的 ros2 topic listros2 node list 最终依赖这些 API。


6. 参数与 Remap

6.1 arguments.c — CLI 解析核心

最大源文件(2079 行),处理:

CLI 参数 解析结果
--remap __ns:=/foo remap 规则
--remap __node:=bar 节点名 remap
--remap from:=to topic/service remap
--params-file file.yaml 参数文件路径
-p name:=value 参数覆盖
--enclave /my_enclave 安全 enclave

Remap 规则解析使用 lexer.c(676 行)+ lexer_lookahead.c 做 token 化。

6.2 rcl_yaml_param_parser

路径:rcl_yaml_param_parser/src/

文件 行数 职责
parse.c 1037 YAML 词法/语法解析
parser.c 449 公开 API
yaml_variant.c 177 参数值类型(bool/int/double/string/array)
namespace.c 命名空间处理
node_params.c 145 按节点组织参数

YAML 格式:

1
2
3
4
5
6
/my_node:
ros__parameters:
param1: 42
param2: "hello"
nested:
sub_param: true

解析结果存入 rcl_params_t,由 rcl_arguments 持有,上层(rclcpp)在 node 创建时读取。


7. rcl_action — Action 子包

路径:rcl_action/src/rcl_action/

Action 在 rcl 层不引入新中间件概念,而是将 Action 协议映射为 topics + services 组合:

Action 概念 底层实现
SendGoal service ~/action_name/_action/send_goal
CancelGoal service ~/action_name/_action/cancel_goal
GetResult service ~/action_name/_action/get_result
Feedback topic ~/action_name/_action/feedback
Status topic ~/action_name/_action/status

核心 API:

头文件 职责
action_server.h / action_client.h 创建/销毁 action server/client
goal_handle.h 单个 goal 的生命周期管理
goal_state_machine.h goal 状态转移(PENDING→ACTIVE→SUCCEEDED 等)
wait.h action 专用 wait set 扩展
names.h 生成 action 相关 topic/service 名
graph.h action 图 introspection

action_server.c 中通过宏 SERVICE_INIT(Type) 批量创建 send_goal/cancel_goal/get_result 三个 service。


8. rcl_lifecycle — Lifecycle 子包

路径:rcl_lifecycle/src/

提供纯 C 状态机,不含网络通信——上层(rclcpp_lifecycle)负责将状态变化发布到 ~/transition_event topic 和 ~/change_state service。

文件 职责
default_state_machine.c 预置状态(Unconfigured/Inactive/Active/Finalized)和转移
transition_map.c 自定义状态机映射
rcl_lifecycle.c 状态 init/trigger/get 等 API
com_interface.c lifecycle_msgs 的转换

核心类型:rcl_lifecycle_state_trcl_lifecycle_transition_trcl_lifecycle_state_machine_t


9. 错误处理

C 风格,无异常

机制 说明
返回值 rcl_ret_tRCL_RET_OK = 0,其余为错误码)
错误消息 RCL_SET_ERROR_MSG() → 线程局部字符串
读取 rcl_get_error_string()
RMW 转换 rcl_convert_rmw_ret_to_rcl_ret()

常见错误码(types.h):

范围 示例
通用 RCL_RET_OKRCL_RET_ERRORRCL_RET_BAD_ALLOC
Context RCL_RET_ALREADY_INITRCL_RET_NOT_INITRCL_RET_ALREADY_SHUTDOWN
Node RCL_RET_NODE_INVALID_NAMERCL_RET_NODE_INVALID_NAMESPACE
Pub/Sub RCL_RET_PUBLISHER_INVALIDRCL_RET_SUBSCRIPTION_TAKE_FAILED
Wait RCL_RET_WAIT_SET_EMPTYRCL_RET_TIMEOUT
Timer RCL_RET_TIMER_CANCELED

10. 源文件布局与规模

10.1 rcl 核心(按行数排序)

文件 行数 职责
arguments.c 2079 CLI 参数/remap/params 解析
subscription.c 783 订阅 init/take/loan/filter
graph.c 744 图 introspection
lexer.c 676 remap 规则词法分析
wait.c 672 wait set + rcl_wait()
node.c 541 节点 init/fini/resolve_name
time.c 475 时钟类型与时间跳变
timer.c 473 定时器
publisher.c 455 发布 init/publish/loan
init.c 260 init/shutdown
service.c / client.c ~350 服务端/客户端
remap.c ~300 remap 规则应用
guard_condition.c ~200 guard condition
event.c ~200 QoS 事件
logging*.c ~400 日志与 rosout

10.2 内部头文件(src/rcl/ 私有)

文件 用途
context_impl.h context 内部结构
init_options_impl.h init 选项内部结构
arguments_impl.h 参数解析内部结构
publisher_impl.h / subscription_impl.h pub/sub 内部结构
remap_impl.h remap 规则存储
common.h 公共辅助函数

11. 完整数据路径

11.1 发布一条消息

1
2
3
4
5
6
rclcpp::Publisher::publish(msg)
→ rcl_publish(publisher, &msg, allocation)
→ rmw_publish(rmw_handle, &msg, allocation) [rmw_fastrtps]
→ DataWriter::write(msg)
→ TypeSupport::serialize() [Fast-CDR]
→ RTPS 发送

11.2 Executor spin 一次迭代

1
2
3
4
5
6
7
8
9
10
11
12
rclcpp::Executor::spin_some()
→ rcl_wait_set_clear/add_*
→ rcl_wait(wait_set, timeout)
→ rmw_wait(subs, gcs, services, clients, events, timeout)
→ DDS WaitSet 阻塞
→ 对每个 ready subscription:
→ rcl_take(sub, msg, &info, allocation)
→ rmw_take_with_info(...)
→ 对每个 ready timer:
→ rcl_timer_call(timer)
→ 对每个 ready service:
→ rmw_take_request(...) → 用户回调 → rmw_send_response(...)

12. 设计特点小结

特点 说明
纯 C 无 C++ 依赖,稳定 ABI,多语言绑定基础
薄封装 大部分函数是参数校验 + rmw_* 转发
零初始化模式 所有 entity 提供 rcl_get_zero_initialized_*()
可注入分配器 rcl_allocator_t 贯穿所有 init 函数
Context 隔离 多 context 支持测试和多 init 场景
Remap 在 rcl 层 topic/node namespace 重映射对上层透明
Timer 无独立线程 依赖 wait set + guard_condition
追踪集成 TRACEPOINT(rcl_init/rcl_publish/...) via tracetools

13. 与相邻层对比

语言 职责 关键抽象
rclcpp C++ 类型安全、Executor、参数服务 Node、Publisher<T>、Executor
rcl C ROS 概念封装、CLI 解析 rcl_node_t、rcl_wait_set_t
rmw C 中间件抽象接口 rmw_publisher_t、rmw_wait
Fast-DDS C++ DDS/RTPS 实现 DomainParticipant、DataWriter

14. 推荐阅读顺序

  1. 总览include/rcl/rcl.h — 模块地图
  2. 生命周期init.ccontext.cinit_options.c
  3. 节点node.c — 理解 namespace/remap/logger
  4. 通信publisher.c + subscription.c — init/publish/take 链路
  5. 调度wait.c + timer.c + guard_condition.c — 理解 spin 底层
  6. CLIarguments.c(可按需跳读)— remap/params 解析
  7. 图 APIgraph.c
  8. 扩展rcl_action/action_server.crcl_lifecycle/rcl_lifecycle.c
  9. 上层集成rclcpp/executor.cpp — 看 rcl_wait 如何被使用

15. API 速查

15.1 最小 C 节点示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <rcl/rcl.h>

int main(int argc, char ** argv)
{
rcl_ret_t ret;
rcl_init_options_t init_options = rcl_get_zero_initialized_init_options();
rcl_init_options_init(&init_options, rcl_get_default_allocator());

rcl_context_t context = rcl_get_zero_initialized_context();
ret = rcl_init(argc, argv, &init_options, &context);

rcl_node_t node = rcl_get_zero_initialized_node();
rcl_node_options_t node_options = rcl_node_get_default_options();
rcl_node_init(&node, "my_node", "", &context, &node_options);

// ... create publisher/subscription, rcl_wait loop ...

rcl_node_fini(&node);
rcl_shutdown(&context);
rcl_context_fini(&context);
rcl_init_options_fini(&init_options);
return 0;
}

15.2 关键函数一览

类别 函数
Init rcl_init(), rcl_shutdown(), rcl_context_fini()
Node rcl_node_init(), rcl_node_fini(), rcl_node_get_name()
Pub rcl_publisher_init(), rcl_publish(), rcl_publisher_fini()
Sub rcl_subscription_init(), rcl_take(), rcl_subscription_fini()
Wait rcl_wait_set_init(), rcl_wait_set_add_*(), rcl_wait()
Timer rcl_timer_init(), rcl_timer_call(), rcl_timer_is_ready()
Graph rcl_get_node_names(), rcl_get_topic_names_and_types()
Args rcl_parse_arguments(), rcl_get_remap_rules()

文档基于 ROS 2 Humble 工作区中的 rcl 5.3.13 源码分析生成。

rcpputils 源码详细分析

rcpputils 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rcpputils
版本:2.4.6(Humble),单包仓库,构建类型 ament_cmake,语言 C++14,许可证 Apache 2.0 + BSD-3-Clause(部分文件)。

rcpputils 是 ROS 2 C++ 工具库,提供跨平台文件系统、动态库加载、字符串处理、断言/异常、RAII scope guard、线程安全注解等。被 rclcpprclpy(C++ 扩展)、rosbag2rviz2class_loader 等广泛依赖。设计上 在 rcutils C API 之上提供 C++ 友好封装(异常、std::string、模板),同时补充 rcutils 未覆盖的 C++ 专属能力(std::chrono、type traits、std::filesystem 替代)。


1. 总体认识

1.1 核心职责

类别 模块 说明
RAII / 资源 scope_exit.hpp 作用域退出时自动执行清理
动态库 shared_library.hpp 封装 rcutils_shared_library,dlopen/LoadLibrary
文件系统 filesystem_helper.hpp std::filesystem 替代(跨平台 path 操作)
库查找 find_library.hpp LD_LIBRARY_PATH 等环境变量中搜索 .so
字符串 split/join/find_and_replace.hpp 分割、拼接、替换
环境 / 进程 env.hppprocess.hpp 环境变量、可执行文件名
断言 asserts.hpp require/check/assert 三层次异常
时间 time.hpp std::chrono → 纳秒安全转换
类型 traits pointer_traits.hpp 智能指针 type traits
字节序 endian.hpp C++20 std::endian 替代
线程注解 thread_safety_annotations.hpp Clang Thread Safety Analysis 宏
符号导出 visibility_control.hpp RCPPUTILS_PUBLIC
数学 rcppmath/clamp.hpprolling_mean_accumulator.hpp 钳制、滑动均值

1.2 在 ROS 2 栈中的位置

C++ 上层rcpputilsC 工具层rclcpprosbag2rviz2rclpy C++ 扩展rcpputils / rcppmathrcutilsOS / libc
对比 rcutils rcpputils
语言 C C++
日志/错误/分配器 核心实现 不重复,依赖 rcutils
文件系统 rcutils/filesystem.h(C) fs::path 等 C++ 风格 API
动态库 rcutils/shared_library.h SharedLibrary 类 + 异常
环境变量 rcutils/env.h get_env_var() 返回 std::string
适用层 rcl、rmw、rosidl C 运行时 rclcpp 及 C++ 工具/应用

分工原则:需要 C ABI 稳定性的放 rcutils;C++ 便利性与模板工具放 rcpputils。两者不应重复实现同一逻辑——rcpputils 的 .cpp 实现大多委托 rcutils。


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
rcpputils/
├── include/
│ ├── rcpputils/ # 主命名空间(17 个头文件 + tl_expected/)
│ │ ├── filesystem_helper.hpp # 359 行,最大公开头
│ │ ├── thread_safety_annotations.hpp
│ │ ├── scope_exit.hpp
│ │ ├── shared_library.hpp
│ │ ├── split.hpp / join.hpp / find_and_replace.hpp
│ │ ├── asserts.hpp / env.hpp / find_library.hpp
│ │ ├── pointer_traits.hpp / time.hpp / endian.hpp
│ │ ├── process.hpp / visibility_control.hpp
│ │ ├── get_env.hpp # 已废弃,转发 env.hpp
│ │ └── tl_expected/expected.hpp # vendored(~2300 行,CC0)
│ └── rcppmath/ # 数学工具命名空间
│ ├── clamp.hpp
│ └── rolling_mean_accumulator.hpp
├── src/ # 5 个编译单元(~781 行)
│ ├── filesystem_helper.cpp # 494 行
│ ├── shared_library.cpp # 114 行
│ ├── find_library.cpp # 86 行
│ ├── env.cpp # 61 行
│ └── asserts.cpp # 38 行
├── test/ # 16 个 gtest
├── docs/FEATURES.md
├── CMakeLists.txt
└── package.xml

2.1 编译 vs 头文件-only

类型 文件 说明
编译进 librcpputils.so 5 个 .cpp 需链接 -lrcpputils
头文件-only scope_exitsplitjoinclamptimepointer_traitsendianprocess #include 即可
vendored 头 tl_expected/expected.hpp C++17 std::expected 替代,lint 排除

CMake 导出:ament_export_libraries(rcpputils)ament_export_dependencies(rcutils)


3. 依赖关系

1
2
rcpputils
└── rcutils # 唯一运行时依赖

构建工具:ament_cmakeament_cmake_rosament_cmake_gen_version_h

C++ 标准:C++14(注释说明待 CXX20 可用后移除 tl_expected)。


4. 核心模块详解

4.1 scope_exit — RAII 清理

ROS 2 C++ 代码中最常用的 rcpputils 特性之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename CallableT>
struct scope_exit final
{
~scope_exit()
{
if (!cancelled_) {
callable_();
}
}
void cancel() { cancelled_ = true; }
...
};

#define RCPPUTILS_SCOPE_EXIT(code) \
auto RCUTILS_JOIN(scope_exit_, __LINE__) = rcpputils::make_scope_exit([&]() {code;})
API 用途
make_scope_exit(fn) 创建 scope guard
cancel() 资源已手动释放时取消自动清理
RCPPUTILS_SCOPE_EXIT(...) 宏简写(rclcpp executor、rosbag2 测试广泛使用)

典型场景:测试里确保进程/线程在退出时 join,或临时修改状态后恢复。

4.2 SharedLibrary — 动态库加载

C++ 封装 rcutils_shared_library_t

1
2
3
4
5
6
7
8
9
10
SharedLibrary::SharedLibrary(const std::string & library_path)
{
lib = rcutils_get_zero_initialized_shared_library();
rcutils_ret_t ret = rcutils_load_shared_library(
&lib, library_path.c_str(), rcutils_get_default_allocator());
if (ret != RCUTILS_RET_OK) {
...
throw std::runtime_error{rcutils_error_str};
}
}
方法 底层
构造 / 析构 rcutils_load/unload_shared_library
get_symbol() rcutils_get_symbol
has_symbol() rcutils_has_symbol
get_platform_library_name() rcutils_get_platform_library_name

下游关键用法rclcpp/typesupport_helpers.cpp 运行时加载 rosidl_typesupport_cpp 共享库以解析消息 typesupport:

1
2
3
const std::string library_path = rcpputils::path_for_library(
package_prefix + dynamic_library_folder,
package_name + "__" + typesupport_identifier);

4.3 find_library — 库路径搜索

1
2
3
4
5
6
7
8
9
10
11
12
std::string find_library_path(const std::string & library_name)
{
std::string search_path = get_env_var(kPathVar); // LD_LIBRARY_PATH / PATH / DYLD_...
std::vector<std::string> search_paths = rcpputils::split(search_path, kPathSeparator);
std::string filename = filename_for_library(library_name); // libfoo.so
for (const auto & search_path : search_paths) {
if (rcutils_is_file((search_path + "/" + filename).c_str())) {
return path;
}
}
return "";
}
函数 作用
find_library_path(name) 在 OS 库搜索路径中查找
path_for_library(dir, name) 在指定目录查找
filename_for_library(name) 生成平台库文件名(lib*.so / .dll / .dylib

4.4 filesystem_helper — 跨平台文件系统

源自 pluginlibfilesystem_helper,在 ROS 2 全平台尚未统一支持 std::filesystem 时提供替代。

命名空间rcpputils::fs

类型 / 函数 能力
fs::path 路径拼接 /exists()is_directory()parent_path()extension()
temp_directory_path() 临时目录(Windows: GetTempPathA;Unix: $TMPDIR/tmp
create_temp_directory(base, parent) 唯一临时目录(mkdtemp 风格)
create_directories(p) 递归创建目录
remove / remove_all 删除文件或目录树
current_path() 当前工作目录
copy / rename 文件复制与重命名

实现(494 行)调用 rcutils 与平台 API(statmkdirremove 等)。

下游:rosbag2 测试临时目录、rviz 配置文件路径、rclcpp 测试资源路径等。

4.5 字符串工具

头文件 函数 说明
split.hpp split(input, delim, skip_empty) 返回 vector<string> 或写入迭代器
join.hpp join(container, delim) 容器元素拼接
find_and_replace.hpp find_and_replace(str, from, to) 全局替换

下游示例

  • rclcpp_components/component_manager.cpp — 解析 ament index 组件清单(split by \n / ;
  • rosbag2_transport/topic_filter.cpp — topic 名 token 化
  • rclpy/node.cpp — 参数名 wildcard 正则构造(find_and_replace

4.6 env / process

env.cpp — 封装 rcutils 环境 API:

1
2
3
4
5
6
7
8
9
10
11
std::string get_env_var(const char * env_var)
{
const char * err = rcutils_get_env(env_var, &value);
if (err) throw std::runtime_error(err);
return value ? value : "";
}

bool set_env_var(const char * env_var, const char * env_value)
{
if (!rcutils_set_env(env_var, env_value)) { ... throw ... }
}

process.hpp — 头文件 inline 函数,调用 rcutils_get_executable_name() 返回 std::string

4.7 asserts — 三层次校验

函数 异常类型 行为
require_true(cond, msg) std::invalid_argument 校验输入参数
check_true(cond, msg) IllegalStateException 校验内部状态
assert_true(cond, msg) AssertionException 校验结果Release 下为 no-opNDEBUG

下游rclcpp/serialization.cpp 在序列化前 check_true 指针非空。

4.8 time.hpp — chrono 转换

1
2
3
4
5
6
7
8
template<typename DurationRepT, typename DurationT>
std::chrono::nanoseconds convert_to_nanoseconds(
const std::chrono::duration<DurationRepT, DurationT> & time)
{
if (time > ns_max_as_double) throw std::invalid_argument{...};
if (time < ns_min_as_double) throw std::invalid_argument{...};
return std::chrono::duration_cast<std::chrono::nanoseconds>(time);
}

防止 duration_cast 静默溢出,供 Executor 超时等场景使用。

4.9 pointer_traits — 智能指针 traits

扩展标准库:

  • rcpputils::is_pointer<T> — 识别 raw / shared_ptr / unique_ptr
  • rcpputils::remove_pointer<T> — 从任意指针类型提取 pointee

用于模板 API 同时接受 raw 与 smart pointer。

4.10 endian.hpp

  • C++17 以上:直接使用 std::endian
  • 否则:自定义 enum class endian { little, big, native }(Linux 用 <endian.h>

供序列化/网络字节序相关代码使用。

4.11 thread_safety_annotations.hpp

Clang Thread Safety Analysis 宏(GUARDED_BYLOCKS_EXCLUDED 等)。非 Clang 编译器下展开为空,跨平台安全。

测试构建时对 Clang 启用 -Wthread-safety -Werror

4.12 visibility_control.hpp

定义 RCPPUTILS_PUBLIC / RCPPUTILS_LOCAL / RCPPUTILS_BUILDING_DLL,控制 Windows DLL 导出与 Unix 默认 visibility。


5. rcppmath 命名空间

rcpputils 同包分发,独立命名空间 rcppmath

5.1 clamp.hpp

1
2
rcppmath::clamp(value, low, high);
rcppmath::clamp(value, low, high, Compare); // 自定义比较

C++14 时代尚未有标准 std::clamp(C++17)时的替代;行为与标准库一致。

5.2 RollingMeanAccumulator

滑动窗口均值累加器(替代 boost rolling mean,避免 boost 依赖):

1
2
void accumulate(T val) { ... O(1) 更新 sum_ ... }
T getRollingMean() const { return sum_ / valid_data_count; }

环形缓冲区 + 运行和,窗口满后 O(1) 更新。


6. tl_expected(vendored)

include/rcpputils/tl_expected/expected.hppTartanLlama/tl::expected 的 vendored 拷贝(CC0 公共领域)。

提供 C++17 风格的 expected<T, E>(类似 Rust Result),供尚未使用 C++23 std::expected 的代码使用。CMake lint 明确排除此文件。


7. 实现与 rcutils 委托关系

rcpputils C++ APIrcutils C APISharedLibraryget_env_varfs::pathfind_library_pathrcutils_shared_libraryrcutils_get/set_envrcutils_filesystem
rcpputils 委托 rcutils / OS
SharedLibrary rcutils_load/unload/get_symbol
get_env_var / set_env_var rcutils_get_env / rcutils_set_env
fs::* rcutils_is_file/is_directory + 平台 syscall
find_library_path rcutils_is_file + 环境变量路径
get_executable_name rcutils_get_executable_name

错误处理模式:rcutils 返回码 + 线程局部错误链 → rcpputils 提取 rcutils_get_error_string()throw std::runtime_error(SharedLibrary/env),或返回 bool/空字符串(find_library)。


8. 下游消费者

包 / 模块 使用的 rcpputils 能力
rclcpp SharedLibrarypath_for_library(typesupport 动态加载)、check_true(序列化)、RCPPUTILS_SCOPE_EXIT(executor/action)
rclcpp_components splitfs::path(解析组件资源)
rclpy(C++) find_and_replace(参数 wildcard)
rosbag2 fs::pathcreate_temp_directoryremove_allmake_scope_exitsplit
rviz2 fs::path
class_loader / pluginlib 历史上 filesystem/split 源码同源,现多直接使用 rcpputils

package.xml 声明依赖 rcpputils 的包:rclcpprclpyrosbag2 等。


9. 测试

test/ 下 16 个 gtest,与模块一一对应:

测试 覆盖
test_asserts debug/release 下 assert_true 行为差异
test_shared_library 加载 dummy .so、symbol 查找
test_filesystem_helper path 操作、临时目录
test_find_library LD_LIBRARY_PATH 搜索
test_env 空/正常环境变量
test_scope_exit cancel 与析构调用
test_thread_safety_annotations Clang 注解编译
test_clamp / test_accumulator rcppmath

10. 与 rcutils 文档对照

详见 rcutils 源码详细分析

需求场景 应使用
rcl/rmw C 代码 rcutils
rclcpp / C++ 工具 rcpputils(必要时仍可直接调 rcutils)
仅头文件 split/join #include rcpputils/split.hpp,无需链接
动态加载 typesupport #include rcpputils/shared_library.hpp,链接 rcpputils
日志 / 分配器 / C 错误链 rcutils(rcpputils 不提供)

11. 设计特点与演进

  1. 轻量:仅 5 个 .cpp,其余 header-only,降低链接开销
  2. 跨平台优先:filesystem、endian、库名前缀/后缀均处理 Windows/macOS/Linux 差异
  3. 渐进废弃get_env.hppenv.hppfs::path 待全平台 C++17 后或迁移至 std::filesystem
  4. Quality Level 1:见 QUALITY_DECLARATION.md,完整 CI lint + 测试
  5. 不重复 rcutils:新 C 级能力应先进 rcutils,C++ 包装再进 rcpputils

12. 推荐阅读顺序

  1. docs/FEATURES.md — 官方功能清单与示例
  2. scope_exit.hpp — 理解 ROS 2 C++ 中最常见的用法
  3. shared_library.cpp + find_library.cpp — 动态加载链(连接 rclcpp typesupport)
  4. filesystem_helper.hpp + .cpp — 最大模块,跨平台 path
  5. asserts.hpp — 三种断言语义
  6. split.hpp / join.hpp — 字符串工具模式
  7. rcppmath/* — 数学小工具
  8. 对照 rclcpp/typesupport_helpers.cpp — 真实集成场景
  9. 对照 rcutils 分析 — 理解 C/C++ 分层

13. 小结

rcpputils 是 ROS 2 C++ 生态的基础工具层,位于 rcutils 之上、rclcpp 之下:

  • 编译库(5 个源文件)提供 filesystem、shared_library、find_library、env、asserts
  • 头文件库提供 scope guard、字符串、chrono、traits、endian、线程注解
  • rcppmath 提供 clamp 与滑动均值
  • 核心模式:rcutils C API + C++ 异常/string + 跨平台 shim

日常阅读 rclcpp/rosbag2 代码时,常见 #include "rcpputils/..."RCPPUTILS_SCOPE_EXIT;排查动态 typesupport 加载问题时,应沿 path_for_librarySharedLibrary → rcutils 链分析。

rcutils 源码详细分析

rcutils 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rcutils
版本:5.1.8(Humble),单包仓库,构建类型 ament_cmake,语言 C11,许可证 Apache 2.0

rcutils 是 ROS 2 最底层 C 工具库:日志、线程局部错误链、可注入分配器、时间、文件系统、动态库加载、字符串/容器数据结构、环境变量等。被 rclrmwrosidl_runtime_crcpputils 及几乎所有 ROS 2 C/C++ 组件依赖。不含 ROS 语义(无 topic/node/DDS),却是整个栈的公共基础设施。


1. 总体认识

1.1 核心职责

类别 模块 说明
分配器 allocator.h 统一 malloc/free/realloc,支持测试注入
错误处理 error_handling.h 线程局部错误链,RCUTILS_SET_ERROR_MSG
日志 logging.h + logging_macros.h 分级日志、可替换 output handler
时间 time.h system/steady 时钟,纳秒时间戳
文件系统 filesystem.h exists/is_file/mkdir/cwd 等
动态库 shared_library.h dlopen/LoadLibrary 封装
环境变量 env.h get/set env
字符串 split/repl_str/format_string/snprintf 分割、替换、格式化
容器 types/* char_array、string_map、hash_map、array_list 等
进程 process.h PID、可执行文件名
CLI cmdline_parser.h 简单命令行选项解析
测试 testing/fault_injection.h 故障注入框架

1.2 在 ROS 2 栈中的位置

应用 / 工具中间层基础层rclcpprclpy C 扩展rosbag2 / rviz2rclrmw / rmw_implementationrosidl_runtime_crcutilsrcpputilslibc / OS API
上层 典型 rcutils 用法
rcl 日志、RCUTILS_SET_ERROR_MSG、allocator、time
rmw 错误链、logging、filesystem
rosidl_runtime_c allocator、字符串
rcpputils shared_library、env、filesystem 的 C 底层
rclcpp 经 rcl/rcl_logging 间接使用;RCLCPP_* 宏最终到 rcutils logging

与 rcpputils 的分工:C ABI 与跨语言边界能力在 rcutils;C++ 便利封装在 rcpputils。读 rcl 源码前宜先熟悉 loggingerror_handling


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
rcutils/
├── include/rcutils/ # 35 个公开头文件
│ ├── logging.h # 542 行
│ ├── error_handling.h # 321 行
│ ├── filesystem.h # 300 行
│ ├── types/ # 容器与返回码
│ │ ├── rcutils_ret.h
│ │ ├── char_array.h
│ │ ├── string_map.h / hash_map.h
│ │ ├── string_array.h / array_list.h
│ │ └── uint8_array.h
│ ├── stdatomic_helper/ # C11 原子跨平台
│ └── testing/fault_injection.h
├── include/rcutils/logging_macros.h # ★ 构建时由 empy 生成
├── src/ # 27 个 .c(~10.6K 行)
│ ├── logging.c # 1000 行,最大单文件
│ ├── filesystem.c # 543 行
│ ├── hash_map.c / string_map.c
│ ├── error_handling.c # 264 行
│ ├── shared_library.c # 351 行
│ ├── time.c + time_unix.c / time_win32.c
│ └── testing/fault_injection.c
├── resource/logging_macros.h.em # 日志宏模板
├── rcutils/logging.py # empy 生成脚本输入
├── test/ # 大量 gtest + launch 测试
└── CMakeLists.txt

2.1 源码规模

指标 数量
公开头文件 ~35
C 源文件 27
src/*.c 总行数 ~10,635
最大实现文件 logging.c(1000 行)

3. 依赖关系

1
2
rcutils
└── libatomic(可选,非 Windows 下检测 __atomic_load_8)

rcl、rmw、rosidl 依赖——刻意保持最底层。构建工具:ament_cmake_rospython3-empy(生成 logging_macros.h)。


4. 核心设计:可注入分配器

几乎所有需动态内存的 API 接受 rcutils_allocator_t

1
2
3
4
5
6
7
8
typedef struct rcutils_allocator_s
{
void * (*allocate)(size_t size, void * state);
void (* deallocate)(void * pointer, void * state);
void * (*reallocate)(void * pointer, size_t size, void * state);
void * (*zero_allocate)(size_t number_of_elements, size_t size_of_element, void * state);
void * state;
} rcutils_allocator_t;
API 作用
rcutils_get_default_allocator() malloc/free/realloc/calloc
rcutils_get_zero_initialized_allocator() 全零函数指针(无效)
rcutils_allocator_is_valid() 校验四函数非 NULL
rcutils_reallocf() realloc 失败时 free 旧指针

设计动机:单元测试可注入 fault-injection / 计数 allocator,检测泄漏与分配失败路径。


5. 错误处理(error_handling)

2017 年从 rmw/error_handling.c 迁移至 rcutils,供 rcl/rmw 统一使用。

5.1 线程局部错误链

1
2
3
4
RCUTILS_THREAD_LOCAL bool gtls_rcutils_thread_local_initialized = false;
RCUTILS_THREAD_LOCAL rcutils_error_state_t gtls_rcutils_error_state;
RCUTILS_THREAD_LOCAL rcutils_error_string_t gtls_rcutils_error_string;
RCUTILS_THREAD_LOCAL bool gtls_rcutils_error_is_set = false;
API / 宏 作用
RCUTILS_SET_ERROR_MSG(...) 设置错误消息 + 文件/行号
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(...) 格式化错误消息
rcutils_get_error_state() 获取结构化错误(message、file、line_number)
rcutils_get_error_string() 格式化字符串(含链式 “at file:line”)
rcutils_error_is_set() 是否有未处理错误
rcutils_reset_error() 必须在处理后调用,避免泄漏

5.2 典型用法(rcl/rmw 模式)

1
2
3
4
5
6
rcl_ret_t ret = rcl_something(...);
if (ret != RCL_RET_OK) {
const char * msg = rcutils_get_error_string().str;
// 记录或向上传递
rcutils_reset_error();
}

错误消息有长度上限(RCUTILS_ERROR_MESSAGE_MAX_LENGTH 1024),链式错误会截断。

5.3 与返回值码的关系

  • rcutils_ret_t:函数直接返回的操作结果(见 §6)
  • 错误链:函数返回 RCUTILS_RET_*RCL_RET_* 时,额外通过 TLS 携带人类可读说明

6. 返回码(rcutils_ret.h)

1
2
3
4
5
6
7
8
9
10
11
typedef int rcutils_ret_t;

#define RCUTILS_RET_OK 0
#define RCUTILS_RET_WARN 1
#define RCUTILS_RET_ERROR 2
#define RCUTILS_RET_BAD_ALLOC 10
#define RCUTILS_RET_INVALID_ARGUMENT 11
#define RCUTILS_RET_NOT_ENOUGH_SPACE 12
#define RCUTILS_RET_NOT_INITIALIZED 13
#define RCUTILS_RET_NOT_FOUND 14
// string_map / logging / hash_map 专用码 ...

rcl/rmw 定义各自的 rcl_ret_t / rmw_ret_t,但错误字符串机制统一走 rcutils。


7. 日志系统(logging)

最大模块(logging.c 1000 行 + 生成宏)。

7.1 初始化与 output handler

1
2
3
4
bool g_rcutils_logging_initialized = false;
static rcutils_allocator_t g_rcutils_logging_allocator;
rcutils_logging_output_handler_t g_rcutils_logging_output_handler = NULL;
static rcutils_string_map_t g_rcutils_logging_severities_map;
函数 作用
rcutils_logging_initialize_with_allocator() 初始化 severity map、解析环境变量
rcutils_logging_shutdown() 释放资源
rcutils_logging_set_output_handler() 替换输出(默认 rcutils_logging_console_output_handler
rcutils_logging_set_logger_level() 按 logger 名设置阈值
rcutils_logging_logger_is_enabled_for() 过滤低 severity

7.2 rcutils_log 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void rcutils_log(
const rcutils_log_location_t * location,
int severity, const char * name, const char * format, ...)
{
if (!rcutils_logging_logger_is_enabled_for(name, severity)) {
return;
}
rcutils_system_time_now(&now);
output_handler = g_rcutils_logging_output_handler;
if (output_handler != NULL) {
va_start(args, format);
(*output_handler)(location, severity, name, now, format, &args);
va_end(args);
}
}

7.3 日志宏(构建时生成)

resource/logging_macros.h.em + rcutils/logging.pyinclude/rcutils/logging_macros.h

宏族 示例
基础 RCUTILS_LOG_DEBUG/INFO/WARN/ERROR/FATAL
命名 RCUTILS_LOG_INFO_NAMED(name, ...)
条件 RCUTILS_LOG_DEBUG_ONCERCUTILS_LOG_WARN_SKIPFIRST
节流 RCUTILS_LOG_ERROR_THROTTLE

编译期可通过 RCUTILS_LOG_MIN_SEVERITY 剔除低级别日志。

7.4 环境变量

变量 作用
RCUTILS_CONSOLE_OUTPUT_FORMAT 输出格式 token({severity}{name}{message}{time} 等)
RCUTILS_COLORIZED_OUTPUT 0/1 强制禁用/启用颜色
RCUTILS_LOGGING_USE_STDOUT 输出到 stdout 而非 stderr
RCUTILS_LOGGING_BUFFERED_STREAM 缓冲策略
RCUTILS_CONSOLE_STDOUT_LINE_BUFFERED 行缓冲

默认格式:[{severity}] [{time}] [{name}]: {message}

7.5 与 RCLCPP 的关系

1
2
3
4
RCLCPP_INFO(logger, "msg")
→ rcl/rcl_logging
→ rcutils_log() / RCUTILS_LOG_*
→ g_rcutils_logging_output_handler (console 或 spdlog 后端)

8. 时间(time.h)

类型 说明
rcutils_time_point_value_t int64_t 纳秒时间戳
rcutils_duration_value_t int64_t 纳秒时长
API 对应 C++
rcutils_system_time_now() std::chrono::system_clock
rcutils_steady_time_now() std::chrono::steady_clock

平台实现:

  • Unix:time_unix.c
  • Windows:time_win32.c
  • 公共逻辑:time.c(格式化、sleep 等)

宏:RCUTILS_S_TO_NSRCUTILS_NS_TO_S 等。


9. 文件系统(filesystem.h / filesystem.c)

C 风格路径 API(非 C++ fs::path):

函数 作用
rcutils_exists / rcutils_is_file / rcutils_is_directory 路径类型检测
rcutils_is_readable / rcutils_is_writable 权限
rcutils_get_cwd 当前工作目录
rcutils_expand_user ~ 展开
rcutils_mkdir 创建目录
rcutils_calculate_directory_size 目录大小
rcutils_get_file_size 文件大小

rcpputils 的 fs::path 在此基础上提供 C++ 封装。


10. 动态库(shared_library.h / shared_library.c)

1
2
3
4
5
6
typedef struct rcutils_shared_library_s
{
void * lib_pointer;
char * library_path;
rcutils_allocator_t allocator;
} rcutils_shared_library_t;
API 作用
rcutils_load_shared_library dlopen / LoadLibrary
rcutils_unload_shared_library 卸载
rcutils_get_symbol / rcutils_has_symbol dlsym
rcutils_get_platform_library_name libfoo.so / foo.dll

下游:rmw 加载 DDS 实现、rclcpp typesupport 动态加载、pluginlib/class_loader。

CMake 链接 ${CMAKE_DL_LIBS}


11. 环境变量(env.h / env.c)

API 说明
rcutils_get_env(name, &value) 读取;value 指向内部存储,下次调用失效
rcutils_set_env(name, value) 设置/取消;Windows 空串行为与 Unix 不同
rcutils_get_home_dir() 用户主目录

rcpputils get_env_var() 在此基础上抛 C++ 异常。


12. 字符串与文本工具

头文件 / 源文件 API 说明
split.h / split.c rcutils_splitrcutils_split_last 按 delimiter 分割,需 allocator
repl_str.h / repl_str.c rcutils_repl_str 子串替换
format_string.h rcutils_format_string snprintf + allocate(默认限 2048)
snprintf.h rcutils_snprintf 安全 snprintf 封装
strdup.h rcutils_strdup 带 allocator 的 strdup
strerror.h rcutils_strerror 线程安全 strerror
strcasecmp.h rcutils_strcasecmp 大小写无关比较
find.h / find.c rcutils_findrcutils_find_last 字符搜索
isalnum_no_locale.h rcutils_isalnum 不受 locale 影响的 isalnum

13. 容器数据结构(types/)

均支持 allocator 注入,部分使用 PIMPL(impl 指针)。

类型 文件 用途
rcutils_char_array_t char_array.c 可增长 char 缓冲区(日志格式化)
rcutils_string_map_t string_map.c 字符串键值 map(logger severity map)
rcutils_hash_map_t hash_map.c 通用 hash map
rcutils_array_list_t array_list.c 动态数组
rcutils_string_array_t string_array.c 字符串数组
rcutils_uint8_array_t uint8_array.c 字节数组

string_map 在 logging 中存储 logger 名 → severity 映射。


14. 其他模块

模块 说明
cmdline_parser rcutils_cli_option_existrcutils_cli_get_option
process rcutils_get_pidrcutils_get_executable_name
qsort rcutils_qsort — 可注入 comparator 的排序
macros.h RCUTILS_WARN_UNUSEDRCUTILS_THREAD_LOCAL、join 宏
visibility_control.h RCUTILS_PUBLIC DLL 导出
stdatomic_helper GCC/Win32 原子操作 shim
get_env.h 已废弃,转发 env.h

15. 测试与 fault_injection

15.1 fault_injection

RCUTILS_ENABLE_FAULT_INJECTION 编译开关(测试构建默认开启):

  • RCUTILS_FAULT_INJECTION_TEST({ ... }) — 在分配/错误路径注入失败
  • 用于覆盖 RCUTILS_RET_BAD_ALLOC 等分支

15.2 测试覆盖

类别 示例
单元测试 test_logging、test_error_handling、test_filesystem
生成宏 lint cppcheck/cpplint/uncrustify 对 logging_macros.h
launch 测试 长消息、输出格式 Python 验证
benchmark benchmark_logging、benchmark_error_handling
shared_library RUNPATH / LD_LIBRARY_PATH / preload 三种加载场景

Quality Level 1:见 QUALITY_DECLARATION.md


16. 构建要点

  1. logging_macros.h 生成:CMake add_custom_command 调用 Python empy 展开 .em 模板
  2. 平台 time 源文件WIN32time_win32.c,否则 time_unix.c
  3. GNU 源:Linux glibc 下 -D_GNU_SOURCE
  4. libatomic:检测并链接 -latomic(某些架构 64 位原子)
  5. Python 包ament_python_install_package(rcutils) 安装 logging.py 供构建使用

17. 日志数据路径

output_handlerseverity 过滤rcutils_logRCUTILS_LOG_* 宏rcl / rmw / 用户output_handlerseverity 过滤rcutils_logRCUTILS_LOG_* 宏rcl / rmw / 用户alt[通过]RCUTILS_LOG_INFO(...)rcutils_log(location, severity, name, fmt, ...)logger_is_enabled_for?rcutils_system_time_nowhandler(location, severity, name, time, fmt, va_list)格式化 token → stderr/stdout

18. 与 rcpputils 对照

能力 rcutils rcpputils
语言 C C++
日志 完整实现 无(用 rcutils)
错误链 TLS error state 无(捕获 rcutils 错误转 exception)
文件系统 C bool 返回值 API fs::path C++ API
动态库 rcutils_shared_library_t SharedLibrary
环境变量 rcutils_get_env get_env_var() → string
split/join rcutils_split + allocator header-only 模板

详见 rcpputils 源码详细分析


19. 下游依赖概览

依赖 rcutils 的典型场景
rcl 全程:init、logging、error、allocator
rmw 实现加载、错误、日志
rosidl_runtime_c 序列化 buffer、字符串
rcpputils shared_library、env、filesystem 底层
rcl_logging_spdlog 替换 rcutils output handler
rclpy C 扩展经 rcl 间接使用

20. 推荐阅读顺序

  1. allocator.h + types/rcutils_ret.h — 理解注入模式与返回码
  2. error_handling.h + error_handling.c — TLS 错误链(读 rcl 必备)
  3. logging.h + logging.c(rcutils_log、console handler) — 日志管线
  4. resource/logging_macros.h.em — 宏如何展开
  5. time.h + time_unix.c — 时钟抽象
  6. filesystem.h + shared_library.c — OS 交互
  7. types/char_array.h + types/string_map.h — 日志内部数据结构
  8. testing/fault_injection.h — 测试如何覆盖失败路径
  9. 对照 rcl 源码分析 中 error/logging 调用点

21. 小结

rcutils 是 ROS 2 C 栈的 零语义公共库,核心模式为:

  • allocator 注入 — 所有动态结构可测试、可定制
  • 线程局部错误链SET_ERROR_MSG + get_error_string + reset_error
  • 可替换 logging handler — 从 console 到 spdlog 的扩展点
  • 平台 shim — time、filesystem、shared_library、stdatomic 统一 Unix/Windows

不含 QoS/YAML/ROS 图等高层概念;这些在 rclrcl_yaml_param_parserrmw 中实现。调试 ROS 2 问题时,沿 上层 ret 码 → rcutils_get_error_string() 追根因是基本技能。

realtime_support 源码详细分析

realtime_support 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/realtime_support
版本:0.13.0(Humble),子包 2 个,构建类型 ament_cmake,许可证 Apache 2.0tlsf_cpp 另含 LGPL 2.1,因底层 TLSF)。

realtime_support 仓库提供 ROS 2 实时性能支撑工具:面向 Linux(尤其 PREEMPT_RT 内核)的周期循环 测量库 rttest,以及将 TLSF 确定性内存分配器 接入 rclcpp 的 tlsf_cpp 包装。二者不实现 DDS/Executor 本身,而是帮助验证与控制 抖动(jitter)、缺页、调度优先级、堆分配延迟 等实时因素。


1. 总体认识

1.1 核心职责

子包 职责
rttest 周期性 clock_nanosleep 唤醒,测量 latency/jitter、pagefault,导出 CSV
tlsf_cpp tlsf_heap_allocator — C++ STL/rclcpp 兼容的 TLSF 堆分配器

1.2 在 ROS 2 栈中的位置

示例 / 验证realtime_supportROS 2 核心外部demos/pendulum_controlrttesttlsf_cpprclcppExecutor / MemoryStrategytlsf 包Linux PREEMPT_RT / sched / mlock
关系 说明
tlsf_cpp → tlsf 调用 init_memory_pool / tlsf_malloc / tlsf_free(见 tlsf 源码分析
tlsf_cpp → rclcpp 示例/测试将 TLSF 注入 Publisher/Subscription/Executor MemoryStrategy
rttest 独立 C API;pendulum demo 用于统计周期性能
非 ROS 核心路径 普通节点不依赖本仓库;仅实时调优/演示场景使用

平台限制:两个子包 CMake 均在 Windows / macOS / Android 上 skip,仅 Linux 构建。


2. 子包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
realtime_support/
├── rttest/ # 实时循环测量库 ★
│ ├── include/rttest/
│ │ ├── rttest.h # C API(206 行)
│ │ ├── utils.hpp # timespec 算术
│ │ └── math_utils.hpp # 标准差计算
│ ├── src/rttest.cpp # 实现(942 行)
│ ├── examples/example_loop.c # 最小示例
│ ├── scripts/
│ │ ├── rttest_plot # 绘图脚本
│ │ └── analyze.py # 延迟统计
│ └── test/
└── tlsf_cpp/ # TLSF C++ 包装 ★
├── include/tlsf_cpp/tlsf.hpp # allocator(140 行)
├── example/allocator_example.cpp
└── test/test_tlsf.cpp
版本 构建产物
rttest 0.13.0 共享库 librttest.so、头文件、rttest_plot
tlsf_cpp 0.13.0 INTERFACE 库(仅头文件)+ tlsf_allocator_example 可执行文件

3. 依赖关系

3.1 rttest

1
2
3
4
rttest
├── (无 ROS 依赖)
├── pthread / m / stdc++
└── Linux: mlockall, clock_nanosleep, sched_setscheduler, getrusage

package.xmlament_cmake + 测试依赖。

3.2 tlsf_cpp

1
2
3
4
5
tlsf_cpp
├── tlsf # vendor 包,C TLSF 实现
├── rclcpp # 示例与测试
├── rmw / std_msgs
└── rmw_implementation_cmake # 测试多 RMW

4. rttest 详解

4.1 设计目标

固定周期任务(control loop、hard real-time thread)提供:

  1. 绝对时间唤醒clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)
  2. Jitter 记录 — 期望唤醒时刻 vs 实际唤醒时刻(纳秒,可正可负)
  3. 缺页统计 — 每轮 getrusage(RUSAGE_THREAD) 增量
  4. 内存锁定 / 预缺页 — 减少运行时 page fault
  5. SCHED_FIFO / SCHED_RR — 实时调度策略与优先级

面向 PREEMPT_RT 等低延迟 Linux 环境。

4.2 核心数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct rttest_params
{
size_t iterations;
struct timespec update_period;
size_t sched_policy;
int sched_priority;
size_t stack_size;
uint64_t prefault_dynamic_size;
char * filename;
};

struct rttest_results
{
size_t iteration;
int64_t min_latency;
int64_t max_latency;
double mean_latency;
double latency_stddev;
size_t minor_pagefaults;
size_t major_pagefaults;
};

内部 rttest_sample_buffer 按 iteration 存储 latency_samplesminor_pagefaultsmajor_pagefaults 向量。

4.3 多线程模型

1
2
std::map<pthread_t, Rttest> rttest_instance_map;
pthread_t initial_thread_id = 0;
  • 每 pthread 一个 Rttest 实例(注释:rttest can have one instance per thread)
  • 首线程 rttest_init / rttest_read_args 后,其他线程调用 rttest_init_new_thread() 复制参数

C API 通过 pthread_self() 路由到当前线程实例。

4.4 典型使用流程

1
2
3
4
5
6
7
8
// 见 examples/example_loop.c
rttest_set_sched_priority(98, SCHED_RR);
rttest_read_args(argc, argv); // 或 rttest_init(...)
rttest_lock_memory(); // mlockall
rttest_lock_and_prefault_dynamic(); // 堆预缺页
rttest_spin(callback, NULL); // 周期循环
rttest_write_results();
rttest_finish();

4.5 spin 与 jitter 测量

1
2
3
4
5
6
7
8
9
10
11
12
13
int Rttest::spin_once(..., const size_t i)
{
multiply_timespec(update_period, i, &wakeup_time);
add_timespecs(start_time, &wakeup_time, &wakeup_time);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wakeup_time, NULL);
clock_gettime(CLOCK_MONOTONIC, &current_time);

this->record_jitter(&wakeup_time, &current_time, i);
user_function(args);
this->get_next_rusage(i);
this->accumulate_statistics(i);
return 0;
}

Jitter 符号:晚于 deadline 为正,早于 deadline 为负(record_jitter 中 parity)。

rttest_spinspin_period:iterations=0 时无限循环(不保存完整 sample buffer,无法写文件)。

4.6 内存锁定与预缺页

API 实现要点
rttest_lock_memory() mlockall(MCL_CURRENT | MCL_FUTURE)
rttest_prefault_stack() alloca + memset 触摸栈页
rttest_lock_and_prefault_dynamic() mallopt(M_TRIM_THRESHOLD,-1)M_MMAP_MAX=0,循环 new char[] 直到无新 pagefault

动态预缺页默认上限 8GB-d 可调),失败时恢复 malloc 参数并 munlockall

4.7 命令行参数(rttest_read_args

选项 含义 默认
-u 更新周期(s/ms/us/ns 1ms
-i 迭代次数(≤0 为无限) 1000
-t 线程优先级 80
-s 调度策略 fifo / rr SCHED_RR
-m 栈预缺页大小 1MB
-d 堆预缺页大小 8192MB
-f 结果输出文件名 不写文件

4.8 结果输出

write_results_file 写入文本 CSV:

1
iteration timestamp latency minor_pagefaults major_pagefaults
  • scripts/analyze.py — 打印 min/max/mean latency、超 30µs 样本数
  • scripts/rttest_plot — 安装到 bin,配合 -f 输出绘图

4.9 辅助头文件

文件 内容
utils.hpp add_timespecssubtract_timespecstimespec_to_uint64
math_utils.hpp calculate_stddev 模板

pendulum_control 等 demo 复用 utils.hpp 做时间运算。


5. tlsf_cpp 详解

5.1 设计目标

T TLSF(Two-Level Segregated Fit) 分配器具有 O(1) malloc/free 与 低碎片,适合实时线程。tlsf vendor 包提供 C 实现;tlsf_cpp 将其包装为 符合 std::allocator_traits 的 C++ 分配器,供 rclcpp 消息与 Executor 使用。

5.2 tlsf_heap_allocator

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
template<typename T, size_t DefaultPoolSize = 1024 * 1024>
struct tlsf_heap_allocator
{
using value_type = T;

explicit tlsf_heap_allocator(size_t size) { initialize(size); }
tlsf_heap_allocator() { initialize(DefaultPoolSize); }

size_t initialize(size_t size) {
memory_pool = new char[pool_size];
memset(memory_pool, 0, pool_size);
init_memory_pool(pool_size, memory_pool);
return pool_size;
}

T * allocate(size_t size) {
T * ptr = static_cast<T *>(tlsf_malloc(size * sizeof(T)));
if (ptr == NULL && size > 0) throw std::bad_alloc();
return ptr;
}

void deallocate(T * ptr, size_t) { tlsf_free(ptr); }

char * memory_pool;
size_t pool_size;
};

要点:

  • 默认池大小 1MB(模板参数 DefaultPoolSize
  • 同一 memory_pool 的 allocator 实例 operator== 为 true(可互相 deallocate)
  • 析构时 destroy_memory_pool

5.3 与 rclcpp 集成模式

example/allocator_example.cpp 展示完整注入链:

tlsf_heap_allocator voidPublisherOptions.allocatorSubscriptionOptions.allocatorMessageMemoryStrategyAllocatorMemoryStrategySingleThreadedExecutorNODE
注入点 作用
PublisherOptionsWithAllocator 发布路径分配
SubscriptionOptionsWithAllocator 订阅路径分配
MessageMemoryStrategy 消息 buffer
AllocatorMemoryStrategy Executor wait/collect 路径
AllocRebind + custom deleter publish(std::move(unique_ptr)) 正确释放

启动参数 intra 等可切换 use_intra_process_comms,对比进程内/跨进程分配行为。

5.4 测试

test/test_tlsf.cpp 通过 call_for_each_rmw_implementation 对每个 RMW 跑 gtest,验证 TLSF 下 pub/sub + spin 正常。


6. 下游消费者

使用
demos/pendulum_control rttest_read_argsrttest_lock_and_prefault_dynamictlsf_heap_allocator、发布 pendulum_msgs/RttestResults
(示例) tlsf_allocator_example 二进制

pendulum_demo.cpprttest + tlsf_cpp + rclcpp 集成的参考实现:实时线程跑 pendulum 控制,同时统计 rttest 结果并通过 ROS topic 输出。


7. 实时实践要点(与本仓库相关)

技术 rttest API 目的
内存锁定 rttest_lock_memory 防止进程内存被 swap
栈预缺页 rttest_prefault_stack 避免栈 growth 缺页
堆预缺页 rttest_lock_and_prefault_dynamic 避免控制循环中 malloc 缺页
实时调度 rttest_set_sched_priority FIFO/RR 高优先级
确定性分配 tlsf_heap_allocator 替代 glibc malloc 的无界延迟
周期测量 rttest_spin 量化 jitter 与 pagefault

注意:rclcpp 默认仍使用系统分配器;要使用 TLSF 需 显式 配置 allocator 与 memory strategy(如 pendulum / allocator_example)。


8. 构建与安装

8.1 rttest

  • 输出 librttest(SHARED)
  • 安装 include/rttest/bin/rttest_plot
  • 非 ament 环境支持纯 CMake 安装(rttestConfig.cmake.in

8.2 tlsf_cpp

  • INTERFACE 库,无 .so;依赖 tlsf::tlsf
  • 安装头文件 include/tlsf_cpp/tlsf.hpp
  • 构建 tlsf_allocator_examplelib/tlsf_cpp/tlsf_allocator_example

9. 数据路径:rttest 一次迭代

getrusageuser_functionrecord_jitterclock_nanosleepspin_oncegetrusageuser_functionrecord_jitterclock_nanosleepspin_once计算 wakeup_time = start + i * periodTIMER_ABSTIME 绝对睡眠唤醒clock_gettime比较 deadline vs actual用户回调记录 minor/major pagefault 增量accumulate_statistics

10. 与相关包对照

层级 关系
tlsf C 分配器 tlsf_cpp 底层
rclcpp 客户端库 MemoryStrategy / 消息 allocator 注入点
rcpputils C++ 工具 无直接关系
realtime_support 实时支撑 测量 + TLSF 包装

11. 推荐阅读顺序

  1. rttest/README.md — CLI 与使用说明
  2. rttest/examples/example_loop.c — 最小集成
  3. rttest/src/rttest.cppspin_oncelock_and_prefault_dynamicread_args
  4. rttest/include/rttest/rttest.h — 完整 C API
  5. tlsf_cpp/include/tlsf_cpp/tlsf.hpp — allocator 接口
  6. tlsf_cpp/example/allocator_example.cpp — rclcpp 注入示例
  7. demos/pendulum_control/src/pendulum_demo.cpp — 生产级 demo 集成
  8. tlsf 源码分析 — 底层池化算法

12. 小结

realtime_support 是 ROS 2 实时性工具链,含两个互补子包:

  • rttest — Linux 周期唤醒 ** instrumentation**:jitter、pagefault、调度、内存锁定;942 行 C++ 实现 + C 头 API
  • tlsf_cpp — 将 TLSF 接入 rclcpp allocator / MemoryStrategy,实现可预测的堆分配

二者均 非默认 ROS 运行时依赖,主要用于 PREEMPT_RT 验证、pendulum 演示、实时内存策略实验。排查控制循环延迟时,应结合 rttest 输出与是否启用 TLSF 分配器一并分析。

rmw_connextdds 源码详细分析

rmw_connextdds 源码详细分析

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

rmw_connextdds 是 RTI 提供的 ROS 2 RMW 实现,将标准 rmw_* C API 映射到 RTI Connext DDS Professionalrmw_connextdds)或 RTI Connext DDS Micrormw_connextddsmicro)。它替代旧版 rmw_connext_cpp,在性能与跨厂商互操作方面做了大量改进。

外部依赖:本仓库 不包含 Connext DDS 本体。构建与运行需安装 RTI Connext,并通过 CONNEXTDDS_DIR / NDDSHOME(Professional)或 RTIMEHOME(Micro)指向安装路径;未检测到安装时对应包会被 跳过编译

上游 README:rmw_connextdds


1. 总体认识

1.1 核心职责

能力 说明
RMW 符号实现 导出全部 rmw_* 函数(经薄封装层)
DDS 实体管理 DomainParticipant、Publisher/Subscriber、DataWriter/DataReader
类型系统 基于 Fast-CDR + rosidl typesupport 注册 DDS 类型(Pro 用 TypePlugin/TypeCode)
Graph 发现 集成 rmw_dds_common,维护 ROS 2 图缓存
Service/Client DDS-RPC basic/extended 映射,可配置跨厂商互操作
QoS 策略 ROS QoS ↔ Connext QoS,支持 XML profile 与运行时环境变量
双后端共享 rmw_connextdds_common 承载 ~16K 行公共 C++ 逻辑

1.2 在 ROS 2 栈中的位置

上层rmw_connextdds 仓库ROS 共享RTI 产品RMW_CONNEXT_DDS_API_PRORMW_CONNEXT_DDS_API_MICROrclrmw_connextdds\n薄 API 层rmw_connextddsmicro\n薄 API 层rmw_connextdds_common\n核心实现rti_connext_dds_cmake_modulermw 头文件/类型rmw_dds_commonrosidl typesupportConnext DDS ProfessionalConnext DDS Micro
对比项 rmw_connextdds rmw_cyclonedds_cpp rmw_fastrtps_cpp
DDS 产品 RTI Connext Pro/Micro Eclipse CycloneDDS eProsima Fast-DDS
实现语言 C++ C++ C++
Graph 共享 rmw_dds_common rmw_dds_common 自有 + 部分 common
序列化 Fast-CDR typesupport 自有 CDR Fast-CDR
默认 Humble RMW 否(需显式选择) 可选

1.3 分层设计(读源码入口)

1
2
3
4
5
rmw.h 符号
└── rmw_connextdds/src/rmw_api_impl_ndds.cpp ← 仅转发,~977 行
└── rmw_api_connextdds_*() ← 参数校验 + 日志
└── RMW_Connext_* 类 / 自由函数 ← rmw_impl.hpp / rmw_impl.cpp
└── DDS C API ← dds_api_ndds.hpp 或 dds_api_rtime.hpp

Micro 版将第一层替换为 rmw_connextddsmicro/src/rmw_api_impl_rtime.cpp,链接 rmw_connextdds_common_micro 而非 _pro


2. 子包结构

1
2
3
4
5
6
7
8
9
10
11
12
rmw_connextdds/
├── rti_connext_dds_cmake_module/ # 查找 Connext 安装、env hook
├── rmw_connextdds_common/ # 共享实现 ★ (~16K 行)
│ ├── include/rmw_connextdds/ # 18 个头文件
│ └── src/
│ ├── common/ # 19 个 .cpp(RMW 逻辑)
│ ├── ndds/ # Professional 专用(TypePlugin、CFT)
│ └── rtime/ # Micro 专用 + rtime_ext.c
├── rmw_connextdds/ # Pro RMW 注册包
│ └── src/rmw_api_impl_ndds.cpp
└── rmw_connextddsmicro/ # Micro RMW 注册包
└── src/rmw_api_impl_rtime.cpp
版本 产出 职责
rti_connext_dds_cmake_module CMake 模块 rti_find_connextpro()rti_find_connextmicro()
rmw_connextdds_common 0.11.6 librmw_connextdds_common_pro / _micro 全部 RMW 逻辑(编译宏区分后端)
rmw_connextdds 0.11.6 librmw_connextdds.so 导出 rmw_*,identifier="rmw_connextdds"
rmw_connextddsmicro 0.11.6 librmw_connextddsmicro.so 同上,identifier="rmw_connextddsmicro"

2.1 依赖关系(rmw_connextdds_common/package.xml

1
2
3
4
5
6
7
8
9
rmw_connextdds_common
├── rmw # API 契约
├── rmw_dds_common # Graph 缓存、ParticipantEntitiesInfo
├── rcutils / rcpputils # 日志、错误、scope_exit
├── fastcdr # CDR 序列化
├── rosidl_runtime_c/cpp
├── rosidl_typesupport_fastrtps_c/cpp
├── rosidl_typesupport_introspection_c/cpp
└── rti_connext_dds_cmake_module

Typesupport 注册(rmw_connextdds/CMakeLists.txt):

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

与 Cyclone/FastRTPS 后端一样,同时支持 fastrtpsintrospection 两套 typesupport。


3. 构建系统

3.1 条件编译与跳过

rmw_connextdds_common/CMakeLists.txt 通过 rti_find_connextpro() / rti_find_connextmicro() 探测安装:

  • 找到 Pro → 构建 rmw_connextdds_common_pro(链 RTIConnextDDS::c_api
  • 找到 Micro → 构建 rmw_connextdds_common_micro + 辅助库 rti_connextdds_micro_ext
  • 两者皆无 → 仅 ament_package(),不产出 RMW 库

rmw_connextdds 包额外检查 TARGET rmw_connextdds_common::rmw_connextdds_common_pro,不存在则跳过。

3.2 编译宏区分后端

Pro Micro
RMW_CONNEXT_DDS_API RMW_CONNEXT_DDS_API_PRO RMW_CONNEXT_DDS_API_MICRO
RMW_CONNEXTDDS_ID "rmw_connextdds" "rmw_connextddsmicro"
默认 RPC 映射 Extended Basic
Security 默认启用编译选项 可选 RMW_CONNEXT_ENABLE_SECURITY
大对象优化 默认开 同 common 逻辑

dds_api.hpp 根据宏 include 不同头文件:

1
2
3
4
5
#if RMW_CONNEXT_DDS_API == RMW_CONNEXT_DDS_API_MICRO
#include "rmw_connextdds/dds_api_rtime.hpp"
#elif RMW_CONNEXT_DDS_API == RMW_CONNEXT_DDS_API_PRO
#include "rmw_connextdds/dds_api_ndds.hpp"
#endif

3.3 Connext 路径变量

变量 用途
CONNEXTDDS_DIR Pro 安装路径(优先于 NDDSHOME
NDDSHOME Pro 传统变量;apt 版 rmw_connext_cpp 可能硬编码
RTIMEHOME Micro 安装路径;Pro 6.x 可自动猜测 bundled Micro

rti_connext_dds_cmake_module 还提供 env_hook/rti_connext_dds.sh.in 等脚本,在 source install/setup.bash 时注入库路径。


4. 核心数据结构

4.1 rmw_context_impl_t

context.hpp 中定义(对应 rmw/init.h 中的不透明 rmw_context_impl_t):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct rmw_context_impl_s {
rmw_dds_common::Context common; // graph 缓存、discovery pub/sub
rmw_context_t * base;

DDS_DomainParticipantFactory * factory;
DDS_DomainId_t domain_id;
DDS_DomainParticipant * participant;
DDS_Publisher * dds_pub; // ROS publisher 共用
DDS_Subscriber * dds_sub; // ROS subscription 共用

/* Built-in Discovery Readers (DCPSParticipant/Publication/Subscription) */
DDS_DataReader * dr_participants;
DDS_DataReader * dr_publications;
DDS_DataReader * dr_subscriptions;

bool localhost_only;
RMW_Connext_RequestReplyMapping request_reply_mapping;
bool cyclone_compatible;
// participant/endpoint QoS override 策略、initial_peers、类型注册表...
std::map<std::string, RMW_Connext_MessageTypeSupport *> registered_types;
size_t node_count;
std::mutex initialization_mutex;
bool is_shutdown;
};

设计要点

  • 延迟创建 Participant:第一个 rmw_create_node 时才真正创建并 enable DomainParticipant(node_count 从 0→1)。
  • 同一 context 内所有 node 必须 localhost_only 一致,否则 initialize_node 失败。
  • rmw_dds_common::Context 嵌入 graph 状态,与 Cyclone/FastRTPS 新实现路径一致。

4.2 实体实现类(rmw_impl.hpp

C++ 类 对应 rmw 句柄 DDS 实体
RMW_Connext_Node rmw_node_t 逻辑节点(无独立 Participant)
RMW_Connext_Publisher rmw_publisher_t Topic + DataWriter
RMW_Connext_Subscriber rmw_subscription_t Topic + DataReader
RMW_Connext_Service rmw_service_t 请求 Subscription + 响应 Publisher
RMW_Connext_Client rmw_client_t 请求 Publisher + 响应 Subscription
RMW_Connext_GuardCondition rmw_guard_condition_t DDS GuardCondition
RMW_Connext_StdWaitSet rmw_wait_set_t DDS WaitSet + 条件 attach 管理

每个 rmw_*_t->data 指向对应 C++ 对象;implementation_identifierRMW_CONNEXTDDS_ID

4.3 全局工厂引用

1
2
DDS_DomainParticipantFactory * RMW_Connext_gv_DomainParticipantFactory = nullptr;
size_t RMW_Connext_gv_ContextCount = 0;

首个 context init 时设置工厂 QoS(autoenable_created_entities = false);最后一个 context fini 且所有 node 销毁后重置。注释指出 context 创建非线程安全(与 rcl 层假设一致)。


5. 生命周期

5.1 rmw_init 流程(rmw_context.cpp

1
2
3
4
5
6
7
rmw_api_connextdds_init()
├── 解析环境变量(QoS override、RPC 映射、Cyclone 兼容等)
├── rmw_dds_common::Context 初始化
├── 创建/复用 DomainParticipantFactory
├── rmw_connextdds_graph_initialize() // discovery pub/sub
├── 启动 discovery 线程(DCPS + ParticipantEntitiesInfo)
└── 填充 rmw_context_t(implementation_identifier、domain_id)

rmw_shutdown / rmw_context_fini 逆序:停止 discovery 线程 → 销毁 graph 实体 → finalize participant → 递减全局计数。

5.2 Node 与 Participant 关系

与 CycloneDDS RMW 类似,多个 ROS node 共享一个 DomainParticipant(同一 rmw_context_t):

  • RMW_Connext_Node::create 仅增加 node_count 并注册 graph
  • Participant 在首个 node 创建时 initialize_participant + enable_participant
  • 最后一个 node destroy 且 count 归零时 tear down participant

6. Topic 命名与 Demangle

6.1 ROS → DDS Topic 映射

前缀常量(demangle.cpp / rmw_impl.cpp):

ROS 语义 前缀 示例
Topic rt /foort/foo
Service 请求 rq + Request 后缀 /addrq/addRequest
Service 响应 rr + Response 后缀 /addrr/addResponse

RMW_Connext_Publisher::create 中:

  • 若 topic 已含 rq/rr/ 前缀,直接使用(service 内部 topic)
  • 否则调用 rmw_connextdds_create_topic_name(ROS_TOPIC_PREFIX, topic_name, ...)

avoid_ros_namespace_conventions=true 时跳过 rt 前缀,用于直连原生 DDS topic。

6.2 Demangle(demangle.cpp

  • _demangle_if_ros_topic:去掉 rt/rq/rr/ 恢复 ROS 图名
  • _demangle_if_ros_type:将 pkg::msg::dds_::Type_ 还原为 ROS 类型名
  • 供 graph introspection、ros2 topic list 等使用

6.3 内部 Discovery Topic

rmw_graph.cpp 创建内部 publisher/subscription:

  • Topic 名:ros_discovery_infoavoid_ros_namespace_conventions=true
  • 消息类型:rmw_dds_common::msg::ParticipantEntitiesInfo
  • QoS:TRANSIENT_LOCAL + RELIABLE(pub depth=1,sub KEEP_ALL)

7. 类型支持与序列化

7.1 RMW_Connext_MessageTypeSupport

核心类(type_support.hpp),使用 Connext 代码生成器,而是:

  1. rosidl_message_type_support_tfastrtps typesupportmessage_type_support_callbacks_t
  2. 用 introspection 计算 _serialized_size_max、是否 unbounded
  3. Pro 版:构建 PRESTypePlugin + DDS TypeCode(rmw_type_support_ndds.cpp
  4. Micro 版:简化类型注册(rmw_type_support_rtime.cpp

消息类别枚举:

1
2
3
4
5
enum RMW_Connext_MessageType {
RMW_CONNEXT_MESSAGE_USERDATA, // 普通 pub/sub
RMW_CONNEXT_MESSAGE_REQUEST, // service/client 请求
RMW_CONNEXT_MESSAGE_REPLY // service/client 响应
};

Service 的 request/reply 在 CDR 前附加 RequestReply 头RMW_Connext_RequestReplyMessage:gid + sequence number),映射方式由 request_reply_mapping 控制。

7.2 序列化格式

1
2
// ndds/dds_api_ndds.cpp
const char * const RMW_CONNEXTDDS_SERIALIZATION_FORMAT = "cdr";

rmw_serialize / rmw_deserializermw_serde.cpp)临时构造 RMW_Connext_MessageTypeSupport,调用 Fast-CDR 回调写入 rmw_serialized_message_t

rmw_get_serialized_message_size 当前返回 RMW_RET_UNSUPPORTED

7.3 Professional 特有:Content Filter Topic

custom_sql_filter.cpp + rmw_connextdds_create_contentfilteredtopic 实现 Content Filtered Topic(订阅端内容过滤),对应 rmw_subscription_set_content_filter。Micro 版部分 CFT API 返回 unsupported。

7.4 大消息优化

_serialized_size_max >= 1MBRMW_CONNEXT_LARGE_DATA_MIN_SERIALIZED_SIZE)且未禁用优化时:

  • 调整 RTPS reliability 协议参数(heartbeat 周期、send window)
  • Pro 版默认 DDS_ASYNCHRONOUS_PUBLISH_MODE_QOS(可用 RMW_CONNEXT_USE_DEFAULT_PUBLISH_MODE 关闭)

8. 发布与订阅数据路径

8.1 发布

1
2
3
4
rmw_api_connextdds_publish()
→ RMW_Connext_Publisher::write(ros_message, serialized=false)
→ MessageTypeSupport::serialize() // Fast-CDR
→ DDS_DataWriter_write()

rmw_publish_serialized_message 传入已序列化 buffer,跳过 ros 消息序列化步骤。

8.2 订阅

1
2
3
4
5
rmw_api_connextdds_take() / take_with_info()
→ RMW_Connext_Subscriber::take()
→ DDS_DataReader_take()
→ deserialize → ros_message
→ 填充 rmw_message_info_t(source_timestamp、publication_handle 等)

还支持 take_serialized_messagetake_sequenceloaned message 系列 API 返回 RMW_RET_UNSUPPORTED

8.3 Graph 钩子

创建/销毁 pub/sub/service/client 时调用 graph_cache.hpp 中:

  • rmw_connextdds_graph_on_publisher_created/deleted
  • 更新 rmw_dds_common::GraphCachermw_connextdds_graph_publish_update 广播

9. Service / Client 与 RPC 映射

9.1 DDS-RPC 两种 Profile

Profile 机制 默认
Basic 请求头(GUID+SN)inline 序列化在 payload 前 Micro
Extended 使用 DDS SampleIdentity 等元数据 Pro

环境变量 RMW_CONNEXT_REQUEST_REPLY_MAPPING=basic|extended 覆盖默认。

9.2 跨 RMW 互操作

目标 配置
rmw_fastrtps_cpp Pro + extended(默认)
rmw_connextddsmicro 两侧 basic
rmw_cyclonedds_cpp RMW_CONNEXT_CYCLONE_COMPATIBILITY_MODE=y(非标准 basic 变体)

Cyclone 默认 pub/sub 可互通,但 service/client 不互通(除非开兼容模式)。

9.3 实现类方法

  • RMW_Connext_Client::send_request / take_response
  • RMW_Connext_Service::take_request / send_response
  • client_service_id 在 context 内递增,用于构造唯一 GUID 后缀

10. Graph 与 Discovery

10.1 双通道发现

ROS Graph 通道DDS Built-in 通道ParticipantEntitiesInfo\nros_discovery_informw_dds_common::GraphCacheDCPSParticipantDCPSPublicationDCPSSubscription
  1. ParticipantEntitiesInfo(与 Cyclone/FastRTPS 共用设计):节点/endpoint 增删时发布更新。
  2. Builtin topic readersdr_participants 等):监听远端 DDS 参与者与 endpoint,填充 graph。

10.2 Discovery 线程(rmw_discovery.cpp

独立线程 rmw_connextdds_discovery_thread

  • 创建 DDS_WaitSet,attach DCPS reader 的 DATA_AVAILABLE 条件
  • attach ros_discovery_info subscriber 与退出 GuardCondition
  • 循环 DDS_WaitSet_wait,分发到 graph_cache 更新函数
  • context shutdown 时 trigger exit guard,join 线程

10.3 Graph API

rmw_info.cpprmw_graph.cpp 实现:

  • rmw_get_node_names / with_enclaves
  • rmw_get_topic_names_and_types
  • rmw_get_publishers_info_by_topic

底层读取 ctx->common.graph_cachermw_dds_common),demangle topic/type 名后返回。


11. Wait Set 实现

11.1 RMW_Connext_StdWaitSetrmw_waitset_std.hpp + rmw_impl_waitset_std.cpp

rmw_wait 流程:

  1. 遍历 rmw_subscriptions_t 等数组,将每个有效 RMW_Connext_SubscriberStatusConditionReadCondition attach 到 WaitSet
  2. Service/Client 的 request/response reader 同理
  3. GuardCondition、Event 条件一并 attach
  4. DDS_WaitSet_wait(wait_timeout)
  5. 未触发的条目在数组中置 NULL(符合 rmw 契约)

11.2 事件与 Listener

QoS 事件(rmw_event.cpp)通过 RMW_Connext_SubscriberStatusCondition / PublisherStatusCondition 映射 DDS status:

rmw_event_type DDS Status
RMW_EVENT_REQUESTED_QOS_INCOMPATIBLE REQUESTED_INCOMPATIBLE_QOS_STATUS
RMW_EVENT_LIVELINESS_CHANGED LIVELINESS_CHANGED_STATUS
RMW_EVENT_MESSAGE_LOST SAMPLE_LOST_STATUS(若支持)

DataReader listener 回调更新 condition 计数,供 rmw_take_event 读取。


12. QoS 处理

12.1 默认 XML Profile

编译期默认 QoS 库名:RMW_CONNEXT_DEFAULT_QOS_LIBRARY = "ros2"static_config.hpp)。

rmw_connextdds_get_datawriter_qos / get_datareader_qosdds_api_*.cpp):

  1. 从 Connext QoS Profile 加载 topic 默认值(支持 topic filter)
  2. 根据 endpoint_qos_override_policy 决定是否叠加 ROS rmw_qos_profile_t
  3. 应用大消息优化、异步 publish 等 RMW 策略

12.2 Override 策略

环境变量 行为
RMW_CONNEXT_PARTICIPANT_QOS_OVERRIDE_POLICY all / basic / never 控制 Participant QoS 修改幅度
RMW_CONNEXT_ENDPOINT_QOS_OVERRIDE_POLICY always / never / dds_topics: <regex> 控制 DataWriter/Reader QoS

rmw_api_connextdds_qos_profile_check_compatible 直接委托 rmw_dds_common::qos_profile_check_compatiblermw_qos.cpp)。


13. 运行时环境变量

(详见仓库 README.md,此处汇总与源码对应关系)

环境变量 作用
RMW_IMPLEMENTATION 选择 rmw_connextddsrmw_connextddsmicro
RMW_CONNEXT_CYCLONE_COMPATIBILITY_MODE Service 与 Cyclone 互操作
RMW_CONNEXT_REQUEST_REPLY_MAPPING basic / extended
RMW_CONNEXT_LEGACY_RMW_COMPATIBILITY_MODE 与旧 rmw_connext_cpp 类型名兼容(_ 后缀)
RMW_CONNEXT_DISABLE_LARGE_DATA_OPTIMIZATIONS 关闭 ≥1MB 类型自动调优
RMW_CONNEXT_DISABLE_FAST_ENDPOINT_DISCOVERY 关闭 Fast endpoint discovery snippet
RMW_CONNEXT_USE_DEFAULT_PUBLISH_MODE 不强制异步 publish(Pro)
RMW_CONNEXT_INITIAL_PEERS 覆盖 discovery initial_peers(Micro 常用)
RMW_CONNEXT_UDP_INTERFACE Micro UDP 网卡(默认 lo
RMW_CONNEXT_ENDPOINT_QOS_OVERRIDE_POLICY Endpoint QoS 覆盖策略
RMW_CONNEXT_PARTICIPANT_QOS_OVERRIDE_POLICY Participant QoS 覆盖策略

14. 模块与源文件对照

14.1 src/common/(双后端共享)

文件 职责
rmw_context.cpp init/shutdown、Participant 生命周期、环境变量解析
rmw_impl.cpp Publisher/Subscriber/Client/Service 类实现、topic 创建
rmw_publication.cpp rmw_publish* API 层
rmw_subscription.cpp rmw_create_subscriptionrmw_take*
rmw_service.cpp Service/Client CRUD 与 request/response
rmw_waitset.cpp guard condition、waitset CRUD API
rmw_impl_waitset_std.cpp WaitSet attach/wait 核心逻辑
rmw_graph.cpp Graph 初始化、DCPS 回调、entity 增删
rmw_discovery.cpp Discovery 后台线程
rmw_info.cpp Graph introspection API
rmw_event.cpp QoS 事件 init/take/callback
rmw_listener.cpp DataReader/DataWriter listener 桥接
rmw_qos.cpp QoS 兼容性检查(委托 dds_common)
rmw_serde.cpp serialize/deserialize
rmw_type_support.cpp 类型注册公共逻辑
demangle.cpp Topic/类型 demangle
rmw_security_log.cpp SROS2 安全日志转发
rmw_network_flow_endpoints.cpp 未实现(返回 UNSUPPORTED)

14.2 src/ndds/(Professional)

文件 职责
dds_api_ndds.cpp Pro 专用 QoS、Participant 配置、CFT
rmw_type_support_ndds.cpp PRESTypePlugin、TypeCode、样本池
rmw_typecode.cpp TypeCode 构建与缓存
custom_sql_filter.cpp Content Filter SQL 表达式

14.3 src/rtime/(Micro)

文件 职责
dds_api_rtime.cpp Micro Participant/Transport 配置
rmw_type_support_rtime.cpp 简化类型注册
rtime_ext.c Micro API 扩展 shim

15. 尚未实现或受限的功能

通过 RMW_CONNEXT_LOG_NOT_IMPLEMENTED / RMW_RET_UNSUPPORTED 标记:

API / 能力 状态
rmw_borrow_loaned_message / rmw_take_loaned_message* 不支持
rmw_publish_loaned_message 不支持
rmw_*_allocation(pub/sub 预分配) 不支持
rmw_get_serialized_message_size 不支持
rmw_publisher_wait_for_all_acked 需查具体版本(部分 stub)
rmw_publisher_get_network_flow_endpoints 不支持
Strict unique network flow endpoints 创建 subscription 时拒绝
Micro 部分 Security / Extended RPC 受限

查阅具体 API 时可在 rmw_connextdds_common/src/common/ 内 grep NOT_IMPLEMENTED


16. Pro vs Micro 差异摘要

维度 rmw_connextdds (Pro) rmw_connextddsmicro
RTI 产品 Connext DDS Professional 5.3.1+ / 6.x Connext DDS Micro 3.x+
类型插件 完整 PRESTypePlugin + TypeObject 轻量注册
默认 RPC Extended(SampleIdentity) Basic(inline header)
Publish 模式 默认异步 不强制覆盖
UDP 接口 自动 默认 lo,需 RMW_CONNEXT_UDP_INTERFACE
Content Filter 支持(SQL) 有限/不支持
互操作 Fast-DDS extended RPC 与 Pro basic 模式互通

17. 与 rmw_dds_common 的协作

本实现 重度依赖 rmw_dds_common(参见 rmw_dds_common 源码详细分析):

rmw_dds_common 组件 在 Connext RMW 中的用法
rmw_dds_common::Context 嵌入 rmw_context_impl_t::common
GraphCache 存储节点/endpoint 图
ParticipantEntitiesInfo discovery topic 消息类型
qos_profile_check_compatible QoS 兼容性 API
security.hpp init 时安全配置

Connext 特有逻辑主要在 DDS API 层(QoS snippet、TypePlugin)和 discovery 线程,graph 语义与 Cyclone 后端对齐。


18. 调试建议

  1. 确认 RMW 已加载rmw_get_implementation_identifier()"rmw_connextdds""rmw_connextddsmicro"
  2. Connext 日志rmw_set_log_severity → 内部 rmw_connextdds_set_log_verbosity;Debug 构建定义 RMW_CONNEXT_DEBUG=1
  3. Graph 问题:检查 ros_discovery_info 是否有数据;对比 DCPS built-in reader 是否触发。
  4. QoS 不匹配:开 RMW_CONNEXT_ENDPOINT_QOS_OVERRIDE_POLICY=never 排查是否 RMW 覆盖导致。
  5. 跨机通信(Micro):必设 RMW_CONNEXT_UDP_INTERFACERMW_CONNEXT_INITIAL_PEERS
  6. Service 不通:核对 RPC mapping 与对端 RMW 是否匹配(见第 9.2 节)。

19. 推荐阅读顺序

  1. rmw_connextdds/src/rmw_api_impl_ndds.cpp — 全部 RMW 入口一览
  2. include/rmw_connextdds/context.hpp — context 状态机
  3. src/common/rmw_context.cpp — init/participant 流程
  4. include/rmw_connextdds/rmw_impl.hpp — 实体类接口
  5. src/common/rmw_impl.cpp — Publisher/Subscriber 创建与 topic 映射
  6. src/common/rmw_graph.cpp + rmw_discovery.cpp — Graph 双通道
  7. include/rmw_connextdds/type_support.hpp + src/ndds/rmw_type_support_ndds.cpp — 类型与 CDR
  8. src/common/rmw_impl_waitset_std.cpprmw_wait 行为
  9. include/rmw_connextdds/static_config.hpp — 编译期常量与环境变量名
  10. rti_connext_dds_cmake_module/cmake/rti_build_helper.cmake — 构建探测逻辑

20. 小结

rmw_connextdds 仓库采用 「薄 RMW 注册层 + 厚 common 库 + 双 DDS 后端适配」 架构:

  • 对上:完整实现 Humble rmw.h 契约(除 loaned message 等可选特性);
  • 对下:封装 RTI Connext Pro/Micro C API,Pro 版使用 TypePlugin 深度集成;
  • 横向:通过 rmw_dds_common 与 Cyclone/FastRTPS 共享 ROS Graph 协议;
  • 互操作:Pub/Sub 默认可跨厂商;Service 需按 RPC profile 与环境变量配置。

替换旧 rmw_connext_cpp 时需注意 类型 discovery 后缀RPC 映射 差异;生产环境应显式固定 RMW_IMPLEMENTATION 与相关 RMW_CONNEXT_* 环境变量。