实时操作系统mbedOS 互斥量调度机制剖析

2022-04-13 07:46王庭琛王宜怀陈瑞雪
现代电子技术 2022年8期
关键词:蓝灯公共资源红灯

王庭琛,王宜怀,陈瑞雪

(苏州大学 计算机与科学技术学院,江苏 苏州 215006)

0 引言

大部分嵌入式产品的开发中,尤其是工业控制和军工产业,不仅对实时性的需求较高,即系统需要在短时间内对线程进行响应并处理,而且要求尽量不浪费CPU的运算资源。因此,通常会选择在设备上使用实时操作系统(Real Time Operating System,RTOS)来调度各个线程,使系统可以多线程同时执行且保持一定的实时性。在这种高实时性的环境中,线程的错误调度可能会破坏整个系统的正常运行并对系统的性能产生较大影响,因此为了保证系统正常运行的同时避免因线程延迟而浪费时间,需要在线程调度策略中加入一些机制来防止上述情况发生。

操作系统运行时,可能会出现多个线程同时申请使用同一公共资源的情况,当某一线程正在使用上述公共资源时,操作系统直接切换其他的线程运行并夺走此公共资源,则运行结果将取决于线程运行的时序情况,若发生竞争条件(Race Condition)现象,运行结果无法预估。为了使线程正常运行,应在两线程异步运行过程中添加制约关系,如一个线程正在使用某一公共资源时,另一个需要使用此公共资源的线程应等待上一个线程使用完毕后再运行,也就是操作系统需要保证对于某一公共资源在同一时刻至多有一个线程使用,此时就需要引入互斥量对线程的调度进行约束,确保线程运行正确并避免公共资源被破坏。

目前,嵌入式设备运行实时操作系统中使用互斥量的程序进行线程调度,然而大多数仅对其进行了简单的原理介绍,对互斥量的实现过程以及操作系统对使用互斥量的线程调度方法的研究较少。因此,本文针对ARM 公司推出的面向物联网(IoT)的mbedOS 实时操作系统,使用ST 公司推出的基于ARM Cortex⁃M4 内核的STM32L431 微控制器,配合苏州大学基于mbedOS 研发的SD⁃mbedOS 工程框架,对mbedOS 的互斥量调度机制进行深度剖析。首先对互斥量进行介绍,其次分析mbedOS 中有关互斥量的结构体、解锁和上锁互斥量的函数,最后设计一个测试工程实际分析mbedOS 对使用互斥量线程的调度情况。

本文研究有助于了解mbedOS 中对于互斥量的实现,厘清mbedOS 中对使用了互斥量的线程调度处理过程,也可为分析比较其他实时操作系统中互斥量的机制提供参考。

1 互斥量介绍

在软件领域,通常使用互斥量来代表一项公共资源是否正在被使用,例如当线程A 需要使用公共资源z 时,线程A 会将公共资源z 的互斥量上锁,此时如果线程B申请公共资源z,因公共资源z 的互斥量已经被锁,所以线程B 无法获得公共资源z。申请失败后,线程B 会进入阻塞队列等待公共资源z 的互斥量被释放后再对其进行重新申请。

在无操作系统的环境下,互斥量常使用全局变量来表示,并使用条件判断语句来判断互斥量是否被使用。这虽解决了线程互相抢占公共资源时,破坏公共资源稳定的问题,但在某些特殊的情况下可能会产生一些其他问题,以下将列举几种使用全局变量作为互斥量可能会引起的问题:

1)同一线程重复上锁导致死锁

在同一线程运行中,已通过一个函数获取了公共资源的互斥量,在之后的使用过程中又直接或间接地回调这个函数,此线程将再次申请此互斥量。由于此互斥量已在此线程使用中,第二次申请将无法成功,造成线程挂起形成死锁。

2)优先级反转

如图1 所示,假设系统中有三个优先级依次递减的线程T,T,T。其中T与T线程需要使用同一种公共资源,在T先运行并获取到互斥量的情况下,T进入时会抢占T,在T运行后会因申请不到互斥量被阻塞,系统将继续运行T。此时T进入将抢占T运行,就会发生T优先级比T低却更早获得运行的情况。此行为将使线程的运行顺序无法被预估,造成系统不稳定。

图1 优先级反转

3)线程异常导致互斥量无法释放

线程在申请互斥量之后若出现异常无法继续执行,互斥量将无法被释放,导致后续需要使用此互斥量的线程都无法正常运行。

为防止上述错误的发生,在操作系统实现的互斥量中通常增加更多的属性,便于系统在调度线程时判断互斥量和线程的不同状态,确保对线程的正确调度。

2 mbedOS 的互斥量实现

2.1 互斥量的数据结构

在mbedOS 中,声明一个互斥量后,系统将先生成一个属性结构体(osMutexAttr_t)来保存此互斥量的信息供开发人员阅读使用,结构体具体成员和作用如表1所示。

表1 属性结构体osMutexAttr_t 各成员作用

