支持原创,转载请附上原文链接
0、平台
FreeRTOS源码版本:V9.0.0
1、引言
RTOS历史上,有一个非常有名bug,“What Happened on Mars?”。 这里说的比较详细,参见:https://www.dazhuanlan.com/2020/02/09/5e3f9f811e02a/
开始下面的正文之前,先讲述2个RTOS中常用的概念:互斥量与信号量
1.1、互斥量
互斥量一般用于共享资源加锁,防止多个task同时访问一个共享资源,比如:多个task都会修改的全局变量,可以通过互斥量加锁。互斥量一般具有如下特性:
1、优先级继承
2、互斥量不能在中断中使用
3、互斥量获取和释放需要再同一个task中
1.2、信号量:
信号量分为计数信号量和二值信号量,一般用于同步,应用场景比如一个任务等待信号量,另一个任务或者中断释放一个信号量,信号量相比于互斥量:
1、没有优先级继承
2、可以在中断中使用
此外二值信号量,也可以用于资源锁加锁,其优缺点如下:
优点:
1、可以支持中断中使用
2、可以在其他任务释放
缺点:
1、如果其他task在使用共享资源,此时中断获取锁,会立即返回,且结果失败,需要注意单独处理
2、因为可以被其他task释放,因此使用过程中需要注意
2、什么是优先级翻转以及其危害
RTOS不同于桌面系统,其特点就是高实时性。其中优先级就是用来保障实时性的手段之一。但在某些场景下,高优先级的任务可能会被挂起不能执行,我们来看看下面场景:
有taskA、taskB、taskC 3个任务,优先级为taskA > taskB > taskC ,有一个共享资源shareData(shareData可能是全局内存、I/O等等)。现在看看如下场景:
1、在time1时刻taskC ready请求获取shareData资源;
2、在time2时刻taskA ready且也请求获取shareData资源,由于shareData资源被taskC占用,taskA再次进入阻塞等待状态;
3、在time3时刻taskB ready,开始抢占taskC的执行权限,此时taskC进入阻塞状态,taskB开始执行
上述场景是否看出啥问题?
taskA的任务优先级高于taskB,但是由于taskA等待请求获取 shareData资源,且taskC持有了shareData资源,因此低优先级的taskB执行了,但是高优先级的taskA被挂起了。
再进一步,试想一下,如果taskB不是一个task,而是一系列优先级介于taskA和taskC之间的任务列表呢?
上述这种由于等待被低优先级持有的共享资源而进入非预期的长久阻塞状态的问题,就是优先级翻转。
优先级翻转在RTOS中,可能会产生不可预期的危害,比如:“What Happened on Mars?”
3、FreeRTOS的规避策略
很多RTOS都有解决优先级翻转的策略,而且基本原理类似,这里结合FreeRTOS来讲述下RTOS是如何尽量规避优先级翻转的
3.1、优先级继承
结合上面的栗子来说说优先级继承的概念:
在高优先级的taskA获取资源锁时,将taskC的优先级临时提高为taskA的优先级,那么上述案例中,taskB就无法打断taskC的执行,因此taskC执行完成释放资源锁后,taskA能及时的进入ready状态,并执行
那么下面结合FreeRTOS源码,我们来看看优先级继承是如何实现的。
3.1.1、第一次交锋
taskC获取互斥锁时,执行了如下代码段:
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* Record the information required to implement
priority inheritance should it become necessary. */
pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
上面代码大致意思是,如果是互斥锁,调用函数pvTaskIncrementMutexHeldCount()设置互斥锁持有者,函数pvTaskIncrementMutexHeldCount()代码如下:
void *pvTaskIncrementMutexHeldCount( void )
{
/* If xSemaphoreCreateMutex() is called before any tasks have been created
then pxCurrentTCB will be NULL. */
if( pxCurrentTCB != NULL )
{
( pxCurrentTCB->uxMutexesHeld )++;
}
return pxCurrentTCB;
}
结合上面两段代码,第一次持有互斥锁时,执行了如下操作:
1、将当前任务快的uxMutexesHeld自加加,表示已经有人持有了互斥锁
2、将当前任务的任务控制块返回赋值给MutexHolder
3.1.2、换将再战
当taskA获取互斥锁时,由于已经被人持有,所以,进入阻塞等待状态,此时执行了如下代码:
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
其中函数vTaskPriorityInherit() 部分代码如下:
void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
/* 如果Mutex holder的优先级低于当前等待任务优先级,则进行优先级继承 */
if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
{
if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Inherit the priority before being moved into the new list. */
pxTCB->uxPriority = pxCurrentTCB->uxPriority;
prvAddTaskToReadyList( pxTCB );
}
else
{
/* Just inherit the priority. */
pxTCB->uxPriority = pxCurrentTCB->uxPriority;
}
traceTASK_PRIORITY_INHERIT( pxTCB, pxCurrentTCB->uxPriority );
}
}
结合上面的分析,大致梳理清楚了优先级继承的实现流程。但是战役还未结束:
taskC的优先级何时恢复呢?
3.2、优先级恢复
优先级恢复流程相对比较简单,在taskC使用完,调用释放接口的时候,会执行优先级恢复,此时taskC继续恢复其低优先级。流程如下:
xSemaphoreGive() -->xQueueGenericSend() -->prvCopyDataToQueue() -->xTaskPriorityDisinherit()
代码也比较简单,这里就不贴出来分析
4、引申互动
4.1、为什么Mutex 互斥量不能中断中调用?
我的理解大概如下几点:
1、中断不能因为等待阻塞的互斥量而进入阻塞状态
2、中断中调用互斥量,会打破优先级继承的里面
4.2、为什么Semaphore 信号量不支持优先级继承?
我同样用栗子来解释这个问题:
有A、B、C三个任务,优先级A > B > C
现在time1时刻C任务开始等待信号量sem_kk, time2时刻A任务也开始等待信号量 sem_kk,time3时刻任务B释放了一个信号量sem_kk。(time3 > time2 > time1)
那问现在来看看如下问题:
此时RTOS中是希望A还是C先执行呢?
答案很明显,是希望A先执行,因为A的任务优先级更高。
但是如果信号量实现了优先级继承,结果会如何呢?
C在等待信号量sem_kk的过程中优先级临时继承了A的优先级,此时A、C优先级相同,无法确保A一定是优先于C执行的了
因此
信号量一般是用于同步的,同步的场景上,需要保证优先级高的任务优先执行,做到真正的实时性。 优先级继承会打破这个需求