ROS 2 通信机制与实时性:DDS、QoS 与 ros2_control 的正确打开方式
拆解 DDS 发现与 QoS 匹配的隐形规则、执行器的调度盲区、进程内零拷贝通信,以及 ros2_control 为什么不用话题传控制指令。
机器人软件栈的上层——规划、感知、任务调度——如今大概率跑在 ROS 2 上。它比 ROS 1 最大的变化是把通信层整个换成了 DDS,顺带把“能不能做实时”从“不能”改成了“能,但每一步都要做对”。本文讲三块最常踩坑的地方:QoS 匹配的隐形规则、执行器(executor)的调度行为,以及控制链路为什么绕开话题机制走 ros2_control。
ROS 1 的通信是自研的:一个中心化 master 负责名字解析,节点间 TCP 直连。三个后果:master 挂则全局瘫痪、TCP 的重传语义对丢得起的传感器流是负资产、没有服务质量的概念。
ROS 2 把整个通信层委托给 DDS(Data Distribution Service,OMG 标准,核电/军工/金融里跑了二十年的成熟货)。关键差异:
- 无中心发现:节点通过组播(默认 SPDP 协议)互相发现,没有单点。代价是大规模系统的发现风暴——上百节点 × 上千话题时,启动阶段的发现流量本身就是问题(ROS 2 Iron 之后的 discovery server 模式就是为此打的补丁);
- 传输可配置:UDP 组播/单播、共享内存、进程内指针传递,按部署形态选;
- QoS:每个发布者/订阅者显式声明通信契约,这是双刃剑,展开讲。
DDS 的 QoS 有二十多个策略维度,ROS 2 暴露了最关键的几个。要害规则是:发布者和订阅者的 QoS 按“请求-提供”模型匹配,不兼容时不报错、不警告(默认日志级别下)、就是收不到消息。这是 ROS 2 新手排名第一的坑:代码全对,话题名全对,ros2 topic echo 却一片寂静。
| 策略 | 选项 | 匹配规则 |
|---|---|---|
| Reliability | reliable / best_effort |
订阅者要 reliable,发布者必须 reliable |
| Durability | volatile / transient_local |
订阅者要 transient_local(补发历史),发布者必须同级 |
| History/Depth | keep_last(n) / keep_all |
本地缓冲行为,不参与匹配但决定丢不丢 |
| Deadline / Liveliness | 周期承诺 / 心跳 | 违约触发回调,用于监控链路健康 |
典型组合背下来能省很多排查时间:
- 传感器流(图像、点云、激光):
best_effort + volatile + keep_last(1~5)——丢一帧无所谓,补传旧帧反而有害。ROS 2 预置的SensorDataQoS就是它; - 控制指令/事件:
reliable + volatile——丢不起; - 静态大数据(地图、标定、TF static):
reliable + transient_local——后启动的订阅者要能拿到“最后一版”,这是 ROS 1 latched topic 的对应物。
两个进阶注意点:reliable 的重传在有损链路(Wi-Fi)上会雪崩——一帧图像丢包触发重传,挤占带宽导致更多丢包,所以无线上传感器流必须 best_effort;Deadline/Liveliness 很少有人用,但它们才是“发布者还活着吗”这个问题的正确答案,比自己写超时检查干净得多。
发布订阅只解决“数据到了”,“回调何时运行”由执行器决定,这是 ROS 2 里第二个被普遍低估的模块。
默认的 SingleThreadedExecutor:一个线程轮询所有已就绪的实体(订阅、定时器、服务),逐个执行回调。两个推论直接影响系统行为:
- 一个慢回调阻塞整个节点:图像处理回调跑 80 ms,同节点 100 Hz 的定时器就丢拍。对策按顺序是——把重活挪出回调(丢进工作线程 + 无锁队列)、拆节点、或换
MultiThreadedExecutor; MultiThreadedExecutor不是免费午餐:回调并发后,共享状态要自己加锁;而回调组(callback group)是比裸 mutex 更结构化的工具——MutuallyExclusive组内回调串行(组即锁),Reentrant组内随便并发。正确姿势是按共享数据划分回调组,而不是全局一把大锁。
调度语义还有一个冷知识:默认执行器在一轮里按实体类型固定顺序(定时器 → 订阅 → 服务……)处理就绪集合,负载高时低优先级类型可能被饿着;对延迟敏感的系统应关注 EventsExecutor(事件驱动、按到达序),它在高频小消息场景的延迟和 CPU 占用都明显更好,Jazzy 之后已经足够成熟。
进程内通信是同一话题的另一半:把多个节点以组件(component)形式装进同一进程,use_intra_process_comms 开启后消息以 unique_ptr 直接移交,不序列化、不过网络栈;跨进程的共享内存方案(CycloneDDS+iceoryx、FastDDS data-sharing,以及零拷贝借出接口 loaned messages)解决大消息(图像/点云)的拷贝开销。数量级感受:同机跨进程一帧 1080p 图像走 UDP 回环要毫秒级,共享内存/进程内是微秒级——感知管线的架构决策(组件化 + 零拷贝)常常比算法优化一个量级更有效。
先把结论放前面:默认配置的 rclcpp 回调路径不满足硬实时——执行器等待集重建、消息反序列化都可能分配内存,DDS 栈里也有锁。这不意味着 ROS 2 与实时绝缘,而是实时部分要按实时 Linux 那篇的规矩单独架构,ROS 2 负责非实时的外围。标准形态就是 ros2_control:
关键设计一句话:实时域内没有话题。控制器(controller)和硬件接口(hardware_interface)之间通过共享内存里的命令/状态接口直接读写双精度数组,controller_manager 的实时循环按 1 kHz 绝对时间踩点依次调用 read() → update() → write()——同一个线程、零拷贝、无锁。话题只出现在边界上:轨迹以 action 一次性送进控制器(非实时侧解析、实时侧插值),状态以话题发出去给监控。
跨域通信的纪律和无锁 SPSC 队列那篇完全一致,ros2_control 生态里现成的工具是 realtime_tools 包:RealtimeBuffer(非实时写、实时读的双缓冲)、RealtimePublisher(实时侧只写缓冲,另一个线程负责真正 publish)。自己写控制器时,update() 里不 malloc、不加锁、不打日志的纪律不变。
部署侧还有三件配套事:控制进程 mlockall + SCHED_FIFO(launch 里用 nice/chrt 或 systemd 配置);DDS 选型与调优(CycloneDDS 在多数基准里延迟表现稳定,FastDDS 功能面广,实时敏感场景都要关掉不必要的内置话题);以及把实时域压到最小——实时域每多一行代码,验证成本翻倍,能放非实时域的都放出去。
按“话题不通 → 延迟异常 → 丢消息”三类高频问题给最短路径:
- 话题不通:
ros2 topic info -v /xxx看两端 QoS 是否兼容(八成是 reliability 或 durability 不匹配);再查ROS_DOMAIN_ID是否一致、跨网段时组播是否被交换机吞了(云端/容器环境常见,改 discovery server 或静态 peer 列表); - 延迟异常:先分清是传输还是调度——
ros2_tracing+ Trace Compass 能看到消息从发布到回调执行的完整时间线,慢在执行器排队的比慢在网络的多得多; - 丢消息:
reliable下丢通常是 history depth 溢出(发布快于消费),best_effort下看网络;大消息看 UDP 分片与内核缓冲区(rmem_max),这是点云上 Wi-Fi 必调的参数。
- QoS 是通信契约,不匹配 = 静默断连;传感器流
best_effort、指令reliable、静态数据transient_local,先背这三条。 - 执行器决定回调调度:慢回调堵节点、多线程要配回调组;高频低延迟场景看
EventsExecutor;同机大数据用组件化 + 进程内/共享内存零拷贝。 - ROS 2 的实时答案是域分离:ros2_control 把 1 kHz 循环收进单线程实时域,话题只在边界;跨域用
realtime_tools的无锁原语。 - DDS 换来了无单点、QoS 和成熟传输,也带来发现风暴和调优面——中间件是可替换的(rmw 抽象),选型要跑自己的消息尺寸和拓扑做基准,别抄别人的结论。
- ROS 2 官方文档:About QoS Settings、Executors、Intra-process Communication 设计文档(design.ros2.org)。
- ros2_control 文档(control.ros.org)与
realtime_tools源码。 - ROS 2 Real-Time Working Group 资料仓库(github.com/ros-realtime)。
- C. Bédard et al. ros2_tracing: Multipurpose Low-Overhead Framework for Real-Time Tracing of ROS 2. IEEE RA-L 2022.
- OMG. DDS Specification v1.4;DDSI-RTPS v2.3。