序言
本篇是FreeRTOS系列的番外篇,其因是最近老被问到”优先级继承“问题,只能回答得一个大概,对内部具体的实现了解不够深刻,所以就有了这篇文章。
Linux的第一篇文章已经初步成型,在搭建qemu和gdb调试module的时候花了不少时间,但是我认为是有必要的,有了gdb调试后,可以更加深刻的了解代码并且能验证自己对代码理解是否正确。如果读者有需要,我也会单独出一篇文章。
优先级反转
既然谈到优先级反转,那么需要了解何为优先级继承。
如果需要对共享资源进行保护,那么简单的模型如下:
take
操作资源
give
在操作资源前加锁,操作完成后释放锁,这是成对的操作。。
现在假设有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和taskC都ready
,但taskB
优先级高于taskC
,于是执行taskB
。
taskA
的任务优先级高于taskB
,但是taskA
请求获取 shareData
资源,且taskC
持有了shareData
资源,因此低优先级的taskB
执行了,但是高优先级的taskA
被挂起了。
如果TaskA
是用于接收外设的数据,那么由于优先级反转而被挂起,会造成外设数据接收不及时而丢数据。进一步,taskB
不是一个task,而是一系列优先级介于taskA
和taskC
之间的任务列表呢?就会造成不可避免的灾难。
总结:当高优先级任务正等待信号量(此信号量被一个低优先级任务拥有着)的时候,一个介于两个任务优先之间的中等优先级任务开始执行。
优先级继承
优先级继承就是为了解决优先级反转问题而提出的一种优化机制。其大致原理是让低优先级线程在获得同步资源的时候(如果有高优先级的线程也需要使用该同步资源时),临时提升其优先级。以前其能更快的执行并释放同步资源。释放同步资源后再恢复其原来的优先级。
在使用互斥锁的情况下,看看如下场景:
1、在time1
时刻taskC ready
请求获取shareData
资源;
2、在time2
时刻taskA ready
且也请求获取shareData
资源,由于shareData
资源被taskC
占用,taskA
再次进入阻塞等待状态。但是在这个时刻,会将taskC的优先级提高到与TaskA一样。
3、在time3
时刻taskB和taskC都ready
,但taskC
优先级高于taskB
,于是执行taskC
,taskC执行完毕后,恢复taskC的原本优先级。
标粗的地方是区别于信号量的,通过优先级继承,使用TaskA能提前得到调度。
实现方法
这一章节分析下在FreeRTOS中如何实现优先级继承。
在FreeRTOS中,sem和mutex都是基于queue实现的。
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
QueueHandle_t xNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( ( Queue_t * ) xNewQueue );
return xNewQueue;
}
在讲解优先级继承之前,需要理解一下基本的锁相关概率。我们知道获取锁(take)和释放锁(give)两种操作。获取锁实际上是uxMessagesWaiting--
,释放锁是uxMessagesWaiting++
。在获取锁函数中对uxMessagesWaiting
进行判断,简化如下:
take function()
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 ) // 判断是否有其他任务持有锁,如果锁已经被持有,那么就阻塞
{
uxMessagesWaiting--;
}
}
如果在taskC
在take
之后未释放,此时又有另一个taskA
去take,那么uxMessagesWaiting
小于0,表明已经有人持锁,需要等待。如果在taskC
释放锁,那么uxMessagesWaiting++
,taskA
去take
时,就能获取到锁,往下执行。
在获取锁时,关键代码如下:
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait )
{
const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
**if( uxSemaphoreCount > ( UBaseType_t ) 0 )**
{
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* Record the information required to implement
priority inheritance should it become necessary. */
pxQueue->u.xSemaphore.xMutexHolder = **pvTaskIncrementMutexHeldCount();**
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}else
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
}
taskEXIT_CRITICAL();
}
}
}
当taskC
获取信号量时,能成功获取到信号量,调用pvTaskIncrementMutexHeldCount
函数将当前线程的TCB
保存到pxQueue->u.xSemaphore.xMutexHolder
中, 并当前线程记录持锁次数pxCurrentTCB->uxMutexesHeld
。
#if ( configUSE_MUTEXES == 1 )
TaskHandle_t pvTaskIncrementMutexHeldCount( void )
{
/* If xSemaphoreCreateMutex() is called before any tasks have been created
then pxCurrentTCB will be NULL. */
if( pxCurrentTCB != NULL )
{
( pxCurrentTCB->uxMutexesHeld )++;
}
return pxCurrentTCB;
}
#endif /* configUSE_MUTEXES */
当taskA
抢占taskC
,并尝试获取信号量时,由于taskC
已经持锁,未被释放,所以不能成功获取锁。往下执行到xTaskPriorityInherit
进行优先级继承
BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxMutexHolderTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
/* If the mutex was given back by an interrupt while the queue was
locked then the mutex holder might now be NULL. _RB_ Is this still
needed as interrupts can no longer use mutexes? */
if( pxMutexHolder != NULL )
{
/* 如果pxMutexHolderTCB(taskC)的优先级比当前线程(taskA)的优先级低,那么进行优先级继承
将pxMutexHolderTCB(taskC)提高至与当前线程优先级一样。*/
if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
{
/* Adjust the mutex holder state to account for its new
priority. Only reset the event list item value if the value is
not being used for anything else. */
if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* If the task being modified is in the ready state it will need
to be moved into a new list. */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* It is known that the task is in its ready list so
there is no need to check again and the port level
reset macro can be called directly. */
portRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Inherit the priority before being moved into the new list. */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
prvAddTaskToReadyList( pxMutexHolderTCB );
}
else
{
/* Just inherit the priority. */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
}
traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );
/* Inheritance occurred. */
xReturn = pdTRUE;
}
else
{
if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
{
/* The base priority of the mutex holder is lower than the
priority of the task attempting to take the mutex, but the
current priority of the mutex holder is not lower than the
priority of the task attempting to take the mutex.
Therefore the mutex holder must have already inherited a
priority, but inheritance would have occurred if that had
not been the case. */
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
需要注意的是uxBasePriority
存放着默认优先级,用于优先级继承后的优先级恢复。
prvInitialiseNewTask() // 创建线程函数
{
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
}
总结优先级继承:
-
当
taskC
获取信号量时,根据pxQueue->uxMessagesWaiting
判断当前线程能否获取到锁。由于首次获取,那么能获取到锁,将当前线程保存到pxQueue->u.xSemaphore.xMutexHolder
中,表明当前锁的持有者是taskC
并记录当前线程持锁的次数(pxCurrentTCB->uxMutexesHeld
)。 -
当
taskA
获取信号量,如果之前线程已经释放锁,那么能获取到锁。由于taskA
的优先级比taskC
优先级高,所以很有可能在taskC
还未释放信号量时,进行了抢占。当前线程(taskA)比持锁线程(taskC
)的优先级高,进行优先级继承, 将持锁线程(taskC
)优先级提高到与当前线程(taskA)优先级一致。
优先级恢复
获取锁时进行优先级继承,释放锁时进行优先级恢复。释放锁的路径如下
xSemaphoreGive() -->xQueueGenericSend() -->prvCopyDataToQueue() -->xTaskPriorityDisinherit()
由于taskA
获取不到锁,进入阻塞状态,现在taskC
比taskB
优先级高,所以taskC
先运行,执行到释放锁时调用xTaskPriorityDisinherit
将taskC
优先级恢复到默认值(uxBasePriority
)。
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL )
{
/* A task can only have an inherited priority if it holds the mutex.
If the mutex is held by a task then it cannot be given from an
interrupt, and if a mutex is given by the holding task then it must
be the running state task. */
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
( pxTCB->uxMutexesHeld )--; // 持锁次数自减,take的时候自加了的。
// 判断是否进行了优先级继承
if( pxTCB->uxPriority != pxTCB->uxBasePriority )
{
/* 只有在uxMutexesHeld才恢复优先级继承。如果在当前任务中进行了两次take
那么只有在第二次give时,恢复优先级继承 */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
{
/* A task can only have an inherited priority if it holds
the mutex. If the mutex is held by a task then it cannot be
given from an interrupt, and if a mutex is given by the
holding task then it must be the running state task. Remove
the holding task from the ready/delayed list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Disinherit the priority before adding the task into the
new ready list. */
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
// 恢复默认优先级
pxTCB->uxPriority = pxTCB->uxBasePriority;
/* Reset the event list item value. It cannot be in use for
any other purpose if this task is running, and it must be
running to give back the mutex. */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
prvAddTaskToReadyList( pxTCB );
/* Return true to indicate that a context switch is required.
This is only actually required in the corner case whereby
multiple mutexes were held and the mutexes were given back
in an order different to that in which they were taken.
If a context switch did not occur when the first mutex was
returned, even if a task was waiting on it, then a context
switch should occur when the last mutex is returned whether
a task is waiting on it or not. */
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
总结优先级恢复:
-
判断是否进行过优先级继承;
-
判断获取锁和释放锁的次数是否一致;
-
如果一致恢复默认优先级。
原文始发于微信公众号(TreeNewBeer):FreeRTOS番外篇—-优先级反转
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/115059.html