APP下载

基于mbedOS的事件机制响应调度的实时性分析

2023-08-10王宜怀刘长勇

计算机应用与软件 2023年7期
关键词:蓝灯实时性线程

汪 恒 王宜怀 刘 肖 刘长勇,2

1(苏州大学计算机科学与技术学院 江苏 苏州 215006) 2(武夷学院认知计算与智能信息处理福建省高校重点实验室 福建 武夷山 354300)

0 引 言

mbedOS是由ARM公司针对ARM CortexM系列处理器专门打造用于物联网(IoT)的一种开源嵌入式实时操作系统(RTOS)[1-2]。对于实时操作系统来说,其实时性是其最重要的特征,它必须对所接收到的某些信号做出“及时”或“实时”的反应,在规定的时间内完成任务的处理[3-5]。而mbedOS提供了相当多的系统服务来实现任务的实时控制,如事件机制、线程信号机制、消息队列机制、互斥锁机制和信号量机制等[6-7],有着优越的实时性,故通常被用于对响应时间有较高实时性要求的嵌入式系统,如智能终端和物联网节点等。

目前有关RTOS的实时性分析大多是从整体的实时能力来考虑,通过上下文切换时间、中断响应速度等指标进行总体的实时性考量[8]。但实际应用RTOS来进行任务的控制时,其实时性的高低通常会受到使用的控制机制影响,针对具体机制进行实时性分析,能为应用该机制提供有效的参考。对此,提出一种基于printf函数的时序分析方法,对mbedOS中事件机制的实时性进行剖析。printf函数是嵌入式开发中被广泛应用于调试的重要手段,有专家甚至认为printf是嵌入式发展过程中的长期基石,通过重要数据、程序运行状态等调试信息的输出,能有效加强初学者在嵌入式端的学习和理解、提高开发人员排除BUG的效率[9-10]。本文利用STM32L431RC芯片结合mbedOS程序框架,通过中断与线程的同步实验,在简要分析事件机制响应调度的整个流程及其理论执行时间的基础上,结合printf方法输出测试了实际响应时间并进行分段解析,最后对影响事件机制响应时间的主要因素进行探究。通过printf对mbedOS事件机制实时性的深入剖析,能有效了解事件机制的实际响应时间,提供对任务的更精确控制[11],减少因超出任务截止时间导致某些难以预测结果的可能性,同时也为其他RTOS的实时性分析及有关应用提供技术基础。

1 事件的含义与作用

同步是一种协调任务执行顺序避免发生时间相关差错的机制,RTOS通常利用同步机制实现任务的有序调度来进行实时控制[12-13]。而事件是RTOS中同步机制中的一种重要手段,相比控制条件单一的信号量、互斥量等机制而言,事件机制可以通过事件的“与”和“或”多种逻辑组合来拥有更加丰富的控制功能。但事件本身只是用作一种控制信号,故无法类似消息队列机制一样传输数据,因此事件一般用于不需要传送数据的场合。事件机制在早期出现的RTOS中就一直存在,无论是在1989年出现的MQX,还是之后陆续出现的诸如μC/OS、FreeRTOS及2014年Arm公司出品的mbedOS等RTOS中,事件机制始终被保留并不断完善。不同RTOS对事件的称呼不太一样,例如MQX中事件被称为事件组,μC/OS和FreeRTOS中称为事件标志组,mbdeOS中称为事件字或事件标志字等,但其原理和作用基本一致。

事件通常用一个32位的字来表示,字中的每一位都可表示一个事件,相互独立且互不干扰。可以认为事件字是一个公共资源,与全局变量形式上很类似,但更方便RTOS对任务的控制和管理。使用事件机制进行同步时,一定会有一个任务在等待事件,然后由其他任务或中断来设置事件位通知事件完成[14-17]。

