在机器人系统中控制指令、传感器数据、关节状态、视觉结果、定位信息都需要不断在不同软件模块之间传递。对于普通应用程序来说一条消息从一个进程传递到另一个进程哪怕中间发生几次数据复制通常也不是特别严重的问题。但机器人实时控制不一样。假设一个机械臂运行在 1 kHz 控制周期1秒 1000个控制周期 1个周期 1ms如果每一个控制周期都需要完成传感器读取 ↓ 数据封装 ↓ 消息发送 ↓ DDS通信 ↓ 消息接收 ↓ 数据复制 ↓ 控制算法 ↓ 控制指令发送那么通信链路中的每一次数据复制、序列化、内存申请、线程切换都可能成为实时链路的一部分。这并不意味着“只要发生数据复制系统就一定不实时”。真正需要关注的是通信链路中的数据处理时间是否稳定、是否可预测以及最坏情况下是否仍然能够满足控制周期。因此当 ROS 2 从普通机器人软件框架进入工业机器人、机械臂、人形机器人、运动控制等高实时场景之后一个非常重要的问题就出现了ROS 2为什么越来越关注 Zero-Copy、Loaned Message、Intra-Process Communication 和共享内存答案并不只是“为了提高速度”。更深层的原因是减少不必要的数据复制和内存操作有助于降低通信链路中的处理开销并进一步改善实时系统的确定性。一、ROS 2的一条消息到底经历了什么理解 Zero-Copy 之前首先要理解 ROS 2 消息是怎么走的。ROS 2本身并没有直接实现一套独立于底层通信的完整网络协议而是通过RMWROS Middleware对接 DDS 等通信中间件。因此可以把 ROS 2 的通信链路简单理解成ROS 2 Node ↓ Publisher / Subscriber ↓ RMW ↓ DDS ↓ 网络 / 共享内存 / 进程内通信 ↓ 另一个 DDS Participant ↓ RMW ↓ ROS 2 Node例如一个相机节点产生图像Camera Node ↓ Image Message ↓ Publisher ↓ DDS ↓ Subscriber ↓ Vision Node如果只是传递一个几十字节的状态消息这个过程通常并不会造成特别大的压力。但机器人系统中经常出现另一种数据图像。例如1920 × 1080 RGB 3 Bytes / Pixel一帧数据大约就是1920 × 1080 × 3 ≈ 6.2 MB如果运行 30 FPS6.2 MB × 30 ≈ 186 MB/s还只是单路 RGB 图像。如果同时存在RGB Depth Point Cloud LiDAR Radar IMU Joint State系统的数据吞吐量很快就会上升。这时候问题就不再只是CPU够不够快而是这些数据到底被复制了多少次假设一帧 6 MB 图像经历三次复制Camera Buffer ↓ 6 MB DDS Buffer ↓ 6 MB ROS 2 Message ↓ 6 MB Vision Buffer单帧就可能产生大量内存读写。而机器人控制系统通常又不是只有一个节点。可能是Camera ↓ Image Processing ↓ Object Detection ↓ Pose Estimation ↓ Motion Planning ↓ Control如果每个环节都进行数据复制那么内存带宽、Cache 和 CPU 都会受到影响。所以 Zero-Copy 的核心目标之一就是让多个模块尽可能复用同一份数据而不是不断创建新的数据副本。二、Zero-Copy到底是什么为什么“零拷贝”没有想象中那么简单所谓 Zero-Copy简单理解就是传统方式 数据A ↓ 复制 ↓ 数据B ↓ 复制 ↓ 数据C变成数据A ↓ 共享/借用 ↓ 多个处理模块也就是说后续模块尽量直接访问已经存在的数据而不是再次复制。但这里有一个非常重要的概念Zero-Copy并不意味着计算机里真的“一次复制都没有”。工程实现中“零拷贝”通常是一个相对概念。它可能意味着减少用户空间之间的数据复制避免不必要的消息复制通过共享内存传递数据让发布者和订阅者访问同一块数据使用 Loaned Message 直接获取中间件管理的缓冲区通过 Intra-Process Communication 减少同一进程内的数据复制。因此讨论 ROS 2 Zero-Copy 时必须先问一个问题到底是在什么边界上实现 Zero-Copy因为不同边界的解决方案完全不同。第一种同一个进程内部例如Process ├── Node A ├── Node B └── Node C如果 Node A 发布数据Node B 和 Node C 都在同一个进程中那么可以利用 ROS 2 的进程内通信机制减少不必要的数据复制。逻辑上可以理解为Node A ↓ Message ↓ Node B ↓ Node C而不是Node A ↓ copy Node B ↓ copy Node C第二种不同进程之间如果Process A ↓ Process B那么两个进程拥有不同的虚拟地址空间。这时候想做到高效数据共享就需要借助共享内存等机制。逻辑上可以变成Process A ↓ Shared Memory ↑ Process B数据不需要在两个进程之间完整复制。第三种不同设备之间如果CPU ↓ GPU ↓ Camera ↓ Network那么问题又完全不同。这时候所谓 Zero-Copy 会涉及DMA GPU Memory Pinned Memory Device Buffer IOMMU PCIe Network Buffer因此“Zero-Copy”不是一个简单的 ROS 2 API而是一个跨越应用层 ↓ ROS 2 ↓ RMW ↓ DDS ↓ 操作系统 ↓ 共享内存 ↓ 驱动 ↓ 硬件的系统工程问题。三、DDS为什么是理解ROS 2实时通信的关键前面的文章已经介绍过ROS 2大量通信能力建立在 DDS 及其 QoS 机制之上。因此如果想理解 ROS 2 的实时通信就不能只研究Publisher Subscriber Topic还需要进一步理解ROS 2 ↓ RMW ↓ DDS ↓ TransportDDS需要处理的不只是“把数据送过去”。它还要考虑可靠性 历史缓存 数据持久性 Deadline Liveliness 发现机制 网络传输 数据序列化 并发线程 缓存管理所以 ROS 2 的一条消息实际上可能涉及很多中间处理。传统通信链路可以简单抽象成Application ↓ Serialize ↓ DDS Buffer ↓ Transport ↓ DDS Buffer ↓ Deserialize ↓ Application这里的 Serialize 和 Deserialize 就是序列化与反序列化。例如一个 C 对象struct JointState { double position[7]; double velocity[7]; double effort[7]; };在发送之前需要将应用程序中的数据转换成适合传输的数据形式。接收端再恢复成可以使用的数据。如果数据量很小这个开销通常不明显。但是对于PointCloud Image DepthImage LargeArray LaserScan这种大数据消息序列化和数据复制的成本就可能变得非常明显。因此在机器人系统中经常出现一个矛盾数据越丰富 ↓ 机器人感知能力越强 ↓ 消息越来越大 ↓ 通信和内存压力越来越大而与此同时控制周期越来越短 ↓ 实时性要求越来越高这两个趋势恰好是冲突的。所以现代机器人软件架构开始越来越重视如何让大量数据快速流动同时尽可能不干扰实时控制链路。四、共享内存为什么能提高效率又为什么会带来新的问题共享内存是解决本机跨进程大数据通信的一种重要思路。传统方式Process A ↓ Copy ↓ Kernel / Middleware Buffer ↓ Copy ↓ Process B共享内存方式Shared Memory ┌───────────────┐ │ Data │ └───────────────┘ ↑ ↑ │ │ Process A Process B数据可以放在一块双方都能够访问的共享区域。这样可以减少大量数据复制。对于机器人视觉、点云、雷达等大数据场景这种方式非常有吸引力。但共享内存并不是“免费午餐”。因为数据不复制之后新的问题就出现了谁拥有这块数据例如Camera ↓ Shared Memory ↓ VisionCamera什么时候可以覆盖这块内存如果 Vision 还没有处理完Camera ↓ 覆盖 ↓ Vision正在读取就会产生数据一致性问题。因此需要引入引用计数 生命周期 锁 原子操作 Ring Buffer 读写指针 同步机制这又回到了我们前面讨论过的实时问题减少数据复制不代表同步问题消失。甚至某些情况下通信从数据复制问题变成了数据生命周期 同步问题 资源竞争问题所以在实时系统中不能简单地追求“Zero-Copy”。应该追求可预测的数据传递。如果 Zero-Copy 让平均性能提高 20%但是因为复杂的锁竞争导致最坏延迟突然增加那么对于硬实时控制任务来说未必是理想结果。五、ROS 2实时控制中的Zero-Copy应该怎么设计如果把机器人系统拆开可以发现不同数据其实具有完全不同的实时要求。例如ROS 2 Robot │ ┌─────────────┼─────────────┐ ↓ ↓ ↓ 感知域 规划域 控制域 │ │ │ Camera Nav2 ros2_control LiDAR SLAM Joint Control PointCloud Planning Servo感知域数据量大 吞吐量高 允许一定处理延迟控制域数据量相对小 周期短 对延迟和抖动敏感这意味着不能用同一套通信策略处理所有数据。例如图像数据更加关注吞吐量 CPU占用 内存带宽 数据复制可以重点考虑共享内存 Zero-Copy Loaned Message GPU/CPU数据共享关节控制数据更加关注周期 Deadline 确定性 最坏延迟 同步这时候通信机制不仅要快更要稳定。例如1 kHz ↓ 每1ms ↓ 读取状态 ↓ 计算 ↓ 发送控制指令真正重要的是999 μs 1000 μs 1001 μs 1000 μs而不是500 μs 1500 μs 700 μs 1300 μs即使后者平均值可能相差不大前者的确定性也明显更好。所以 Zero-Copy 应该服务于实时系统而不是反过来让实时系统迁就 Zero-Copy。六、从“减少拷贝”进一步走向“实时数据通路”到了这里可以把 ROS 2 实时通信进一步抽象成一条数据通路Sensor ↓ Driver ↓ Buffer ↓ DDS / RMW ↓ Executor ↓ Callback ↓ Control Algorithm ↓ Command ↓ Driver ↓ Actuator真正的实时通信优化需要同时回答几个问题第一数据有没有必要复制如果没有必要就尽量复用或者共享。第二数据什么时候必须准备好如果控制周期是 1 ms就必须围绕 1 ms 的 deadline 设计。第三数据传递过程中是否会产生动态内存操作如果处于关键实时路径需要尽量减少不可预测的分配行为。第四通信过程中是否存在锁如果存在需要考虑锁等待、优先级反转以及临界区长度。第五通信线程运行在哪个 CPU需要结合 CPU Affinity、核心隔离以及 IRQ Affinity 进行规划。第六通信本身是否会受到非实时任务影响例如 AI 推理、视觉处理、大量日志或者网络任务。这时候就需要进一步进行资源隔离。于是我们前面几篇文章讨论的内容终于可以全部串起来ROS 2 ↓ Topic / DDS / QoS ↓ Executor ↓ Callback ↓ Thread ↓ Linux Scheduler ↓ CPU Core ↓ IRQ ↓ Memory ↓ Cache ↓ Communication Buffer ↓ Hardware任何一个环节都可能成为实时链路中的变量。因此真正意义上的 ROS 2 实时系统并不是简单地ROS 2 一个高优先级线程也不是ROS 2 Zero-Copy而应该是ROS 2 合理Executor 合理调度策略 CPU核心隔离 IRQ隔离 内存预分配 通信优化 资源隔离 实时操作系统共同构建出来的。七、Zero-Copy之后还需要解决什么从技术演进角度看机器人系统正在经历一个非常明显的变化。早期机器人软件更关注能不能运行然后开始关注能不能实时运行再进一步能不能在复杂任务并发情况下稳定实时运行而现在的人形机器人、工业机器人、智能移动机器人越来越需要同时运行AI 视觉 SLAM 规划 ROS 2 运动控制 通信这意味着机器人操作系统实际上正在从“单一控制程序”走向一个复杂的实时计算平台。在这样的系统中CPU 内存 Cache IRQ 通信 GPU 网络 存储都可能成为资源竞争来源。因此未来的机器人实时系统设计很难再只讨论一个“实时线程”。而是需要建立完整的实时计算域。例如┌──────────────────────────────────────────┐ │ Robot Computing │ │ │ │ ┌────────────────┐ ┌─────────────────┐ │ │ │ Real-Time │ │ Non-Real-Time │ │ │ │ Domain │ │ Domain │ │ │ │ │ │ │ │ │ │ Joint Control │ │ AI │ │ │ │ Servo │ │ Vision │ │ │ │ Safety │ │ SLAM │ │ │ │ State Update │ │ Planning │ │ │ │ │ │ Logging │ │ │ └───────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ └───────┬───────────┘ │ │ ↓ │ │ Controlled Communication │ └──────────────────────────────────────────┘实时域负责确定性 低抖动 严格deadline非实时域负责复杂计算 高吞吐 AI 视觉 规划两者之间通过经过设计的通信机制交换数据。这其实也是未来机器人操作系统非常重要的一个方向不是让整个系统都变成实时而是让真正需要实时的部分具备可预测的执行环境。对于需要更强实时能力、确定性以及资源隔离能力的机器人和工业控制系统底层操作系统同样是整个架构的重要基础。像望获rtLinux这样的实时 Linux 环境可以作为这类系统的一种底层技术选择与 ROS 2、DDS、Executor、CPU隔离、内存管理等机制共同构建实时运行环境。但最终仍然需要强调实时性从来不是某一个组件单独提供的能力。DDS 可以优化通信Zero-Copy 可以减少复制Executor 可以组织回调Linux 调度器可以管理线程CPU 隔离可以减少计算干扰内存预分配可以降低运行时不确定性而实时操作系统则可以提供更加适合确定性任务的底层运行环境。只有这些环节共同配合才能真正形成一条稳定的实时数据通路。八、结语Zero-Copy的终点不是“零拷贝”而是“可预测”回到最开始的问题ROS 2实时控制为什么需要 Zero-Copy答案其实已经越来越清楚。并不是因为Zero-Copy 实时。而是因为减少不必要的数据复制可以降低通信路径中的计算、内存和带宽开销为构建更稳定的实时数据通路创造条件。但真正的实时系统还必须进一步考虑数据复制 内存分配 页面访问 Cache 锁 Executor 线程调度 CPU隔离 IRQ 驱动这也是为什么机器人实时系统的技术问题最终一定会从 ROS 2 应用层逐渐走向操作系统内核和硬件资源层。从最开始的Node Topic DDS Executor一路向下Callback ↓ Thread ↓ Scheduler ↓ CPU ↓ Core Isolation ↓ Memory ↓ Zero-Copy ↓ IRQ ↓ Driver ↓ Hardware这条链路实际上就是理解ROS 2 实时控制的一条完整技术路线。而当 ROS 2 真正进入机械臂、工业机器人、人形机器人以及高性能运动控制系统之后另一个问题又会逐渐变得越来越重要如果 ROS 2 的通信和计算都已经优化了那么真正负责“让机器人动起来”的控制框架到底是什么这就需要进入 ROS 2 机器人控制体系中一个非常核心的组件ros2_control。下一篇可以从最底层的read → update → write控制循环开始详细拆解ros2_control 到底是什么、Controller Manager 如何工作、硬件接口如何连接以及为什么 ros2_control 最终会再次回到实时 Linux。
阅读完成 · 觉得有帮助?