Skip to content

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);

总结

  1. RMW 是接口层,定义序列化的契约(函数签名),不执行实际序列化
  2. rmw_cyclonedds_cpp 是适配层,把 RMW 调用转给底层 DDS,不关心序列化细节
  3. rosidl_typesupport_c 是真正的序列化实现,知道 ROS2 消息的内存布局,生成 CDR 字节流
  4. Cyclone DDS 是传输层,接收已序列化的数据,处理 RTPS 协议和 QoS

这样设计的好处:ROS2 可以切换不同 DDS,而序列化逻辑保持一致(都是 rosidl 生成的 CDR)。

基于 VitePress 构建