2. 自动驾驶通信中间件概览

自动驾驶系统本质上是一台“移动的实时分布式计算机”:感知、定位、预测、规划、控制等模块以不同频率持续产生和消费数据,激光雷达点云单帧可达数 MB,摄像头图像、毫米波雷达目标列表、组合导航位姿、底盘反馈等数据在毫秒级时间窗内必须完成端到端流转。通信中间件正是承载这些数据流转的“神经系统”。Cyber RT 是百度 Apollo 面向自动驾驶场景自研的通信中间件,它并不只是一个消息总线,而是一套包含调度、通信、组件模型与服务发现的运行时框架。

一、Cyber RT 的定位:介于操作系统与算法模块之间的实时运行时

从系统分层看,Cyber RT 位于操作系统与自动驾驶算法模块之间。向下,它依赖 Linux 的线程、共享内存、epoll、CPU 亲和性等能力;向上,它为感知、规划等模块提供统一的 Component 编程模型、Channel 通信抽象和参数/服务接口。其核心定位可以概括为三点:

  • 确定性调度:通过协程(Coroutine)与调度器(Scheduler)把不同频率的任务编排到固定 CPU 核上,降低抖动。
  • 高效通信:同一主机内优先走共享内存,跨主机走 RTPS/DDS 等网络通道,实现“零拷贝”或“少拷贝”传输。
  • 模块解耦:算法开发者只关心“订阅什么、发布什么”,无需关心底层是共享内存还是网络。
一句话理解:Cyber RT 让算法模块像“插在背板上的板卡”一样工作——背板负责供电(调度)与数据交换(通信),板卡只负责计算。

二、架构分层与模块组成

Cyber RT 自上而下大致可分为四层,每层职责清晰:

层次主要模块核心职责
应用层Component、TimerComponent、消息定义(.proto)算法以组件形式注册,声明发布/订阅通道
通信层Node、Channel、Reader/Writer、Transport提供发布订阅语义,屏蔽底层传输差异
传输层Shared Memory、RTPS、Intra-process按同机/跨机、进程内/进程外选择最优路径
运行时层Scheduler、Coroutine、Task、CRoutine任务编排、协程调度、CPU 亲和性绑定
基础层Parameter、Service、Logger、Time、Record参数服务、日志、时间同步与数据回放

其中,Transport(传输层)是性能关键路径。Cyber RT 会根据通信双方的拓扑关系自动选择传输方式:同一进程内直接指针传递;同一主机不同进程优先使用共享内存;跨主机则回退到 RTPS 网络传输。这种“自适应传输”是 Cyber RT 区别于普通消息队列的重要特征。

三、共享内存通信在 Cyber RT 中的角色

共享内存是 Cyber RT 实现零拷贝的核心机制。其基本思路是:发送方把消息序列化写入一块由多个进程共同映射的共享内存段,接收方通过同一块内存直接读取,从而避免“用户态→内核态→用户态”的多次数据搬运。Cyber RT 的共享内存传输通常包含以下关键对象:

  • Segment(内存段):由 shm 创建的大块共享内存区域,按固定大小分块管理。
  • Block(内存块):实际承载消息数据的单元,带引用计数,支持多读者并发读取。
  • Channel 索引:记录某通道下可读 Block 的位置,读者据此定位数据。
  • Notifier:通过条件变量或事件通知读者“有新数据可读”,避免忙等。

下面这张图展示了 Cyber RT 中一条典型共享内存通信链路的结构关系:

Cyber RT 共享内存通信链路结构 发布方进程 Component / Writer 序列化 + 写入 Block 共享内存 Segment Block 0(点云帧) 引用计数 = 2 Block 1(图像帧) 引用计数 = 1 Channel 索引表 记录可读 Block 位置 Notifier 通知机制 条件变量 / 事件唤醒 订阅方进程 Component / Reader 直接读取 Block 写入 读取 通知 数据只在写入时拷贝一次,读者通过共享内存直接访问,实现零拷贝读取

从图中可以看到,共享内存通信把“数据搬运”变成了“数据共享”:发布方写入一次,多个订阅方各自读取,互不干扰。引用计数保证 Block 在所有读者释放后才被回收,Notifier 则解决“何时读”的同步问题。

四、典型场景:激光雷达点云从驱动到感知

以激光雷达为例,驱动进程以 10 Hz 发布 PointCloud 消息,感知进程订阅同一通道。若使用传统 socket 或普通队列,每帧数 MB 的点云需要经历多次内存拷贝,CPU 占用和延迟都会显著上升。在 Cyber RT 中,该链路大致如下:

# 伪代码示意:组件声明通道
class LidarDriver : public cyber::Component<PointCloud> {
  bool Init() {
    writer_ = node_->CreateWriter<PointCloud>("/apollo/sensor/lidar/front");
    return true;
  }
  bool Proc(const std::shared_ptr<PointCloud>& msg) {
    writer_->Write(msg);   // 同机默认走共享内存
    return true;
  }
};

class Perception : public cyber::Component<PointCloud> {
  bool Init() {
    reader_ = node_->CreateReader<PointCloud>(
        "/apollo/sensor/lidar/front",
        [this](const std::shared_ptr<PointCloud>& msg) { Process(msg); });
    return true;
  }
};

当驱动与感知位于同一主机时,Cyber RT 的 Transport 会自动选择 SHM 通道;若感知被部署到另一台计算单元,则自动切换为 RTPS 网络传输。算法代码无需修改,这正是中间件“屏蔽传输差异”的价值。

工程建议:在单机内尽量让高带宽数据(点云、图像)走共享内存;跨机通信前先评估带宽与延迟,必要时对消息做降采样或压缩,避免网络成为瓶颈。

五、常见误区与复盘

  • 误区一:用了 Cyber RT 就一定零拷贝。零拷贝主要发生在同机共享内存路径;跨机 RTPS 仍需序列化与网络发送,并非零拷贝。
  • 误区二:共享内存越大越好。Segment 过大浪费内存,过小则频繁分配回收;应结合消息大小与并发读者数估算 Block 数量。
  • 误区三:忽略读者释放。若读者长期持有 Block 引用,发布方会因无可用 Block 而阻塞或丢弃数据,需关注引用计数与超时策略。
  • 误区四:把通信中间件当业务框架。Cyber RT 负责调度与传输,业务逻辑、数据校验、异常恢复仍需算法模块自身保证。

理解 Cyber RT 的定位、分层与共享内存角色,是后续深入零拷贝工程实践的基础:只有先看清数据在“组件—通道—传输—共享内存”这条链路上如何流动,才能在调优时准确判断瓶颈究竟在调度、序列化还是内存管理环节。