作者 | 胡冲
出品 | 汽车电子与软件
一、前言
实时性(RealTime/Real-Time)是嵌入式软件领域一个关键性能指标,也是计算机系统领域一个老生常谈的话题。本文将从工程实践的角度系统性描述软件实时性的概念、影响软件实时性的因素,以及如何提高软件实时性。虽部分内容是基于Linux描述,但相关思想和方法也适用于其它通用操作系统(General Purpose Operating System,后文简称OS)。Linux的应用非常广泛,相关问题比较有代表性,且是开源软件,资源比较丰富,相应的性能提升也能带来更大的价值。掌握OS的基本概念如中断、异常、线程、调度等有助于更好的理解本文。
二、实时性概述
2.1 实时性概念
很多人容易对实时性产生误解,认为实时性是衡量完成一个任务的最快时间。相反实时性关注的不是完成一个任务的最快时间,而是完成这个任务的最差时间。在对实时性进行更深入探讨前,先简单回顾下软件实时性问题的由来。实时性的概念是伴随着OS产生的,在没有OS之前,基于图灵机模型的计算机按照逻辑串行执行指令,在系统层面不存在影响任务实时性和确定性的因素。随着软硬件技术的发展,有了OS、中断处理、调度、抢占、临界区、SMP等概念,影响实时性的一些关键要素就出现了(虽然裸机也有中断,但不是本文关注点)。
在某些应用场景,如果任务实时性不达标,会导致非常严重的后果如交通、航天、医疗等安全关键领域,因此实时性在这些领域是一个非常重要的技术指标。维基百科对计算机领域实时的定义已非常准确,直接引用如下:
Atask in a software-controlled system is called real-time (or "executable in real-time, in real-time") if the combined response and execution time of the task is less than the maximum time allowed, taking into account outside influences.
简单总结即“响应时间+执行时间小于允许的最大时间,则称为实时”。使用图1的模型表示实时的概念会更形象一些:
图1 实时性概念
CPU按序执行指令流,在T0时刻发生外部中断(也适用于如信号量、文件描述符事件等内部事件),CPU因为某些因素在经过一段时间的延迟后进入事件响应函数,此时定义为T1,从进入事件响应函数到事件完全处理完成,到达T2结束完成整个事件处理流程。为了统一语言,把T0~T1描述为响应时间也称为延迟Latency,T1~T2描述为任务处理时间,把M称为Deadline,按照这种方式划分,可适用于任何对实时性有要求的场景。如外部事件是中断,则T0~T1为中断响应时间或中断延迟,T1~T2为中断处理时间;如外部事件是一个资源就绪的调度事件,则T0~T1为调度响应时间或调度延迟,T1~T2为调度处理时间。响应时间/处理时间的最大值与最小值之间的差值,称为抖动Jitter,对于需要周期性完成的任务来说抖动是很重要的一个指标。任务在执行过程中发生切换,称之为抢占,实时任务对非实时任务的抢占是确保实时性非常重要的机制。
关于实时性还有硬实时和软实时的概念,图1中若”T2 - T0 < M”恒为真则为硬实时,而如果是基于一定条件使”T2 - T0 < M”为真则为软实时,如T2 - T0在一段时间内的平均值小于Deadline,则可以称为软实时。
Deadline是一个期望值,根据需求确定,但是T2-T0却是一个动态值,在不同的应用场景或者系统内外部状态下值是不同的,也就是上文说的抖动,如何找到Max(T2-T0)就是对实时性进行验证的关键。业界通常使用可调度性分析对软件实时性、确定性进行验证,ISO26262对高功能安全等级软件的可调度性分析是强制要求。可调度性分析可通过对系统软件架构(必要时分析可深入至代码级)的数据流、控制流、执行路径的分析,构造对应的测试用例,通过测试验证系统的可调度性,如Linux常用的cycletest就是通过测试对系统的实时性进行验证。对实时应用(后简称RT App)执行的实时性验证,需要计算应用处理的最大时间和最长执行路径,在计算机系统领域通常使用最差执行时间(Worst-case execution time,简称WCET)表示,WCET的计算和验证也有多种方法,包括抽象解释、静态分析、测试验证等。关于实时性的验证,内容非常的多,为了确保本文不发散,这里仅做概念介绍,不再深入分析。
为了确保”T2 - T0 < M”,在M是常量的情况下,需要减少T0~T2的时间跨度,并在这个时间跨度内合理的分配响应和处理时间。对于开放式OS,其部署多个具备不同业务特征且Deadline不同的实时任务,单纯的依靠OS提供的调度策略或实时性机制很难保证系统内所有实时任务满足其Deadline,需要配合任务的到达时间arrive time、松弛时间slack time等进行定量分析计算并制定相应的调度策略。
2.2影响实时性的关键因素
在讨论影响软件实时性因素之前,需要界定一个范围,因为影响实时性的因素太多,芯片、内存(cache)、外设、软件等等。通常来说硬件特性会显著影响软件的执行速度,如CPU是否多核,主频高低,是否有MMU,是否有特权级切换,是否有cache,几级cache,CPU是否动态调频,内存是什么介质,访问方式是UMA还是NUMA等?进而影响系统实时性,但这部分内容与特定平台相关,为避免发散本文仅讨论软件因素。
为了统一共识,这里限定一个具体的场景,该场景能将影响实时性的相关因素都串起来,后续的分析也基于此:“实时应用RT App阻塞等待某OS资源,而该资源又依赖外部中断”,场景可实例化为RT App通过read接口阻塞在网络socket上,系统在socket数据就绪后唤醒RT App任务读取网卡报文,并完成数据处理。
按照图1的模型,外部事件为网卡中断,T0~T1为网卡驱动与协议栈处理并唤醒阻塞在read调用的任务,即网络报文的响应时间;而T1~T2是RT App对网络报文的处理时间。先从OS负责的响应时间部分开始如图2,这里将响应时间T0~T1继续分解,得出如下几个点:
T0:外部发生网卡物理中断
T01:CPU进入中断处理程序(Interrupt Service Routine)ISR
T02:完成网卡驱动与协议栈处理唤醒目标线程,也称就绪时间或Arrival Time
T03:目标线程被选为CPU的当前任务
图2 响应时间分解
实际环境中,在上述几个时间点之间仍然可能会出现新的中断、异常、非预期调度、负载均衡等(实际系统在做实时性优化时需要尽量避免这些情况发生),但是这些行为最终回归到上述四个步骤,不影响本文分析。
从上图可以看出,在操作系统层面,影响任务实时性的关键因素有这四个:
- T0~T01中断延迟(外部中断发生到CPU执行中断处理程序间的延迟)
- T01~T02中断处理开销(为了简化模型,把一些内核功能如协议栈的开销也算在中断处理里了)
- T02~T03调度延迟(任务等待的资源就绪,到CPU选定目标任务之间的延迟)
- T03~T1调度开销(上下文切换,返回到用户态RT App阻塞点)
T0~T01中断延迟主要由受系统关中断影响,因为中断发生时系统可能处于关中断状态,在关中断状态下CPU会暂时屏蔽外部信号,在打开中断后才响应该外部中断。如系统当前正在执行的代码不能被中断打断时,调用接口屏蔽外部中断,处理完成后再打开外部中断。显然关中断的代码范围越大执行时间越长,可能的中断延迟也会越大。对于支持中断优先级的芯片,还受中断优先级影响,高优先级中断会优先于低优先级中断被处理。
T01~T02中断处理开销主要受中断处理逻辑复杂度影响,操作系统为提高中断处理速度,通常会分段处理中断,把一些关键性操作放在ISR中处理,这部分代码必须处于关中断状态运行。把更耗时的操作在开中断状态下执行,如Linux的软中断机制,再比如微内核架构的OS会将复杂的中段处理放在用户态线程处理。在OS内核处理中断或者其它必要流程中可能会访问临界区,这时会使用自旋锁或者互斥锁,这些操作也会影响中断处理时长,进而影响整个系统实时性。在工程实践上影响内核实时性、确定性很多时候都是这些临界区访问导致的。所有中断相关的处理工作完成后,OS通过调度接口唤醒目标任务进入下一阶段。
图3 Linux应用运行模型
T02~T03调度延迟主要受系统关调度(也称关抢占)、调度点(也称抢占点)的影响。当目标任务所需资源就绪时,目标任务所在的CPU上下文可能处于关调度的状态,禁止抢占,必须等待打开调度后才能发生调度行为,同关中断一样,关调度的范围越大,可能的调度延迟也会越大,遇到这种情况OS一般会设置一个标志,等待调度发生。同时由于调度是软件行为,必须有调度点才能发生调度,即目标任务就绪,目标任务所在CPU也处于可调度状态,此时需要有合适的调度点让CPU发生调度行为。调度点又有主动调度点和系统调度点。主动调度点是指调用了可能引起阻塞的函数引起系统调度,而系统调度点是指系统在运行过程中的调度,非应用主动发起,也称抢占点,即当前任务在未主动让出CPU的情况下被动发生的调度,如Linux内核中断处理完成或者从内核态返回用户态时。
T03~T1调度开销是系统的基础开销,受场景和OS具体实现影响如是同一地址空间内线程切换还是进程间切换、是否有特权级切换、是否实时线程、采用哪种调度策略、是否有资源配额限制等,这些因素会显著的影响调度的开销。当调度发生,已就绪的目标线程是否被选为当前任务取决于系统的调度策略。如Linux默认采用的公平调度算法,任务根据动态优先级共享CPU。这在很大程度上保证了系统的公平,却导致实时性的降低。此时基于优先级的调度策略就派上了用场,当高优先级任务就绪时,抢占低优先级任务,不管低优先级任务是否完成,一般的实时OS均支持基于优先级的调度策略。使用优先级的调度策略,在使用加锁的共享资源时,可能会遇到优先级反转问题,导致高优先级任务的实时性降低。另外系统中可能存在多个实时任务,影响多个实时任务的实时性的因素还包括这些实时任务的DeadLine的先后以及任务执行时机,仅按优先级处理也可能会导致任务DeadLine的失守。
表1:
至此完成影响系统响应时间的分析,总结内容如表1。下面对RT App负责的T1~T2处理时间进行分析,从应用编程的角度出发,RT App被唤醒后有一般三种运行模式:
1.执行计算逻辑
2.执行系统调用,访问系统资源
3.线程间、进程间通信
工程真实场景是上述三种情况的各种可能组合,但是影响实时性的因素主要与上述三种情况有关。
刨除RT App业务自身算法逻辑、调试、BUG等对计算时间的影响,在计算资源充足的情况下,影响计算逻辑时长的可能因素主要是内外部事件的打断和抢占。关于内外部事件打断,是指RT App在执行过程中频繁被外设中断、异常、信号等打断,这些打断会显著的影响应用执行的确定性。中断一般属于不可屏蔽事件,如系统定时器、各种外设的IO访问;异常最常见的是缺页异常,OS为提高应用内存使用效率,对物理内存的映射是延迟执行的,如在缺页异常中完成,当任务首次访问某内存页面或首次执行某代码段时,会造成缺页异常,触发相应的内存分配、映射机制,这部分操作的不确定性会影响任务的计算时间。内存回收是指OS可用内存低于某个水线时,系统对可回收内存如cache进行回收,之后再重新分配给应用,这个过程可能比较耗时,进而影响内存分配的实时性,在某些极端情况下触发OOM(Out Of Memory)和LMK(Low Memory Killer),会导致更多非预期行为。关于信号是指POSIX信号,OS一般会在特权级切换时处理信号事件,这也会导致任务的计算被延迟。关于抢占,与任务本身的计算无关了,是当前任务被更高优先级的任务抢占或者由于配置了cgroup等配额管理导致没有计算资源可用。除此之外,在某些架构的处理器上可能还有一些调试异常、字节非对齐异常等打断任务的执行。
RT App执行系统调用,又有几种情况,一种是阻塞式系统调用,阻塞时长的不确定性会导致Deadline的失守,如waitpid;第二种情况是该系统调用本身比较慢,如操作串口IO的printf,串口打印会显著拖慢应用的执行速度就是这个原因。还有一种情况是该系统调用本身不会阻塞,但是在该系统调用的实现中访问了全局资源导致阻塞。
在多核多任务系统上线程间同步互斥或者进程间通信可能会导致当前任务的阻塞,也会显著的影响计算确定性,进而导致Deadline的失守,如mutex lock。
站在OS的角度看影响T1~T2处理时间的因素,就两个:
1.线程执行的非预期打断
2.可能导致线程阻塞的调度行为
图4 APP视角的运行模型
站在目标任务Rt App的角度上,影响T1~T2处理时间的因素就一个即RT App让出CPU去执行其它代码。毕竟CPU永远都是在按照设计流程执行计算,并无刻意偷懒。
除此之外系统对计算资源的分配也会显著影响任务的实时性。如图5:a、b、c三个同优先级的任务同时到达,如果按图示的执行顺序执行则只有Da(a任务的Deadline)能满足,其它两个任务的Deadline都会失守,但如果把a任务的执行适当延迟到b、c任务之后,则可以保证三个任务的目标Deadline都满足。
图5 实时任务调度
通过上述分析,可以看出影响任务实时性的主要因素基本与中断(包含内部中断和外部中断)、调度时机、调度策略、资源分配有关,掌握这些原理能帮助研发人员更好的理解实时性问题的分析和解决方法。
三、如何解决实时性问题
3.1 实时性问题分析
书接上文,在动手解决系统实时性问题前,需要先明确系统的实时性瓶颈在哪里,根因是什么,以及制定优化后的目标。不同的系统、不同的场景,其面临的实时性需求差异很大,没有统一的答案,甚至没有统一的方法。留下本章节的目的主要是想说明这部分内容的重要性,后续的优化措施都是从这里出发,可根据第二章节的描述,大胆假设、小心求证,一步步接近真相。有道无术,尚可求也,若需求或者方向错了,一切皆是惘然。
3.2 RT App的实时优化
据2.2章节分析所述影响RtApp实时性的因素主要是内外部事件打断和调度引起的阻塞。这两种因素都可以视为外部干扰,针对干扰,最好的优化措施是防止干扰,次之是降低干扰,安全OS一般都提供了丰富的免打扰机制。免于干扰是一个非常重要的安全功能,它能提高系统的实时性、确定性、安全性,在进行安全相关业务的开发和部署时一定要充分利用免于干扰的特性。
Linux、Unix、QNX等POSIX OS的很多免于干扰机制都是在进程上生效的,因此实时任务要和其它通用任务做隔离,即使是实时任务间仍然可以使用线程隔离运行。在资源允许的情况下,可以使用核隔离加亲和性绑定的机制保证实时任务的运行隔离,使用isolcpus参数隔离出独立的CPU核用作实时任务的运行,加上亲和性绑定,可以保证除内核必要的系统线程外,没有其它线程能干扰到绑定到隔离核上实时任务的运行。对于内存使用导致的缺页异常或者可能的分配不及时,可以使用预先分配内存资源加mlockall资源锁定的方法解决。RtApp在启动后可以预先将所需的内存资源全部申请好,然后使用mlockall将所有资源锁定,在后续的执行过程中就不会存在向系统申请资源的情况。资源的预先申请可以使用动态申请的方式,也可以使用静态分配的方式。静态分配的方式确定性更好也是功能安全相关标准强烈推荐的内存使用方式,但是需要RtApp在设计实现时就确定相关资源;动态申请的方式需要RtApp先从内核将资源申请好,然后在用户态由应用自己维护内存池,提供定制化的动态申请释放接口,gibc库也有一定缓存管理机制,但是不一定满足应用的定制化诉求。不管是哪种方式,核心思想都是在使用资源时保证其确定性,而不是和系统中的其它任务竞争。
图6免于干扰的模型
除了对实时任务进行设置外,还可以通过对其它非实时任务进行限制排除或降低运行干扰。在运行上可以通过调整非实时任务的优先级降低其CPU使用率,也可以通过一些工具如cpulimit、nice等命令控制任务的CPU占用率,控制组cgroup机制功能强大,可控制包含CPU、内存等资源的使用,也可使用POSIX接口setrlimit控制内存等资源使用上限。
对于因RtApp主动调度降低实时性的问题,需要靠RtApp在设计实现时避免使用此类接口,避免使用临界区,通常规则是在做实时操作时可以释放资源如锁、信号量等,但是不要获取资源。对于阻塞式操作如IO操作,可以使用POSIX规范设置IO_NONBLOCK非阻塞工作模式,在该模式下IO操作以非阻塞模式运行,任务根据资源的就绪情况进行处理。对于被抢占,需要根据系统可调度性分析结果,根据需求合理的设计各任务之间的调度策略、优先级、任务执行时机等,同时在实时任务间进行互斥时,使用带有优先级继承的mutex锁,防止出现反转。
关于调度策略和计算资源在多实时任务间的分配,这里以Linux为例介绍一下常见的调度策略,其中非实时调度策略为:SCHED_OTHER、SCHED_BATCH、SCHED_IDLE,这三个调度策略大同小异,都是基于动态nice值的公平调度。实时调度策略为:SCHED_FIFO、SCHED_RR、SCHED_DEADLINE。SCHED_RR和SCHED_FIFO都是基于固定优先级的调度策略,其中RR相较于FIFO的区别在与优先级相同情况下是否会主动让出CPU,而SCHED_DEADLINE是根据Deadline的先后来调度的也就是EDF调度算法,EDF可抢占FIFO、RR的任务。在进行计算资源分配时需要结合业务特征选择合适的调度算法,防止出现图5情况的发生。
针对一些其它打断如软硬中断太多,可通过亲和性设置把中断和软中断线程绑定到其他核上;对定时信号打断可更换定时器的使用方式为timerfd,在资源允许的情况下对于一些场景,还可能使用轮询模式代替中断模式来提高实时性,但是此类情况需要具体问题具体分析了。
针对自动驾驶应用场景,普遍使用CPU、GPU、BPU、DSP、NPU等异构核混合计算,存在大量任务覆盖感知、规划、决策全流程,基于事件驱动并完全交由OS编排调度,存在任务执行抖动大的问题。而基于有向无环图的方式组织计算任务,并根据图的特性对任务进行编排能大大提高任务的实时性,这种方法降低了任务运行确定性对于OS的依赖,且保持较高的灵活性,百度Apollo的Cyber以及英伟达DriveWorks的CGF均使用了该方法。
总结下来对于提高任务实时性的核心思想就是通过使用确定性资源,隔离或减少外部干扰,避免执行被非预期打断达到时间、空间的确定性,对于多任务场景还需要结合其它任务的特征选择合适的调度策略。
3.3 OS实时优化方案
在给出具体的优化措施分析前,回到表1分析出的影响实时性的关键因素,从源头出发,给出解决OS实时性问题的思路,如表2。
表2
后文将根据这个优化思路基于Linux这个应用广泛的OS,描述在工程实践中针对OS的实时性增强,通常的措施有如下一些:
1.根据业务场景需求调整实时中断的优先级或亲和性,确保实时任务关注的中断能够被及时的响应,合理设置中断的触发时机,规避中断风暴。
2.提高内核时钟频率,也就是CONFIG\_HZ,增加内核时钟HZ可以显著的提高任务响应的实时性,本质上是通过增加定时中断,变相增加调度点,因为Linux内核在中断处理完成时会发起调度。同理,取消CONFIG\_NO\_HZ的配置,也是相同的道理,该配置会减少内核无用定时器中断,降低开销,进而变相减少调度点。
3.不论是哪种OS内核,在开发实现时都需要减少锁的使用,尤其是多个场景或者关键路径上同时持有一个锁是需要尽量避免的,可通过大锁换小锁或者无锁设计来解决。即使必须使用锁,也需根据场景选择开销最小的方式,如读写锁、互斥锁、自旋锁。
4.打开内核抢占,大部分OS包括Linux都是支持抢占模式的,用户可根据自己的应用场景选择配置不同的抢占模式,在下图menuconfig中已很明确的说明了每种配置所支持的应用场景:强调吞吐性和可用性的服务器模式(无抢占点,此模式下内核态代码完全无法抢占);强调均衡性能以及用户响应的桌面模式(此模式下通过硬编码增加了一些自愿抢占点);基于桌面的低延迟增强模式(此模式相对于之前的模式抢占点增加更多)。这三种模式本质上都是做一件事,就是在内核的各种关键路径上逐级递增调度点。
图7 Linux内核抢占模式
5.Linux RT补丁,打上该补丁后,Linux的抢占模式还会增加两种,每种模式都包含一些优化措施,这些措施本质上依然是两点:减少关中断、关抢占的代码路径与增加更多的调度点。如中断线程化,该措施会更进一步减少关中断执行的代码范围,把更多的处理逻辑放到中断线程中去做,线程上下文是可打断可抢占的;再如通过rt_mutex 重新实现spinlock_t,Linux内核的spinlock机制是一种CPU锁机制,在没打RT补丁时,一旦调用该锁,则禁止中断或禁止抢占,并持续占有CPU直到锁资源就绪,非常影响实时性。重写spinlock后内核中大量使用自旋锁spinlock的地方变成了可以调度的点,且不会无意义的空耗CPU。
6.如果不局限实时任务的处理必须在用户态,可以在内核开发实时任务,这种方法配合上述其它措施,可大大提高任务的实时性,因为任务处于内核态,不会有特权级切换,不会有缺页异常,上下文切换导致的cachemiss开销也会小很多,经典RTOS所有任务都运行在一个地址空间,在这一点上有很大优势,即使对于微内核OS,在工程实践中也可以通过将部分关键任务放在内核态来提高其实时性能如定时器管理任务。
7.除了上述专门针对实时性的优化措施外,还有部分内核调试、安全检查功能对于实时性影响较大,如lockup检测机制以及一些调试功能如ftrace,这些功能单点耗时并不多,但是加在一起也是一个很可观的开销。
对于Linux,完成上述优化措施后,在不修改内核接口以及框架的情况下,通过打补丁的方式能做的优化措施也已经基本做完了。通常来讲,此时Linux任务的实时性会有一个很明显的提升,但是是否可以做到硬实时?实践中对于多数场景已经够了,但是Linux内核功能的丰富性和复杂性决定了其依然有大量的不确定性,为此就引出了后续的一些优化方案。
8.基于Xenomai或EVL的双域方案(还有更早的RTLinux),该方案采用双域,将内核分为两个域,一个功能简单结构精简的EVL Core实时域,一个非实时的Linux域,两个域共同使用一个硬件抽象层,实时域提供一套独立的接口供上层RT App使用。该方案比较重要的几个点是:
- Linux域的所有代码均可被中断打断,Linux域的关中断代码被重写了,实际上不会关硬件中断,而仅是置一个标志位
- 所有外设中断,均会先交由EVL Core实时域先处理,之后才交给Linux域处理,且不再受Linux域关中断影响,因此大大提高中断处理的及时性
- 运行在实时域的实时任务的调度不再依赖Linux域,因为EVL Core实时域的功能简单,调度延迟相较于Linux的调度延迟低很多,可大大提高调度及时性
- 为保证实时性,RtApp的变成需要使用实时域专用接口,否则可能导致运行域切换影响实时性
图8 EVL架构示意图
该方案也支持CPU核和内存资源的预先分配,可确保EVL Core实时域获得确定的CPU核和内存资源,该方案在X86平台上可做到10微秒级的延迟与抖动。把两个域作为一个整体来看,其核心依然是通过dovetail和evl Core大大降低了中断延迟与处理的时间以及EVL侧的调度延迟。
9.如果上述方案依然无法满足实时性需求,而又确实想使用Linux的生态和资源,业界仍然有解决方案,即基于内存分区、CPU核隔离的Linux+RTOS双系统方案,该方案的相较于EVL的双域方案更进一步,直接在物理层做了设备隔离,OS做了软件隔离,可确保Linux不会影响RTOS侧的实时业务,如果两个OS需要通信,既可通过外设通信,也可通过共享内存的机制实现。该方案在工业控制领域有不少成功的案例,且Linux也可切换为其它操作系统如Windows+RTX。
图9 Windows+RTX示意图
措施8、9都是通过不同的方法确保实时任务和非实时任务的隔离,且实时任务由于使用了精简的RT运行时确保任务的实时性与确定性。
3.4 实时性与应用场景探讨
实时和非实时都有其适用的应用场景,如对于任务执行时间确定,到达时间确定的周期性任务,采用RMA策略的实时OS能很好的满足其需求,但是也有部分场景不适合,比如手机、PC机、服务器等有大量各种类型任务同时并发的开放式应用场景。在上述这些应用场景里,如果系统内的任务全部设置为基于优先级调度,会是什么情况呢?假设优先级设置不合理,或者部分应用恶意调高优先级频繁占用CPU资源,会导致一些正常业务饿死,UI卡顿,用户会出现明显的不适。
在Mixed-critical系统里,既部署有实时任务,也部署有非实时任务,此时需要在保障实时任务的前提下也确保系统的可用性、吞吐性等,这种场景和需求下并不是所有的实时措施都适合。如在对RtApp进行优化时使用的免于干扰机制,不论是核隔离绑定、还是内存资源的预先确定对资源消耗都非常大,会对系统的可用性有影响,在系统资源受限时需要慎重使用;对于双域方案,则需要对业务的部署进行域的划分,且控制应用的编程接口和运行模式,适用于新开发的应用或者是容易移植的场景。在进行实时性优化时需要根据自己的应用场景对实时路径进行分解,明确实施指标并结合其它系统需求选择合适的方案。
除此之外实时性不是没有代价的,高实时性系统保障的是最差场景下的时间约束,而不是平均场景或者最常用场景,实际工程实践经验是其可能带来其它场景的性能下降。以下是一些在工程实践时需要特别注意的点:
1.亲和性设置,不论是中断的亲和性还是任务的亲和性,需要考虑负载的均衡,避免出现“忙死、饿死”等现象尤其是业务负荷比较复杂的mix-critical系统,可能会出现除实时性任务或实时中断能及时响应外,其它任务都不能及时响应的问题,最终仍然是不能满足项目目标的。
2.对于大量使用第三方或者开源组件的项目,mlockall需慎用,避免出现内存资源快速耗尽导致OOM的问题,如果系统出现OOM,需要配合调整相关实时任务的优先级确保其不会被LMK杀掉。
3.关于实时任务的优先级,不能设置成相同的最高99优先级,这会导致系统一些实时性要求不高,但是对于可用性非常重要的任务如Linux里用于各种同步的worker线程饿死,导致系统以其它形式卡死导致不可用,同时优先级不是万能的,优先级的设置必须符合业务特征且充分考虑了多个实时任务间的影响。
4.关于提高时钟频率,关闭动态调频、休眠等措施,会显著的提高系统平均功耗,这种情况下需要结合软件应用场景和硬件平台的特性进行优化,如外挂低功耗MCU做一些低功耗实时任务的方式解决。
5.关于Linux的RT补丁,其普适性是有争议的,尤其是对于大量第三方驱动的适配并未经过充分验证,盲目打RT补丁可能会导致系统的稳定性降低。
6.对于EVL或AMP这种双域/双系统方案,其对软件部署的模型、外设资源的分配都有要求,需要站在整个系统层面评估方案,避免出现返工。且EVL方案在提高EVL域的实时性的代价是降低了Linux侧的整体实时性与应用编程的便捷性。
7.部分优化措施是有矛盾点的,如为提高中断或调度响应的及时性需要增加抢占点和可中断区域,但是实时任务如果想提高实时性和确定性就必须减少系统中断和调度的干扰,最好是非抢占,这些措施之间的平衡需要结合场景和需求考量。
8.实时不等于端到端的快,尤其是多核多任务的长流程业务,为提高实时性额外增加的调度点和中断势必会增加系统本身的开销而降低应用可使用CPU的资源,且实时性与极限吞吐性的矛盾从原理上来说是不可调和的。实时性是一个需求,但不是唯一的需求,需要回到源头的应用场景和客户价值上来做判断。
9.最后关于系统实时性,不仅是OS或者单个实时任务的事情,而是整个软件产品里所有有实时诉求的任务、中间件、OS等各种可能的组合,针对这些具体场景需要结合系统的可调度性分析,配合工具进行优化和配置,避免头痛医头、脚痛医脚。
四、总结
本文介绍了实时性的概念,基于一个场景从应用和OS的角度分析了影响实时性的各种关键因素,并基于这些因素给出了相应的解决方案和思路。在最后章节探讨了这些解决方案的适用性及需要注意的问题。全文总结如下:
1.影响任务响应的最大因素为中断延迟和调度延迟,基于Linux,调度延迟尤甚
2.提高响应速度的关键是降低关中断、关调度的区域以及更多的调度点
3.提高任务实时性和确定性的关键是排除干扰和基于具体场景的计算资源分配
4.方法可以通用,方案源于需求,细节决定成败,没有包打天下的方案
作者: 胡冲
来源:汽车电子与软件
微信公众号:
推荐阅读:
更多汽车电子干货请关注汽车电子与软件专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。