对事件进行操作的常用函数一般有三种:等待函数、设置函数和清除函数。等待函数一般由被控制的任务使用,任务通过该函数进入等待事件状态并进入阻塞;设置函数通常由其他任务或中断使用,通过设置事件字的指定事件位(即事件字对应位进行置1)让等待事件的线程退出阻塞状态接受调度;清除函数则是清除指定的事件位(事件位清0)。在mbedOS中,常用wait_any()等待事件、set()设置事件、clear()清除事件,与其他RTOS中的事件函数相比,mbedOS中事件函数在功能上并未发生太大的变化,但基于ARM的OS体系,使得事件函数每次被调用时都会触发相应的系统服务调用SVC(Supervisor Call)中断或可挂起系统调用PendSV(Pendable Supervisor)中断,从而进行对应的调度过程。这种保护机制能有效提高系统的稳定性,但在某种程度上也加大了进行时序分析的难度,如何在这些干扰因素下进行有效而精确的时序分析是很重要的。

2 基于printf函数的时序分析方法

本文对mbedOS中事件机制响应调度的实时性分析主要通过printf函数来实现。在嵌入式领域中,通常利用printf进行打桩调试来定位BUG,或是输出有效的提示信息来辅助理解程序执行的动态过程。但是printf的问题在于输出速度较慢,直接用于时序分析时会对系统的实时性能有较大的影响。经测试,在48 MHz的内核时钟频率下,输出一个字符平均大约需要80 μs,可见输出字符的操作本身会占用较多时间。这点在周期较长、粗糙的时序分析时或许不明显,但对于实时性较高的RTOS,几秒甚至是毫秒、微秒级的误差就有可能导致较为严重的后果。故利用printf来进行精确的时序分析时,有必要通过主观的编程手段来排除这部分误差。

测试时通常根据一段代码开始和结束这两端的时间差来有效算出该段代码的实际执行时间,为了避免在测试代码两端直接加printf带来的较大时间误差,可以利用全局变量保存代码两端时间的方式,将printf输出放到测试代码外执行,而全局变量赋值花费的时间是在纳秒级,基本可以忽略其影响,这样能保证得到较为精确的结果。

测试时间一般会基于一个统一的定时器,在mbedOS中,存在一个利用时间嘀嗒中断来进行整体任务调度的定时器SysTick。该定时器是一个24位的递减计数器,其中断周期默认为1 ms,对应SysTick中的计数器需要计数48 000次。由于每次嘀嗒中断会让内核中嘀嗒计数结构体变量osRtxInfo.kernel.tick加1来保存嘀嗒次数,故根据嘀嗒计数和SysTick自身计数器的计数值便能够有效计算出具体当前时间[18]。基于SysTick定时器的时间具体计算方式为[osRtxInfo.kernel.tick×1 000+(48 000-SysTick->VAL)/48 000×1 000],其中SysTick->VAL为计数器对应值,时间单位为微秒。但要注意的是,在执行某些诸如SVC和PendSV等过程时,Systick中断会被推迟执行,此时嘀嗒计数值osRtxInfo.kernel.tick不会发生变化,故只能根据SysTick计数器的值来算出1 ms之内的有效执行时间。

3 mbedOS的事件机制实时性分析与实践

本文实验选用的开发板型号为STM32L431RC,对应的集成开发环境为意法半导体(ST)公司推出的STM32CubeIDE 1.3.0开发工具,移植的工程框架基于5.15.1版本的mbedOS。该开发板是Cortex-M4内核,其RAM大小为64 KB,一般用来存放堆栈、静态变量与全局变量等;另外,主要用于存放程序代码、中断向量表、常数等内容的Flash区大小为256 KB[19]。

3.1 测试工程功能设计

测试工程的功能主要依靠串口中断服务程序以及mbedOS启动后依次创建的优先级为1的空闲线程、优先级为24的蓝灯线程来实现。蓝灯线程调用wait_any()等待蓝灯事件即等待蓝灯事件位被设置,从而反转蓝灯亮暗,同时用于printf输出测试过程中保存在全局变量中的时间信息;低优先级的空闲线程保证在蓝灯线程因等待蓝灯事件进入阻塞后能占据CPU,确保MCU处于运行状态;串口中断服务程序始终开放,等待接收字符并进行判断,如果接收到的字符为“b”,就调用set()设置蓝灯事件位。测试工程的执行流程如图1所示。

