ROS2 RMW 序列化机制
背景
ROS2 的序列化机制是 DDS 通信的基础,理解其层次结构和调用流程对于性能优化和问题排查至关重要。
架构层次
RMW 层是序列化的发生点,但实际序列化逻辑由不同层次协作完成。
序列化流程
1. 消息定义到类型支持
idl
// example.msg
string name
int32 count
float64 value ROS2 通过 rosidl 生成:
- C++ 结构体
- 类型支持库(TypeSupport)
- 序列化/反序列化函数
2. CDR 序列化格式
ROS2 默认使用 CDR (Common Data Representation),这是 DDS 标准定义的:
- 大端/小端可配置
- 支持嵌套结构、数组、序列
- 对齐填充保证跨平台兼容
3. RMW 层的角色
cpp
// rmw 发送时
rmw_publish(publisher, &ros_message, allocation);
// 内部流程:
// 1. 从 TypeSupport 获取序列化函数
// 2. 调用 rmw_serialize() 将消息转为 CDR 字节流
// 3. 传给 DDS vendor 的 write() 4. 关键数据结构
cpp
// 类型支持定义
typedef struct rmw_type_support_t {
const char * type_name;
const message_type_support_callbacks_t * data;
} rmw_type_support_t;
// 序列化后的表示
typedef struct rmw_serialized_message_t {
char * buffer; // CDR 字节流
size_t buffer_length;
size_t buffer_capacity;
} rmw_serialized_message_t; RMW 序列化与 Cyclone DDS 序列化的关系
层次关系
关键区别
| 层次 | 职责 | 代码位置 |
|---|---|---|
| RMW | 定义接口 rmw_serialize() / rmw_deserialize() | rmw/include/rmw/ |
| Cyclone DDS | 实际序列化逻辑,CDR 编码 | Cyclone 源码 |
实际调用链
cpp
// 用户代码
publisher->publish(msg);
// 调用链
rclcpp → rcl → rmw_publish()
→ rmw_cyclonedds_cpp::publish()
→ Cyclone DDS 的 ddsi_serdata_from_sample()
→ CDR 编码(Cyclone 自己实现) 不同 DDS 的序列化差异
虽然都遵循 CDR 标准,但实现细节不同:
| DDS 实现 | 序列化特点 |
|---|---|
| Cyclone DDS | 轻量,内联 CDR 编码,无额外依赖 |
| Fast-DDS | 使用 Fast-CDR 库,支持共享内存零拷贝 |
| Connext | 使用 RTI 的类型代码生成器 |
字符串序列化示例:
c
// Cyclone 的实现(简化)
void ddsi_serdata_from_sample(...) {
// 直接操作内存,紧凑布局
memcpy(cursor, str, len + 1);
}
// Fast-DDS 的实现
void Cdr::serialize(const std::string& str) {
// 通过 Fast-CDR 库,有额外封装
serialize_sequence(str.c_str(), str.length());
} 结论
- CDR 字节流格式:理论上兼容(都遵循标准)
- 性能特征:不同实现有差异
- 内存布局细节:可能有微小差异(对齐、padding)
不同 DDS 的节点可以互相通信,因为 CDR 格式标准一致。但混用时要注意类型哈希校验(ROS2 Humble+)。
rmw_cyclonedds_cpp 的序列化细节
核心操作
cpp
// rmw_cyclonedds_cpp 发布消息时的调用链
rmw_publish()
→ rmw_cyclonedds_cpp::publish()
→ create_serdata() // 创建序列化数据结构
→ 调用 rosidl 生成的序列化函数
// 或使用 Cyclone 的类型支持 关键代码路径
1. 类型注册阶段
cpp
// rmw_cyclonedds_cpp 注册 ROS2 类型
rmw_cyclonedds_cpp::get_type_support()
→ 将 rosidl_typesupport_c 转换为 Cyclone 的类型表示
→ 创建 Cyclone 的 ddsi_sertype(类型描述) 2. 序列化执行
cpp
// rmw_cyclonedds_cpp 的 serdata_from_sample 实现
static struct ddsi_serdata * serdata_from_sample(
const struct ddsi_sertype * type,
enum ddsi_serdata_kind kind,
const void * sample)
{
// 关键:这里调用的是 ROS2 的序列化函数
// 不是 Cyclone 原生的序列化
auto ts = static_cast<const rosidl_typesupport_c_type_support_t*>(type->type_support);
// 调用 rosidl 生成的 CDR 序列化
ts->serialize(sample, &serialized_msg);
} 和原生 Cyclone DDS 的区别
| 对比项 | rmw_cyclonedds_cpp | 原生 Cyclone DDS |
|---|---|---|
| 类型定义 | rosidl (.msg/.idl) | IDL (OMG IDL) |
| 序列化函数 | rosidl_typesupport_c 生成 | Cyclone IDL 编译器生成 |
| 内存布局 | ROS2 消息结构体 | Cyclone 的样本结构体 |
| 类型哈希 | ROS2 类型哈希 | DDS 类型代码 |
具体差异
原生 Cyclone DDS:
c
// 使用 IDL 定义
module example {
struct Point {
double x;
double y;
};
};
// Cyclone 生成
struct example_Point {
double x;
double y;
};
// 序列化直接操作结构体
void ddsi_serdata_from_sample_Point(...) {
// 直接 memcpy 或 CDR 编码
} rmw_cyclonedds_cpp:
cpp
// 使用 ROS2 消息定义
// geometry_msgs/msg/Point.msg
float64 x
float64 y
// rosidl 生成
namespace geometry_msgs::msg {
struct Point {
double x;
double y;
};
}
// 序列化通过 rosidl_typesupport_c
auto ret = rosidl_typesupport_c__serialize_Point(
&ros_msg, &serialized_msg); 设计架构
实际代码证据
cpp
// rmw_cyclonedds_cpp/src/serdata.cpp
static struct ddsi_serdata * serdata_from_sample(
const struct ddsi_sertype * type,
enum ddsi_serdata_kind kind,
const void * sample)
{
// 关键:调用 ROS2 的序列化
const rosidl_message_type_support_t * ts =
static_cast<const rosidl_message_type_support_t *>(type->type_support);
// 使用 rosidl 的 CDR 序列化
rmw_ret_t ret = rmw_cyclonedds_cpp::serialize_ros_message(
sample, &serdata->data, ts);
return &serdata->c;
} 性能优化点
| 优化 | 说明 |
|---|---|
| 零拷贝 | Fast-DDS 支持共享内存传输,避免序列化开销 |
| 类型哈希 | ROS2 Humble 引入类型哈希校验,避免类型不匹配 |
| 预分配 | 序列化 buffer 可预分配复用 |
手动序列化示例
cpp
// 手动序列化(调试用)
auto msg = std_msgs::msg::String();
msg.data = "hello";
rmw_serialized_message_t serialized;
rmw_serialize(&msg, type_support, &serialized);
// 手动反序列化
std_msgs::msg::String out_msg;
rmw_deserialize(&serialized, type_support, &out_msg); 总结
- RMW 是接口层,定义序列化的契约(函数签名),不执行实际序列化
- rmw_cyclonedds_cpp 是适配层,把 RMW 调用转给底层 DDS,不关心序列化细节
- rosidl_typesupport_c 是真正的序列化实现,知道 ROS2 消息的内存布局,生成 CDR 字节流
- Cyclone DDS 是传输层,接收已序列化的数据,处理 RTPS 协议和 QoS
这样设计的好处:ROS2 可以切换不同 DDS,而序列化逻辑保持一致(都是 rosidl 生成的 CDR)。