从零搭建自动驾驶中间件(一):为什么自动驾驶需要自研中间件

前言

如果你正在读这篇文章,大概率你已经接触过 ROS2 或 CyberRT,甚至可能正在维护一套自研的中间件系统。我也是这样------从事自动驾驶行业三年,从 ROS2 起步,到深度使用 CyberRT,再到参与 HyperFlow 的设计开发,对自动驾驶中间件有了不少踩坑和思考。

这个系列博客,我想把这些经验整理出来,聊聊如何从零搭建一套自动驾驶中间件。不是纸上谈兵的架构图,而是每个设计决策背后的工程考量和踩坑经验。


1. 先聊聊自动驾驶的"特殊性"

很多人会问:自动驾驶不就是机器人吗?为什么不能直接用 ROS2?

答案的关键在于自动驾驶对"确定性"的要求完全不同

1.1 一个刹车数据的生命周期

考虑这个场景:前方 50 米有障碍物,自车时速 120km/h。

复制代码
激光雷达检测到障碍物
    → 感知模块识别+分类         (~30ms)
    → 预测模块推演轨迹          (~10ms)
    → 规划模块生成避障路径       (~20ms)
    → 控制模块输出制动指令       (~5ms)
    → CAN 总线执行刹车           (~5ms)
──────────────────────────────────
总延迟:~70ms

在 120km/h 下,70ms = 车辆行驶 2.3 米

如果中间件的通信延迟再增加 10ms,刹车距离就多出 0.33 米。 在紧急场景下,这可能就是事故和安全的分界线。

这不是 ROS2 的 DDS 能轻松应对的------DDS 的序列化、发现协议、QoS 协商,每一层都在吃延迟。

1.2 自动驾驶的数据特征

特征 说明 对中间件的要求
高频传感器数据 激光雷达 10Hz、摄像头 30Hz、IMU 100Hz+ 高吞吐、低延迟
固定拓扑 传感器→感知→预测→规划→控制,拓扑固定 不需要动态发现
多消费者 同一帧点云可能被感知、定位、可视化同时消费 零拷贝、一写多读
大尺寸数据 一帧点云 100KB~2MB,一帧图像 5~20MB 避免数据拷贝
跨进程 安全隔离要求模块运行在不同进程 跨进程零拷贝通信
实时性 控制环路延迟 < 100ms 可预测的调度延迟

1.3 嵌入式部署的现实

自动驾驶计算平台不是你的开发机:

  • 算力受限:Orin NX 8GB 内存、8 核 A78,不像服务器可以随便开线程
  • 温度约束:仪表盘下 85°C 环境,CPU 不能长时间满载
  • 资源隔离:感知进程不能因为一个 bug 把控制进程的 CPU 抢光
  • 车载诊断:需要对接 MCU 的 DTC 故障码体系

这些需求,ROS2 一个都不原生支持,CyberRT 覆盖了一部分,但没有一家能 100% 满足你的项目需求。


2. 三大框架的真实对比

我深度使用过这三个框架,说说我的真实感受(不是官方文档的搬运)。

2.1 ROS2 --- 优雅但不适合上车

优点

  • 生态无敌。rviz2、moveit2、nav2、rosbag2......你需要的工具基本都有
  • DDS 标准化,理论上可以替换底层实现
  • 社区活跃,遇到问题 Stack Overflow 能搜到

痛点

  • 延迟不可控:DDS 的发现协议(SDP)在启动时可能花数秒,运行中的通信延迟在 1~10ms 量级波动
  • 序列化是硬伤:CDR 序列化对高频大数据(点云、图像)的开销不可忽视
  • 进程内通信也要过 DDS:同一个进程的两个 Node 通信,数据还是要序列化→DDS→反序列化
  • 没有调度:每个 Node 自己管线程,无法做全局调度优化
  • 没有诊断:车载 DTC 体系完全需要自建

我的经历:在 ROS2 上做过原型验证,很爽。但一到性能优化阶段,就开始和各种 DDS 参数搏斗------SHM 传输配置、QoS 策略选择、零拷贝配置......每个 DDS 实现还不一样。

