在机器人系统中,控制指令、传感器数据、关节状态、视觉结果、定位信息,都需要不断在不同软件模块之间传递。

对于普通应用程序来说,一条消息从一个进程传递到另一个进程,哪怕中间发生几次数据复制,通常也不是特别严重的问题。

但机器人实时控制不一样。

假设一个机械臂运行在 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本身并没有直接实现一套独立于底层通信的完整网络协议,而是通过 RMW(ROS 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 FPS:

6.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
 ↓
Transport

DDS需要处理的不只是“把数据送过去”。

它还要考虑:

可靠性
历史缓存
数据持久性
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
 ↓
Vision

Camera什么时候可以覆盖这块内存?

如果 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。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