图1 测试工程执行流程

3.2 事件响应调度的实时性分析

事件响应调度的方式一般有两种:一种是线程等待中断设置事件位来响应PendSV调度;另一种是线程等待其他线程设置事件位来响应SVC调度。本文主要通过中断与线程的同步实验来具体剖析线程响应PendSV调度的实时性。

蓝灯线程优先级最高,首先被SysTick中断调度开始运行,调用wait_any()等待事件位被设置。在wait_any()过程中,蓝灯线程被设置为等待事件位状态且进入等待队列(osRtxInfo.thread.wait_list)和事件阻塞队列(osRtxEventFlags_t.thread_list),并从就绪队列(osRtxInfo.thread.ready)中取出空闲线程开始运行。此时蓝灯线程阻塞,等待系统发出信号(中断设置事件位)来响应调度。

3.2.1理论响应时间分析

响应PendSV调度的整段过程是从串口中断调用set()设置事件位开始,到等待事件位的蓝灯线程被正式切换运行结束。根据编译后的.lst文件,找到这段过程对应的代码,其机器指令为1 680条。测试芯片的系统时钟频率为48 MHz,即一条机器指令执行时间为1/48 μs,故该过程对应的理论执行时间即响应时间约为35 μs。

3.2.2实际响应时间分析

实际响应时间会根据硬件因素有所不同,但不会偏离理论响应时间太多,可以基本确定实际响应时间是在1 ms之内,故可以利用系统内的SysTick时钟有效测出响应PendSV调度的实际响应时间。

1) 总响应时间分析。分别在测试工程中串口中断的set()前一句和蓝灯线程的wait_any()后一句插入时间赋值语句,即将[osRtxInfo.kernel.tick×1 000+(48 000-SysTick->VAL)/48 000×1 000]赋值给对应的全局变量,并在蓝灯线程中利用printf输出两次时间值及对应差值。进行百次输出实验,去除对应osRtxInfo.kernel.tick值不一样的测试结果,这是因为在进行printf输出时,蓝灯线程已经被调度完成,osRtxInfo.kernel.tick可以正常增长,虽然printf输出这段过程极为短暂,但依旧会产生极少数的osRtxInfo.kernel.tick值不同的结果,为使结果更加精确,排除这部分异常数据,得到时间差值的均值为41 μs,即该过程总执行时间约为41 μs。

2) 分段过程执行时间分析。

(1) 事件响应调度的过程说明。事件响应调度的整个过程为:调用set()设置事件位,蓝灯线程接收到事件位被设置的信号,解除阻塞开始运行。调用set()是蓝灯线程响应调度的开始,串口中断调用set()设置事件位并挂起了PendSV中断。set()调用结束后,继续执行中断的后续代码,串口中断结束后立即触发PendSV中断,跳转执行PendSV中断实际服务程序osRtxPendSV_Handler()开始调度处理。主要处理过程是调用事件处理函数osRtxEventFlagsPostProcess()和线程调度函数osRtxThreadDispatch()来改变线程状态和队列状况,osRtxEventFlagsPostProcess()将已设置事件位的蓝灯线程移出阻塞队列和等待队列并设置为就绪态放入就绪队列,此时就绪队列蓝灯线程优先级最高,于是又通过osRtxThreadDispatch()从就绪队列取出蓝灯线程设置为激活态。处理结束后,等待到事件位被设置的蓝灯线程已经解除阻塞准备运行,从osRtxPendSV_Handler()返回PendSV中断,执行后续的上下文处理程序SVC_Context进行上下文保护和线程切换,PendSV中断结束后,蓝灯线程被正式切换运行。至此,蓝灯线程响应调度结束。

(2) 时序分析。对于响应调度的蓝灯线程来说,最关键的三个要素为:系统何时发出了使其调度的信号(设置事件位);系统是怎样调度的,需要花费多长时间来进行调度处理;处理结束到正式被切换运行又需要耗费多长时间。除此之外,调度过程中通常还有一些参数验证、类型转换和条件判断等操作,但这些一般不作考虑,不是关心的重点。

