概述
资源管理是指嵌入式系统中,针对TASK 和中断处理程冠希访问CPU以外的硬件资源(如某段存储空间,某个外设)的控制。
嵌入式系统中,一段时间内访问硬件资源的TASK和中断处理程序数量总是有限的。因此OS需要提供一套机制来保证访问硬件资源的TASK数量收到限制。
以一个简单的UART输出为例,假定,
· TASK 1需要通过UART输出字符串“[TASK 1]:ABC.\n”
· TASK 2需要通过UART输出字符串“[TASK 2]:abc.\n”
· TASK 1优先级低于TASK 2,但初始时TASK 1占据了CPU资源
· 期望UART输出“[TASK 1]: ABC.\n [TASK 2]: abc.\n”
则可能发生如下图所示情况,导致输出类似于“[TASK[TASK2]: abc.\n 1]: ABC”的乱码。
如下图所示,
其中,
· TASK 1在时刻t1开始输出字符串“[TASK1]: ABC.\n”
· 时间段t1~t2中,TASK 1向UART完成了部分字符串“[TASK”的输出
· 时刻t2发生了时间片中断,OS中断了TASK 1的执行,转而执行TASK 2,
· 时间段t2~t3中,TASK 2占据了CPU,向串口输出了字符串“[TASK 2]: abc.\n”
· 时刻t3,TASK 2完成了输出,主动放弃了CPU资源,OS转而执行TASK 1
· 时间段t3~t4,TASK 1继续向UART输出字符“ 1]: ABC”
其中将TASK 1换成一中断处理程序,也会出现类似情况。
基于临界区的资源管理
临界区机制用于一段时间内访问硬件资源的TASK数量为1的情况。
其基本原理是TASK在访问硬件资源前关闭CPU中断响应,以避免中断触发的任务切换,访问完成后在恢复CPU中断响应。
关闭CPU中断响应到恢复CPU中断响应之间的处理过程即为临界区(Critical Section)。
临界区中关闭了CPU中断响应,会导致CPU对于中断响应的延迟,因此在实际的使用中,临界区的执行时间要尽可能的短。
实际使用中,不推荐在临界区内调用任何函数和定义为特殊操作的宏,除非能够确保这些函数和宏的执行时间十分短,并且在后期的代码维护中不会修改。
同样是之前给出两个TASK向UART输出字符串的例子,只需TASK 1和TASK2使用如下函数输出字符串,则不会输出类似乱码,
其中,
· taskENTER_CRITICAL()和taskEXIT_CRITICAL()为freeRTOS提供的API宏,用于关闭和恢复CPU中断响应,需要在freeRTOS移植时根据硬件平台定义
· taskENTER_CRITICAL()和taskEXIT_CRITICAL()之间的代码区域即为临界区,临界区内的代码被执行时,CPU不会响应中断
· freeRTOS还提供xTaskSuspenddAll()和xTaskResumeAll()两个API函数,用于暂停和恢复OS的TASK调度,也可以构造出类似的临界区,但是只能规避多个TASK对于硬件资源的访问控制,不可规避中断处理程序对于硬件资源的访问控制,因此并不推荐使用
此时,TASK 1和TASK2在CPU上的执行时序如下图所示,
基于互斥信号量的资源管理
互斥信号量机制用于一段时间内访问硬件资源的TASK数量为1的情况。
互斥信号量可以看做是 一个特殊的“全局变量”,其取值为0或1,0表示对应资源没有被TASK访问,1表示正在被访问。
TASK访问该硬件资源时,都需要先获取信号量:判断该互斥信号量是否为0,若为0则将其置为1;若为1则进入锁定态等待该硬件资源被释放。
获取信号量的TASK才能访问该以硬件资源。
当TASK完成硬件资源的访问后,需要释放信号量:将互斥信号量置为0,并唤醒等待该硬件资源的最高优先级的TASK。
整个过程如下图所示,
同样是之前给出两个TASK向UART输出字符串的例子,只需TASK 1和TASK2使用如下函数输出字符串,则不会输出类似乱码,
其中,
· xSemaphoreTake()和xSemaphoreGive()是freeRTOS提供的API函数,用于获取和释放信号量。
· 在使用信号量之前需要使用freeRTOS提供的API函数xSemaphoreCreate()创建信号量,一般是在OS启动之前的初始化过程或任务的初始化过程中创建。
此时,TASK 1和TASK2在CPU上的执行时序如下图所示,
然而,这并非互斥信号量的全部。
优先级翻转
为了避免优先级翻转(priorityinversion)问题,OS可能会自动提升已获取互斥信号量的TASK的优先级,以保证其不小于所有等待该互斥信号量的TASK,直到其释放互斥信号量后恢复,即优先级继承(priority inheritance)。
如下图所示,其中假定存在一优先级介于TASK1和TASK 2之间的TASK 3,则
· 若没有优先级继承,则TASK 3会在t4时刻获取CPU资源,导致优先级更高的TASK 2由于无法获取互斥信号量而不能运行,即优先级翻转。
· 若有优先级继承,则t2时刻,OS发现等待互斥信号量的TASK 2优先级高于拥有互斥信号量的TASK 1,则立即暂时提高TASK 1的优先级,这样在t4时刻,TASK 3无法再获取CPU资源;在t5时刻,TASK 1释放互斥信号量后,OS恢复其优先级,TASK 2顺利获得CPU资源。
死锁
简而言之,死锁就是两个TASK都拥有了对方期望获取的互斥信号量,导致双方都不能运行的情况,如下图所示,
死锁是OS无法避免的,只能依靠用户从系统设计的层面上避免,如禁止任务在已经获取了互斥信号量的情况下获取其他互斥信号量,或合理安排任务执行的时序,当然这是以牺牲设计的灵活度为代价的。
除此之外,大部分的RTOS包括freeRTOS都推荐获取信号量时,将等待时间设置为有限值,并在获取不成功时进行容错处理。
基于信号量的资源管理
信号量机制用于一段时间内访问硬件资源的TASK数量有限(允许大于1)的情况。
同样考虑之前提到的例子,但修改一些设定
· UART提供双通道(记为UARTCHAN 1和UART CHAN 2),可同时允许两个TASK输出字符串
· 除之前的TASK 1和TASK 2外,还存在优先级介于它们之间的TASK 3,希望输出字符串“[TASK 3] : Abc\n”
则可以基于信号量来对UART进行管理,如下图所示。
信号量的使用和效果与互斥信号量多有类似,此处就不再叙述。
基于TASK的资源管理
为避免临界区和互斥信号量的各种缺陷,freeRTOS推荐了一种基于TASK的资源管理方案:设置与硬件资源相对应的专门任务(记为Gate Keeper TASK),所有需要访问硬件资源的任务都必须通过Gate Keeper TASK进行,如下图所示,如此不会出现死锁和中断延迟的问题。