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),为 rclcpp、rclpy 及其他语言绑定提供语言无关、稳定 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 能力 |
| 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_ID、ROS_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_t、rmw_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);
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 list、ros2 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_t、rcl_lifecycle_transition_t、rcl_lifecycle_state_machine_t。
9. 错误处理
C 风格,无异常:
| 机制 |
说明 |
| 返回值 |
rcl_ret_t(RCL_RET_OK = 0,其余为错误码) |
| 错误消息 |
RCL_SET_ERROR_MSG() → 线程局部字符串 |
| 读取 |
rcl_get_error_string() |
| RMW 转换 |
rcl_convert_rmw_ret_to_rcl_ret() |
常见错误码(types.h):
| 范围 |
示例 |
| 通用 |
RCL_RET_OK、RCL_RET_ERROR、RCL_RET_BAD_ALLOC |
| Context |
RCL_RET_ALREADY_INIT、RCL_RET_NOT_INIT、RCL_RET_ALREADY_SHUTDOWN |
| Node |
RCL_RET_NODE_INVALID_NAME、RCL_RET_NODE_INVALID_NAMESPACE |
| Pub/Sub |
RCL_RET_PUBLISHER_INVALID、RCL_RET_SUBSCRIPTION_TAKE_FAILED |
| Wait |
RCL_RET_WAIT_SET_EMPTY、RCL_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. 推荐阅读顺序
- 总览:
include/rcl/rcl.h — 模块地图
- 生命周期:
init.c → context.c → init_options.c
- 节点:
node.c — 理解 namespace/remap/logger
- 通信:
publisher.c + subscription.c — init/publish/take 链路
- 调度:
wait.c + timer.c + guard_condition.c — 理解 spin 底层
- CLI:
arguments.c(可按需跳读)— remap/params 解析
- 图 API:
graph.c
- 扩展:
rcl_action/action_server.c → rcl_lifecycle/rcl_lifecycle.c
- 上层集成:
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);
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 源码分析生成。
正在加载留言…