因此,设置事件位的set(),调度处理时调用的osRtxThreadListRemove()、osRtxThreadWaitExit()和osRtxThreadDispatch(),切换上下文的SVC_Context,这几个过程无疑是测试的重点。对此,根据调度过程中系统的主要操作,将整段过程的运行时间分成7个时间段。过程分段结果如图2所示。

图2 事件响应调度过程分段

测试时依旧通过全局变量保存每段过程对应位置的时间点,最后通过蓝灯线程输出时间值及时间差大小。例如求取T0-T1段即set()执行的时间,可以定义全局变量T0、T1,在调用set()这句代码的前后分别插入T0、T1的时间赋值语句,在蓝灯线程中利用printf输出T0、T1及两者的差值。另外为了减小误差,每次测试时只测时间段对应的两个时间点,即只使用两个全局变量保存Ti-T(i+1)两点所在时间值,不同时进行多个时间段的测试处理。

对事件响应调度这段过程中不同时间段分别进行百次printf输出实验,排除异常数据后计算每段时间对应的平均值,结果如表1所示。7个时间段加起来的时间为11+8+1+7+3+8+3=41 μs,与测出的总执行时间一致,基本可以认为测出的时间数据是可信和正确的。T0-T1是触发调度的开始,设置事件位,将PendSV挂起推迟执行;T2-T3、T3-T4、T5-T6三段为调度时主要的处理过程,主要为队列的变化和线程状态的改变;T6-T7即为调度处理结束,返回SVC_Context切换蓝灯线程正式运行的时间。

表1 事件响应调度分段过程执行时间

以T0为时间基点,蓝灯线程响应调度的整个时序过程如图3所示,粗线条长度代表对应过程执行时间。可以看到,在PendSV调度未开始时,调用set()已耗费了较多时间,约有11 μs,这是由于se()的执行经历了好几层的嵌套调用,其调用顺序为set()→osEventFlagsSet()→isrRtxEventFlagsSet(),在isrRtxEventFlagsSet()中才会调用子函数EventFlagsSet()设置事件位和osRtxPostProcess()挂起PendSV中断。PendSV开始后,进行一些调度相关的处理,主要调整了队列和线程状态,并在最后进行上下文切换工作,这段过程共耗费了30 μs。其中,在T3-T4和T5-T6阶段队列的进出操作花费了较多时间,这里蓝灯线程在T3-T4阶段先进入了就绪队列,由于优先级最高在T5-T6阶段又被调出了就绪队列并激活运行,是否可以考虑直接通过优先级的比较判断其优先级最大时直接激活运行,避免无意义的进出队列操作,从而减少整个调度过程的执行时间,这一点值得思考并有待进一步研究。

图3 事件响应PendSV调度时序图

3.3 影响事件机制响应时间的要素分析

事件机制的响应时间主要受两方面影响,客观角度上可以通过编译优化来大幅度提高代码执行效率以减少响应时间来增强实时性,主观角度上根据程序功能的不同需求会在某些方面影响响应速度。

3.3.1编译优化的影响

编译优化可以通过不同优化等级对编译器的影响,在编译时对代码进行不同程度的优化。使用编译优化选项会影响编译耗费的时间、程序占用的Flash和RAM大小、代码的运行效率等,甚至有可能会导致代码运行异常,故通常需要根据实际情况来设置。本文只分析不同优化等级对代码执行效率的影响,分别测试不同优化等级下事件机制的响应时间,即调用set()设置事件到等待事件的线程结束等待被调度运行这段过程的总时间。在STM32CubeIDE中,提供了7种优化等级(optimization level),从低到高分别为:-O0、-Og、-O1、-O2、-O3、-Os、-Ofast。-O0为默认不优化,不优化情况下已经求出响应时间约为41 μs,按照同等方式分别求出其他优化等级下对应响应时间,同样进行百次输出实验并取平均值,结果如表2所示。

表2 不同优化等级下的事件机制响应时间

