智驾技术务虚会其三之智驾实时性讨论
1 前言和资料
本文我们继续智驾技术务虚会,重点讨论智驾或机器人编程的实时性问题。程序实时性属于熟悉的陌生人问题,多数程序员都听过,但又很难说出个所以然,大多数人都近似认为是高性能编程。本文尝试从理论定义到编码实践对这个问题进行系统概述。
本文参考资料如下:
(1)ECUandRTOS
(2)Understanding real-time programming
(3)realtime_background
(4)RealtimeROS2
(5)what-is-a-real-time-operating-system–rtos
(6)Ubuntu22.04编译实时内核安装preempt_rt实时补丁
(7)关于Linux安装RT-PREEMPT实时补丁
本系列博客汇总:Autoware.universe 高效学习系列。
2 正文
2.1 明确实时性概念
(1)实时计算(Real-time computing)是许多机器人系统的关键特征,尤其是自动驾驶汽车、航天器等安全关键系统(safety-critical),以及工业制造等任务关键系统(mission-critical)。
(2)实时计算(Real-time computing)的概念:
第一,确定性(determinism)是实时性的核心特征,而不是高性能(performance)或低延时(low-latency)。尽管很多实时系统也是低延时系统,但是实时系统不是由低延时定义的,而是确定性定义的,因此必须衡量系统的延迟,并设置最大值。
第二,确定性的核心是在正确的时间进行正确的计算(correct computation at the correct time)
第三,不回应或超时回应视为错误回应。
(3)三类实时系统:
硬实时系统(Hard real-time systems):有严格的截至时间要求,超时或超限(overrun)视为系统故障,可能造成重大后果。适用于安全关键系统(safety-critical),比如自动驾驶系统。
可靠实时系统(Firm real-time systems):截至时间要求在软/硬实时系统之间,超时或超限(overrun)后,结果视为不可用,但系统并不失败。适用于任务关键系统(mission-critical),比如财务系统,机器人装配线。
软实时系统(Soft real-time systems):截至时间要求并不严格,超时或超限(overrun)后,结果仍然可用,但会降低体验,且系统并不失败,一般用于低关键系统(Low-critical),比如影音娱乐。当超时频繁发生时,软实时系统会降低 QoS (服务质量)。
2.2 实时操作系统(RTOS)引入
(1)桌面操作系统(Desktop OS)与实时操作系统(Real-Time Operating System,RTOS)的比较:
(2)操作系统三种常用的调度策略:
Cooperative Scheduling (合作式调度):每个任务自主决定占用多少处理器时间,可以任务完成后释放处理器,也可以中途退出释放处理器。只要当前任务不主动释放,其余任务只能等待。这种设计简单且易于实现,在早期的操作系统中广泛采用。
Round-Robin Scheduling (轮询式调度):操作系统给每个任务分配不同的时间片,只要时间片到了,任务会被挂起,处理器去执行队列前面的其他任务。这种设计能提供更好的响应时间,是目前主流操作系统的做法。
Pre-emptive Scheduling (抢占式调度):根据任务的优先级,低优先级任务可随时被打断,从而确保高优先级任务总能及时得到处理,保证满足任务截至日期。
(3)实时操作系统的调度策略为 Pre-emptive Scheduling (抢占式调度),并且对外部中断任务设置了很高的优先级,确保关键任务满足截至时间要求。
补充:时间敏感网络(Time Sensitive Networking,TSN)属于硬实时系统的一种,本质是追求网络传输的确定性,因此核心依赖也是 Pre-emptive Scheduling (抢占式调度),即 IEEE Std 802.1Qbu Frame Preemption 。
(4)常见实时操作系统:
第一,QNX ,一款符合 POSIX 标准的实时操作系统,适用于硬实时系统,广泛用于汽车行业,尤其是自动驾驶(包括 ADAS)。很多主机厂明确指定 QNX 作为智驾软件操作系统。
第二,RT_PREEMPT Linux 内核补丁,可以使标准的 Linux 内核能够实时特性,适用于可靠实时系统。
补充:读者可以参考第一节提到的两个资料,尝试安装 RT_PREEMPT 补丁,测试运行效果。
第三,Xenomai ,一个独立的实时开发框架,符合 POSIX ,它模拟了传统实时操作系统的行为,提供了更严格的实时性保证。可与 Linux 内核协作,其负责实时任务处理,而 Linux 内核负责标准任务处理。适用于硬实时系统。
补充:VxWorks 也是实时操作系统,与 QNX 各有千秋,但汽车行业一般使用 QNX。
(5)使用 cyclictest 命令测试 RTOS 的抖动:
原理:cyclictest 会创建多个线程或进程,每个都设置一个周期性的定时器。一旦定时器到期,线程就会被唤醒。cyclictest 计算预定唤醒时间与实际唤醒时间之间的差异,并记录为“延迟”。通过长时间地运行并收集数据,cyclictest 可以提供系统调度的最大延迟和平均延迟。
执行方式:
# 安装 cyclictest
sudo apt-get install rt-tests
# 运行 cyclictest :启动五个线程,线程优先级为最高的99,定时器间隔为1000us,五个线程分开启动
sudo cyclictest -t5 -p99 --interval=1000 --distance=0
由于我的系统是 ubuntu 22.04桌面版,不是实时操作系统,可以看出最大延迟还是很大的。
2.3 实时编程指导
(1)实时性程序既需要实时操作系统(RTOS),也需要提供确定性执行的用户代码(实时编程)。非实时操作系统上的确定性用户代码或实时操作系统上的非确定性代码,都不会产生实时性能。
(2)实时编程三段式:
初始化阶段(非实时):申请堆内存,创建并启动线程等。
运行阶段(实时):正常运行阶段,一般是循环体实现,要求具有实时性。通常情况下,实时编程指的是这段代码的编程要求。
销毁阶段(非实时):释放堆内存,回收线程。
(3)实时编程指导之避免非确定性行为(更多细节参考:realtime_background):
Memory management (内存管理):不要在运行阶段使用 malloc/new 和 free/delete 进行内存管理,如果发生页面错误,处理器将暂停所有任务,造成任务处理预期。推荐的思路是在初始化阶段进行内存申请,必要情况下,维护自己的内存分配器,确保在运行阶段能以 O(1) 的复杂度进行堆内存分配和释放。
补充1:Linux 提供 mlockall 接口,可以提前锁定地址空间到 RAM 中,避免交互,避免页面错误。
补充2:全局和静态变量是在初始化阶段分配的,因此对他们的访问是实时安全的。
补充3:异常会占用大量的栈资源,造成性能损失,在实时编程中,应尽量避免使用。
Multithreaded Programming and Synchronization(多线程同步):线程应在初始化阶段创建,并设置较高的优先级。通常情况下,线程同步使用互斥锁(mutex),但是实时编程应使用无锁数据结构和算法。
补充4:由于会导致时钟漂移,实时编程不推荐使用 Spinlocks (自旋锁)
补充5:fork 函数不是实时安全的,实时编程不推荐使用。
日志:运行阶段应避免执行 printf,cout 这些非实时安全函数进行日志输出。可以实现自己的满足实时性要求的 log 库,通常情况 log 库会提供日志缓存,然后使用后台线程单独执行输出。
设备 I/O:运行阶段应避免进行设备 I/O 访问,比如文件读写,大概率会引入不可接受的延迟。
网络访问:运行阶段应避免进行网络访问,尤其是 TCP/IP 。ROS2 DDS 采用了实时友好协议 FastRTPS(ROS2高效学习第一章 – ros2整体介绍及DDS引入)。
3 总结
本文从实时性理论定义,到实时操作系统,再到实时编程指导,对软件实时性进行了理论上的系统梳理。下一篇我们将详细分析实时编程样例之倒立摆,学习如何基于 ros2 (使用 C++)实现实时编程。毕竟,理论弄的再清楚,不落地也是产生不了价值的。
2024.8.11更新:Ros2 实时编程实践系列 – 倒立摆 Inverted Pendulum