第三章 软件架构设计:自动驾驶软件分层与经典框架解析
各位同学,大家好。今天我们聊聊自动驾驶的软件架构。说实话,这是整个系统里最「虚」但也最「实」的部分。虚在你看不见摸不着,实在它决定了你的代码能不能跑起来、跑得稳、跑得久。
我个人习惯把软件架构比作盖楼。地基没打好,后面装修再漂亮也没用。自动驾驶这栋楼,地基就是操作系统和中间件,承重墙就是各个功能模块。今天我们就一层层拆开来看。
3.1 自动驾驶软件分层:OS、中间件、功能模块
先问大家一个问题:为什么一定要分层?
答案很简单——解耦。你想想看,感知团队和规划团队如果代码搅在一起,改个参数都要互相等,那项目就别想按期交付了。我在量产项目里见过太多这种惨痛教训。
典型的自动驾驶软件架构分三层:
| 层级 | 职责 | 典型组件 |
|---|---|---|
| 操作系统层(OS) | 硬件抽象、资源管理、进程调度 | Linux(PREEMPT_RT)、QNX、VxWorks |
| 中间件层 | 通信、数据分发、服务发现、时间同步 | ROS 2、CyberRT、DDS(FastDDS、CycloneDDS) |
| 功能模块层 | 感知、定位、预测、规划、控制 | 目标检测、卡尔曼滤波、A*、PID |
3.1.1 操作系统层——选对「地基」
量产车上的操作系统,和你在PC上用的Ubuntu完全不是一回事。我刚开始做量产时,天真地以为装个普通Linux就行。结果呢?车辆在高速上遇到紧急刹车,调度延迟直接飙到50ms。50ms啊,车都撞上了。
所以量产项目必须用实时操作系统。目前主流选择:
- QNX:黑莓家的,功能安全认证(ISO 26262 ASIL-D)最全。奔驰、宝马都在用。缺点?贵,而且生态封闭。
- Linux with PREEMPT_RT:开源,生态好。但要做大量内核裁剪和优先级配置。我踩过的坑是——网卡中断优先级设错了,导致控制指令被卡住。
- VxWorks:老牌RTOS,航天级可靠性。但开发体验嘛...嗯,你懂的。
核心原则:OS层要保证「确定性」。说白了就是——同样的输入,每次输出的时间偏差必须在微秒级。做不到这一点,后面的调度全是空谈。
3.1.2 中间件层——让模块「说同一种语言」
中间件是自动驾驶的「神经系统」。感知模块发现了行人,怎么告诉规划模块?靠中间件。
我参与的第一个量产项目,团队自己撸了一套通信协议。结果呢?每加一个新模块,就要改一遍协议。后来我们全切到了DDS(Data Distribution Service)。
DDS的好处:
- 去中心化:没有单点故障。一个节点挂了,不影响其他节点。
- QoS策略:可以设置可靠性、时效性。比如控制指令必须「可靠+实时」,而摄像头画面可以「尽力而为」。
- 自动发现:新模块上线,自动注册,不用手动配置IP。
不过DDS也有坑。我记得有一次,两个模块的QoS策略不匹配,导致数据一直收不到。排查了整整两天。后来我们定了个规矩:所有模块的QoS模板统一管理。
3.1.3 功能模块层——各司其职
这一层大家最熟悉。感知、定位、预测、规划、控制,每个模块各干各的。但要注意:
- 模块间接口必须稳定。我见过最离谱的事——规划模块改了输出结构体,感知模块没同步更新,结果车直接往墙上开。
- 每个模块要有独立的状态机。初始化、运行、降级、故障。不能一个模块崩了,全车瘫痪。
3.2 经典框架解析:Apollo vs Autoware
说到自动驾驶框架,绕不开两个名字:百度的Apollo和开源的Autoware。我都深度用过,说说真实感受。
3.2.1 Apollo——工业级「巨无霸」
Apollo给我的第一印象:重。非常重。但重有重的好处。
- 架构:基于CyberRT中间件。CyberRT是百度自研的,性能比ROS 2强不少。我记得做过压测,CyberRT在1000个topic下延迟依然稳定在1ms以内。
- 模块划分:感知、预测、规划、控制,每个模块都有完整的工程化实现。特别是规划模块的EM Planner,在结构化道路上表现很好。
- 工具链:Dreamview可视化、数据回放、仿真测试,一套全齐。
但Apollo的缺点也很明显:
- 代码耦合度高。想单独拿一个模块出来用?基本不可能。
- 升级成本高。从3.0升到5.0,接口全变了。我有个朋友的公司,升级花了三个月。
我的建议:如果你做的是L4级Robotaxi,Apollo是很好的起点。但如果是L2+量产项目,建议只参考它的架构思路,不要直接搬代码。
3.2.2 Autoware——灵活但「散装」
Autoware是ROS生态下的开源框架。我用它做过一个园区物流项目。
- 架构:基于ROS 2。模块化做得很好,每个功能都是一个独立的node。想换算法?直接替换对应node就行。
- 灵活性:支持多种传感器配置。激光雷达、摄像头、毫米波雷达,随便组合。
- 社区活跃:GitHub上issue回复很快。我提过一个关于NDT配准的bug,两天就有人回复了。
但Autoware的问题:
- 实时性不足。ROS 2虽然比ROS 1好很多,但和CyberRT比还是有差距。我在高速场景下遇到过调度抖动。
- 缺少量产验证。目前还没有看到基于Autoware的量产车型。
避坑指南:我曾经尝试把Autoware的感知模块直接移植到量产平台。结果发现它依赖的ROS 2库版本和我们的RTOS不兼容。后来花了三周做适配。所以——用开源框架前,先确认你的目标平台支持哪些依赖。
3.3 实时性与确定性调度
这是量产落地的核心难点。很多团队在原型阶段跑得挺好,一上车就出问题。为什么?因为原型阶段用的是普通Linux,调度是「尽力而为」的。而量产车要求「确定性」。
3.3.1 什么是确定性调度?
简单说:每个任务必须在规定时间内完成。比如控制模块,周期是10ms。那么无论系统负载多高,控制任务必须在10ms内跑完。
我见过一个案例:某团队在测试时,感知模块偶尔超时,但概率很低(0.1%)。他们觉得无所谓。结果量产车在高速上遇到一次超时,直接导致控制指令延迟,车辆偏离车道。0.1%的概率,在百万辆车上就是1000次事故。
3.3.2 如何保证确定性?
几个关键手段:
- 任务优先级分配:控制 > 规划 > 感知 > 日志。控制任务必须是最高优先级,且不能被抢占。
- CPU隔离:把关键任务绑定到特定CPU核心上。比如核心0跑控制,核心1跑规划,核心2跑感知。互不干扰。
- 内存锁定:防止关键任务的内存被换出到swap。用mlockall()锁定。
- 中断管理:把非关键中断(比如USB热插拔)绑定到非关键核心上。
一个实用的检查清单:
- 所有关键任务的WCET(最差执行时间)是否已测量?
- 是否有任务会抢占控制任务?
- 中断处理函数是否过长?
- 内存分配是否会在运行时触发page fault?
3.3.3 调度策略实战
在Linux PREEMPT_RT下,我常用的配置:
# 设置控制任务为SCHED_FIFO,优先级99
chrt -f -p 99 $(pidof control_node)
# 设置规划任务为SCHED_FIFO,优先级80
chrt -f -p 80 $(pidof planning_node)
# 设置感知任务为SCHED_FIFO,优先级60
chrt -f -p 60 $(pidof perception_node)
# 将控制任务绑定到CPU0
taskset -c 0 -p $(pidof control_node)
注意:SCHED_FIFO优先级高的任务会一直运行,直到主动让出CPU。所以控制任务里绝对不能有死循环或长时间阻塞操作。我曾经犯过这个错——控制任务里加了个printf调试,结果printf阻塞了,整个系统卡死。
3.4 小结
今天的内容信息量不小。总结几个关键点:
- 软件分层是解耦的基础。OS、中间件、功能模块各司其职。
- Apollo适合L4级项目,Autoware适合快速原型。量产选型要慎重。
- 确定性调度是量产的生命线。优先级、CPU隔离、内存锁定,缺一不可。
下一章我们聊感知模块的工程化实现。到时候我会分享一些传感器标定和融合的实战经验。嗯,先到这里,有问题随时交流。