操作系统调度线程时,实际使用的是根据属性结构体再创建出的控制块结构体(osRtxMutex_s),此结构体内除互斥量的基本信息外还保存系统调度时所使用到的互斥量参数,结构体具体成员和作用如表2 所示。

表2 控制块结构体osRtxMutex_s 各成员作用

mbedOS 中可供选择的互斥量属性有:

1)嵌套互斥量(osMutexRecursive),用于避免单一线程重复上锁导致自身死锁,当互斥量拥有此属性时,互斥量可以重复上锁,并且解锁时需要上锁的线程解锁同样次数;

2)内部优先级互斥量(osMutexPrioInherit),用于避免优先级反转,当互斥量拥有此属性时,线程将使用优先级继承(Priority Inheritance)的方法来提升正在使用互斥量的程序的优先级;

3)健壮互斥量(osMutexRobust),若互斥锁的持有者出现异常,则解除互斥锁锁定,并对下一个使用此互斥锁的线程抛出一个错误。

mbedOS 中的互斥量包含一个阻塞队列和一些指向与该互斥量有联系的线程的指针,便于使用优先级继承方法避免优先级反转时快速改变有关的线程优先级。在加解锁时,列表和指针也需要相应的改变。

2.2 互斥量操作函数

系统的结构设计中,将对互斥量的操作通过多层函数进行封装。锁定互斥量和解锁互斥量的函数实际调用情况如图2 所示。

图2 互斥量加解锁函数调用情况

在mbedOS 中互斥量加锁实际使用的是svcRtxMutexAcquire 函数,其运行流程如图3 所示。

图3 svcRtxMutexAcquire 函数运行流程

函数首先检测线程、互斥量等参数的合法性,再检测互斥量是否锁定,若未锁定则进行加锁,并将互斥量加入线程的互斥量列表中后,返回锁定并成功退出。具体过程为:

若互斥量已锁定,则检查互斥量是否具有嵌套属性且为同线程上锁,若是则将互斥量计数增1 后退出,具体过程为:否则判断互斥量是否设置等待时间,若未设置则申请失败并退出,有设置等待时间则检查互斥量是否含有内部优先级属性,有则提升为此互斥量加锁的线程优先级。之后再将被阻塞的线程插入互斥量阻塞队列中,同时根据设置的等待时间大小将线程放入等待队列或延时队列中,获取当前优先级最高的线程运行。

在 mbedOS 中实际为互斥量解锁的是svcRtxMutexRelease 函数,其运行流程如图4 所示。

图4 svcRtxMutexRelease 函数运行流程

具体步骤如下:

首先检测线程、互斥量等参数的合法性,再检测互斥量是否已加锁,若互斥量未加锁则返回资源错误,若互斥量已被锁定,则将互斥锁计数减1。

然后判断互斥量计数是否为0,若为0 则互斥量已经被解锁,将互斥量从当前互斥量的私有线程队列中移出。

之后判断互斥量是否含有内部优先级属性,有则将当前线程优先级调至被当前线程占用的其他互斥量阻塞的最高优先级线程相同。

最后若互斥量阻塞队列中有线程,则将最高优先级线程移出,同时将此线程从等待队列中移出,放入就绪队列。

函数在执行过程中,需要使用公共资源时加入加锁函数,使用完毕后,加入解锁函数便可实现对公共资源的互斥加锁。

3 线程调度实例分析

本文将分析STM32L431 微控制器使用SD⁃mbedOS工程框架运行多线程优先级相同情况的例程。SD⁃mbedOS 框架提供printf 函数,该函数功能为通过串口向外打印文字信息,在程序中插入printf 语句即可在程序运行过程中从串口收取程序运行情况。

将例程使用的微控制器3 个引脚设置为GPIO 口并连接红、绿、蓝三色灯的引脚,同时创建3 个优先级相同的小灯线程来控制每个灯的亮暗。将3 个线程分别设置为:红灯线程点亮1 s,熄灭0.5 s,不设延时;蓝灯线程点亮1 s,熄灭0.5 s,延时0.5 s 启动;绿灯线程点亮1 s,熄灭0.5 s,延时1 s 启动。

此外,为方便观察程序走向,在互斥量的锁定、解锁函数中加入一些printf 语句,打印出互斥量的状态与函数中的操作。例程运行后可通过三色灯的颜色显示情况以及串口输出的关于三色灯操作的字符串来观察实验现象,再根据串口输出的互斥量操作信息分析并理解线程的切换以及互斥量的占用顺序,下文为程序的详细剖析。

3.1 三色灯不互斥运行的情况

在没有互斥锁的情况下,线程交替运行,所有的线程都能自由地访问到三色灯,因此其中一个线程改变颜色后,其他的线程依然能够访问到三色灯,并改变三色灯的颜色,则可得到有两种颜色的小灯同时亮起,三色灯观察到混合色。观察到的三色灯颜色如图5 所示。

图5 无互斥量的程序三色灯亮灯情况