2.2 CyberRT --- 强大但封闭

优点

  • 共享内存通信:Intra-process 直接回调,跨进程走 SHM Transport,延迟很低
  • 协程调度:Croutine 调度器,支持 Classic/Choreography 两种策略
  • Protobuf:比 CDR 高效,代码生成也好用
  • DAG 配置:可以通过 DAG 文件定义模块拓扑和调度策略
  • 优先级调度:Choreography 策略支持优先级和 CPU 亲和性

痛点

  • 序列化仍然存在:Protobuf 序列化/反序列化在跨进程通信中仍有开销
  • 闭源工具链:cyber_monitor、cyber_recorder 等工具不开源,出了问题没法调试
  • 依赖 Apollo 全家桶:很难脱离 Apollo 生态独立使用
  • 没有诊断系统:DTC 诊断需要自建
  • 没有交互终端:运行时观测只能靠 cyber_monitor,无法交互操控
  • 没有进程管理:依赖 Docker 容器做隔离,资源控制不够细粒度

我的经历:CyberRT 的调度设计确实牛,特别是 Choreography 策略。但 Protobuf 序列化在处理点云数据时还是能感受到开销,而且脱离 Apollo 单独使用 CyberRT 的文档几乎为零。

2.3 自研中间件 --- 适合你吗?

在决定自研之前,先问自己几个问题:

  1. 你的系统是否运行在嵌入式平台? 如果是 x86 服务器,ROS2 可能就够了
  2. 你是否需要 DTC 诊断? 如果不需要对接 MCU,CyberRT 可能就够了
  3. 你是否需要运行时交互调试? 如果能接受只有日志,ROS2 可能就够了
  4. 你的团队有多少人? 自研中间件至少需要 2-3 人持续维护
  5. 你是否需要极致的通信延迟? 如果 1ms 的波动可以接受,不需要自研

如果上述问题中你有 3 个以上的答案是"是",那么自研中间件值得考虑。


3. 自研中间件的设计哲学

在 HyperFlow 的设计中,我们坚持了三个核心原则:

3.1 零序列化

序列化是延迟的万恶之源。 在自动驾驶系统中,模块间传递的数据结构是固定的、平台是一致的,完全没有必要做序列化/反序列化。

复制代码
传统方案:                          HyperFlow:
  struct → serialize → bytes        struct → 直接写入共享内存
  bytes → deserialize → struct      共享内存 → 直接读取指针

代价是什么?跨平台兼容性。但自动驾驶的部署环境是固定的(Linux + ARM64/x86_64),这个代价完全可以接受。

3.2 零侵入

框架不应该侵入业务代码。 算法工程师不应该关心框架的存在。

  • 数据读写:Write<T>("data_name", data) / Read<T>("data_name", data)
  • 数据回灌:只需配置 "enable_snapshot": true,业务代码零改动
  • 时间同步:回灌模式下 GetTimestamp() 自动返回录制时间,业务代码无感知

3.3 可观测优先

运行时不知道系统在干什么是不可接受的。 嵌入式系统不像 Web 服务可以随时加日志。

  • 内置 Telnet 终端:运行时可直接查看模块状态、数据统计、调整参数
  • 内置诊断系统:DTC 故障码管理,直接对接 MCU
  • 内置进程监控:CPU/内存实时统计,异常自动重启

4. 中间件的核心模块拆解

一个完整的自动驾驶中间件,至少需要以下模块:

复制代码
┌─────────────────────────────────────────────────────┐
│                   自动驾驶中间件                       │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐  │
│  │  通信层       │  │  调度层       │  │  运维层    │  │
│  │              │  │              │  │           │  │
│  │ • 共享内存    │  │ • 协程调度    │  │ • 终端    │  │
│  │ • 零拷贝     │  │ • 事件驱动    │  │ • 诊断    │  │
│  │ • 跨进程     │  │ • 定时触发    │  │ • 监控    │  │
│  │ • 信号通知    │  │ • 触发器      │  │ • cgroup  │  │
│  └──────────────┘  └──────────────┘  └───────────┘  │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐  │
│  │  模块层       │  │  数据层       │  │  扩展层    │  │
│  │              │  │              │  │           │  │
│  │ • Module基类  │  │ • 数据录制    │  │ • 状态机  │  │
│  │ • 插件加载    │  │ • 数据回灌    │  │ • RPC    │  │
│  │ • 工厂注册    │  │ • 数据快照    │  │ • 存储    │  │
│  └──────────────┘  └──────────────┘  └───────────┘  │
└─────────────────────────────────────────────────────┘

这个系列博客将逐一展开每个模块的设计与实现:

篇章 主题 核心内容
(二) 共享内存通信 DataQueue 环形缓冲区、零拷贝读写、跨进程通知
(三) 事件驱动与协程调度 marl 协程、Trigger 机制、定时/事件双模式
(四) 数据录制与回灌 Snapshot 机制、MCAP 格式、frameid 匹配、时间源切换
(五) 状态机与诊断 层次化状态机、DTC 管理、进程监控与 cgroup
(六) 交互式终端 Telnet Server、命令树、按键状态机

5. 性能对比:数据会说话

为了给出一个直观的感受,我们在同一台机器上(Intel i7-12700H, 32GB DDR5, Ubuntu 22.04)做了简单的延迟对比测试:

注:以下数据为进程内通信的单次 round-trip 延迟,仅供参考。

框架 通信方式 平均延迟 P99 延迟
ROS2 (Humble) Intra-process (FastDDS) ~850μs ~3.2ms
CyberRT Intra-process (Intra) ~45μs ~120μs
HyperFlow 共享内存零拷贝 ~8μs ~25μs

差距的核心原因:

  1. ROS2:数据需要经过 DDS 层的序列化/反序列化,即使是进程内通信
  2. CyberRT:进程内通过直接回调避免序列化,但仍有调度开销
  3. HyperFlow:直接读写共享内存指针,无序列化、无额外调度层

当然,这个对比不完全公平------ROS2 有 QoS、CyberRT 有优先级调度,它们做的事情更多。但在自动驾驶的核心链路上,延迟就是安全


6. 写在最后

自研中间件不是炫技,而是需求驱动。如果你的项目对延迟、资源、诊断有极致要求,现有方案无法满足,那就值得投入。

但也要清醒地认识到代价:没有生态、没有社区、所有工具都要自建。我的建议是从最小可用版本开始,逐步演进------先搞定通信和调度,再扩展诊断和终端。

下一篇,我们将深入 HyperFlow 的通信层,聊聊如何从零设计一套共享内存零拷贝通信机制。


下期预告:《从零搭建自动驾驶中间件(二):共享内存零拷贝通信的工程实践》

相关推荐
IT策士1 小时前
AI skills研究:入门到精通
人工智能
cici158746 小时前
卡尔曼滤波器实现RBF神经网络训练
人工智能·深度学习·神经网络
Neolnfra10 小时前
拒绝数据“裸奔”!把顶级AI装进自己的硬盘,这款神仙开源工具我粉了
人工智能·开源·蓝耘maas
code_li10 小时前
只花了几分钟,用AI开发了一个微信小程序!(附教程)
人工智能·微信小程序·小程序
飞Link10 小时前
瑞萨联姻 Irida Labs:嵌入式开发者如何玩转“端侧视觉 AI”新范式?
人工智能
RSTJ_162510 小时前
PYTHON+AI LLM DAY THREETY-SEVEN
开发语言·人工智能·python
郝学胜-神的一滴10 小时前
深度学习优化核心:梯度下降与网络训练全解析
数据结构·人工智能·python·深度学习·算法·机器学习
Aision_10 小时前
Agent 为什么需要 Checkpoint?
人工智能·python·gpt·langchain·prompt·aigc·agi
小贺儿开发10 小时前
《唐朝诡事录之长安》——盛世马球
人工智能·unity·ai·shader·绘画·影视·互动