不同等级的优化选项效果不同:-O1主要对代码的分支、常量、表达式等进行优化;-Og可以看作是O0.5,是在O1的基础上,去掉了影响调试的优化;-O2会尝试更多的寄存器级的优化以及指令级的优化;-Os启用了所有-O2优化选项但排除了增加目标文件大小的选项,主要用于缩减代码尺寸;-O3在-O2的基础上进行更多的优化,例如使用伪寄存器网络、普通函数的内联等;-Ofast是在-O3的基础上,添加了一些非常规优化,无视严格的标准合规性,一般不推荐使用。

可以看到随着优化等级越高,代码的执行效率越高,即事件机制的响应时间越小。通过编译优化,响应时间最高减少到15 μs,相比不优化时的41 μs,响应速度提高了一倍多,基于事件机制的mbedOS实时性得到了有效提高。但要注意的是,代码的优化通常是以程序的可调试性为代价的,优化度越高,可调试性越低,故通常要根据需求来进行合理设置。

3.3.2程序功能的影响

前面已经说明,中断中调用set()结束后会执行中断后续未执行的语句,PendSV被挂起直到中断结束才会触发。从set()到PendSV开始这段过程,执行时间主要受set()之后的代码数量影响,本文测试工程中set()为中断处理函数中的最后一个操作,基本可以看作set()结束后中断就已经结束,PendSV立即被触发,但实际应用时中断里可能会根据需要进行一些其他操作比如添加printf输出提示,因此这部分执行时间受主观需求影响是不确定的。

另一方面,由图3可以看到,在执行PendSV调度处理时,执行时间主要耗费在进队列或出队列等操作上,T2-T3、T3-T4和T5-T6的时间和为16 μs,超过了PendSV总执行时间30 μs的一半。本文测试工程只进行了蓝灯线程与空闲线程间的调度示例,不同队列中存在的线程数不会超过两个,这是为了排除其他线程的干扰,这样在最简单的条件下才能测出最准确的响应时间。但实际应用时往往会有多个任务线程,队列中线程的位置会根据不同场合发生变化,例如同时有多个线程等待事件,事件阻塞队列会有多个线程,而出队列的只会是已经设置事件位的对应线程,不同位置进出队列所耗费的时间自然会有所变化,从而影响整体的响应时间。特别要注意的是T5-T6阶段就绪队列的改变,线程调度函数osRtxThreadDispatch()是根据就绪队列中线程优先级的比较来激活确定下一运行线程的。只有就绪队列最高优先级线程的优先级高于正在运行的线程优先级,才会进行线程的切换,将优先级更高的线程调度运行。因此,基于事件机制等待响应调度的线程,优先级通常要设置为比其他线程高一点,这样才能在等待到事件位被设置后,立即接受调度运行;反之优先级比其他线程低的话,在接收到事件位已设置的信号后,可能被其他等待运行的高优先级线程抢占,响应时间无法估测,不再有实时的特点。

4 结 语

本文对mbedOS中事件机制响应调度的实时性进行了剖析。首先提出基于printf函数的时序测试方法,然后分析得到事件响应调度的理论时间为35 μs,满足提出的时序测试条件,在简要说明了响应调度过程的基础上,对实际响应时间进行了测试,研究表明线程等待中断设置事件位来响应PendSV调度所需的时间约为41 μs,其中队列操作花费时间占据比例最大,最后进一步说明了影响事件机制响应时间的主要因素有编译优化和程序功能两方面,其中编译优化最高可将实际响应时间减少到15 μs。通过printf对事件响应调度实时性的深入剖析,不仅为mbedOS中事件机制的精确使用提供参考,也为剖析其他RTOS的实时性提供一定的借鉴。

猜你喜欢

蓝灯实时性线程
基于规则实时性的端云动态分配方法研究
基于虚拟局域网的智能变电站通信网络实时性仿真
航空电子AFDX与AVB传输实时性抗干扰对比
浅谈linux多线程协作
一种车载Profibus总线系统的实时性分析
基于上下文定界的Fork/Join并行性的并发程序可达性分析*
Linux线程实现技术研究
么移动中间件线程池并发机制优化改进