3.2 三色灯互斥运行的情况

为三色灯增加一个互斥量,每个线程在亮灯的期间视为占用互斥量,因此同时只能有一个线程使用三色灯亮灯,在线程初始化前先声明互斥量:

之后在各个线程需要申请互斥量的时候使用以下语句申请互斥量:

在互斥量使用完毕时,通过以下语句解锁:

增加互斥量对三色灯访问进行限制后,线程的执行情况如图6 所示。

详细情况和串口输出数据如下:

1)系统初始化

在系统开机时,对红、蓝、绿灯线程依次进行初始化,为确保线程能正常被创建,不被其他线程打断,在创建用户线程的过程中使用互斥量,线程初始化后将线程移入就绪队列按照优先级依次执行。初始化所有程序时,已初始化的线程可以运行,所有线程初始化后将阻塞主线程。对应图6 中步骤1~步骤4,创建红灯线程时串口信息如下:

2)红灯线程申请互斥量

系统从就绪队列中红灯线程运行,红灯线程开始申请互斥量。由于此时互斥量为0,申请成功,线程被放入互斥量的私有线程队列中,互斥量被放入线程的私有互斥量列表中。继续运行点亮红灯,之后执行延时函数,线程被放入延时队列中。对应图6 中步骤5~步骤9,串口接收到以下信息:

3)蓝灯线程申请互斥量

蓝灯线程在经过初始化后被放入延时队列等待0.5 s,之后从延时队列中被取出放入就绪队列。系统从就绪队列中取得蓝灯线程运行,蓝灯线程开始申请互斥量。由于此时互斥量被占用,互斥量申请失败返回错误,蓝灯线程被放入互斥量阻塞队列和等待队列中,对应图6 中步骤10~步骤14,串口接收到以下信息:

4)红灯线程释放互斥量

红灯线程在延时结束后被移入就绪队列,系统从就绪队列中取得红灯线程运行。红灯线程先熄灭红灯,之后解锁互斥量(互斥量变为0),线程被从互斥量私有线程列表移出,并且取出互斥量阻塞队列中优先级最高的第一个线程,即蓝灯线程,将互斥量给这个线程,将线程移入互斥量私有线程,再将此线程从等待队列中移出放入就绪队列。对应图6 中步骤15~步骤23,串口接收到以下信息:

5)蓝灯线程申请互斥量

系统从就绪队列中取得蓝灯线程运行,蓝灯得到互斥量(互斥量为1),点亮蓝灯,执行延时函数被放入延时队列中。对应图6 中步骤24,串口接收到以下信息:

图6 使用互斥量的程序线程调度时序

6)绿灯线程申请互斥量

绿灯线程延时1 s 结束后开始运行,发生如蓝灯线程申请互斥量的绿灯申请失败过程。对应图6 中步骤25~步骤29,串口接收到以下信息:

7)红灯线程申请互斥量

发生如蓝灯线程申请互斥量的红灯申请失败过程。对应图6 中步骤30~步骤34,串口接收到以下信息:

8)蓝灯线程释放互斥量

发生如红灯线程释放互斥量的释放过程。对应图6 中步骤35~步骤43,串口接收到以下信息:

9)绿灯线程申请互斥量

发生情况5)蓝灯线程申请互斥量的申请成功过程,对应图6 中步骤44,串口接收到以下信息:

10)蓝灯申请互斥量

同蓝灯线程申请互斥量申请失败被阻塞,对应图6中步骤45~步骤46。

11)绿灯线程释放互斥量

发生如红灯线程释放互斥量的释放过程,对应图6中步骤47~步骤55,串口接收到以下信息:

12)红灯线程申请互斥量

如蓝灯线程申请互斥量的申请成功过程,对应图6中步骤56。

之后重复以上除初始化环节,三色灯观察到的亮灯情况为红、蓝、绿灯依次点亮1 s,无颜色混合现象,如图7 所示。

图7 有互斥量的程序三色灯亮灯情况

4 结 语

互斥量在多线程并发运行时,能够保护公共资源,对限制线程的自由调度有着重要的作用。在实时操作系统中实现的互斥量中通常带有更多属性来避免一些错误,因此程序调度也变得更加复杂。本文主要针对mbedOS 中的互斥量,对其数据结构、操作函数功能进行分析,最后通过一个三色灯的运行例程,使用SD⁃mbedOS 在STM32L431 上对互斥量在系统调度时的作用进行详细剖析。通过剖析,读者可快速理解mbedOS对于使用互斥量线程的调度机制,并类比实现在不同的微处理器上使用互斥量进行多线程编程,同时也可为比较分析其他实时操作系统的互斥量提供基础。

猜你喜欢
蓝灯公共资源红灯
公共资源交易平台构建及体制机制创新
为什么红灯停,绿灯行
红灯笼
云南省积极推进公共资源交易标准化工作
红灯停,绿灯行
红灯变堵“墙”
云南省搭建公共资源交易信息平台
天津的“5+1”公共资源交易管理模式