FreeRTOS信号量
参考链接:FreeRTOS-信号量详解_freertos信号量-CSDN博客
目录
- FreeRTOS信号量
- 一、信号量是什么
- 二、 FreeRTOS信号量
- 1、二值信号量
- 1、获取信号量
- 2、释放信号量
- 2、计数信号量
- 3、互斥信号量
- 1、优先级反转
- 2、优先级继承
- 3、源码解析
- 1、互斥量创建
- 2、获取互斥量
- 3、释放互斥量
- 4、递归互斥信号量
- 1、互斥信号量的缺陷
- 2、递归互斥信号量解析
- 1、创建递归互斥信号量
- 2、释放递归互斥信号量
- 3、获取递归互斥信号量
一、信号量是什么
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,实现的方式主要就是依靠队列(信号量是特殊的队列)的任务阻塞机制。
既然队列也可以实现同步与互斥那为什么还要信号量?
信号量相比队列更节省空间,因为实现同步与互斥不需要传递数据,所以信号量没有队列后面的环形存储区,信号量主要就是依靠计数值uxMessagesWaiting(在队列中表示队列现有消息个数,在信号量中表示有效信号量个数)。
uxMessagesWaiting作为复用在信号量中表示资源的数量,所有获取信号量的任务都会将该整数减一,当该整数值为零时,则此时想要获取的任务则会进入阻塞态,释放信号量的任务都会将该整数加一,不过当该整数值为最大值时(最大值要看你是什么信号量),则此时想要释信号量的任务则并不会进入阻塞态,直接返回释放信号量失败。
二、 FreeRTOS信号量
1、二值信号量
所谓二值信号量其实就是一个队列长度为1,没有数据存储区的队列,而二值则表示计数值uxMessagesWaiting只有0和1两种状态(就是队列空与队列满两种情况),uxMessagesWaiting在队列中表示队列中现有消息数量,而在信号量中则表示信号量的数量。
uxMessagesWaiting为0表示:信号量资源被获取了;uxMessagesWaiting为1表示:信号量资源被释放了。把这种只有 0 和 1 两种情况的信号量称之为二值信号量。
由于二值信号量就是特殊的队列,其实它的运转机制就是利用了队列的阻塞机制,从而达到实现任务之间的同步与互斥(有优先级反转的缺陷)。
二值信号量用于同步:
在串口接收中,我们并不知道什么时候有数据发送过来(等数据过来标记一次),还有一个处理串口接收到的数据,在任务系统中不可能时时刻刻去判断是否有串口有数据过来(判断标志位),所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来。
二值信号量一般不用于任务之间的互斥,因为它有优先级反转的缺点,解决方式就是使用互斥信号量(具有优先级继承的机制能减少优先级反转的影响)
在任务与在中断中使用的API函数的区别,其实函数的主体代码是一模一样的,就是在中断中:
1.不能允许阻塞
2.不能立马发起任务调度
二值信号量的创建也是调用xQueueGenericCreate函数进行队列的创建,创建的参数:队列长度为1,队列项(消息)为0,,也就是缓冲区大小为0,队列类型为二值信号量
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
信号量的个数(资源的个数)用队列结构体中的uxMessagesWaiting表示(uxMessagesWaiting在队列中表示队列中现有消息数量,而在信号量中则表示信号量的数量)
1、获取信号量
获取信号量的函数:
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
#if ( configUSE_MUTEXES == 1 )
BaseType_t xInheritanceOccurred = pdFALSE;
#endif
/* Check the queue pointer is not NULL. */
configASSERT( ( pxQueue ) );
/* 检查这是否真的是信号量,在这种情况下,项目大小将为0 */
configASSERT( pxQueue->uxItemSize == 0 );
/*如果调度程序已挂起,则无法阻塞。 */
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
for( ; ; )
{
/* 关中断 */
taskENTER_CRITICAL();
{
/* 信号量是消息大小为0的特殊队列
uxMessagesWaiting在信号量中表示资源数 */
const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
/* 判断信号量是否有资源 */
if( uxSemaphoreCount > ( UBaseType_t ) 0 )
{
traceQUEUE_RECEIVE( pxQueue );
/* 信号量资源数减一 */
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
#if ( configUSE_MUTEXES == 1 )
{
/* 判断队列的类型是否为互斥信号量 */
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* 设置互斥信号量的持有者,并更新该任务拥有互斥信号量的数量 */
pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
}
}
#endif /* configUSE_MUTEXES */
/* 判断信号量的释放阻塞任务列表中是否有任务 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
/* 将阻塞任务从信号量释放阻塞任务列表中移除 */
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
}
/* 开中断 */
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
/* 若信号量无资源不能获取信号量,则需要判断任务是否需要阻塞 */
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 要发生继承,必须存在初始超时,并且调整后的超时不能变为 0,因为如果为 0,函数就会退出。*/
#if ( configUSE_MUTEXES == 1 )
{
configASSERT( xInheritanceOccurred == pdFALSE );
}
#endif /* configUSE_MUTEXES */
/* 信号灯计数为 0,未指定阻止时间(或阻止时间已过期),因此立即退出。 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 信号量无资源,并指定了阻塞时间(则任务需要阻塞),
所以需要记录下此时系统节拍计数器的值和溢出次数
用于下面对阻塞时间进行补偿 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
}
}
/* 开中断 */
taskEXIT_CRITICAL();
/* 开中断:此时中断和其他任务可以向信号量获取和获取信号量。 */
/* 挂起任务调度器 */
vTaskSuspendAll();
/* 队列上锁 */
prvLockQueue( pxQueue );
/* 判断阻塞时间补偿后,是否还需要阻塞 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
/* 阻塞时间补偿后,还需要进行阻塞(未超时)
再次确认信号量是否为空(无资源) */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
/* 判断队列类型是否为互斥信号量 */
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
/* 进行优先级继承,互斥信号量用于解决优先级翻转的问题*/
xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
}
taskEXIT_CRITICAL();
}
}
#endif /* if ( configUSE_MUTEXES == 1 ) */
/* 将任务添加到队列读取阻塞任务列表中进行阻塞 */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
/* 解锁队列 */
prvUnlockQueue( pxQueue );
/* 恢复任务调度器,判断是否需要的进行任务切换 */
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
}
else
{
/* 解锁队列 */
prvUnlockQueue( pxQueue );
/* 恢复任务调度器 */
( void ) xTaskResumeAll();
}
}
else
{
/* 已超时,解锁队列 */
prvUnlockQueue( pxQueue );
/* 恢复任务调度器 */
( void ) xTaskResumeAll();
/* 判断信号量是否为空 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
#if ( configUSE_MUTEXES == 1 ) // 互斥量特有的判断
{
/* 判断任务是否发生优先级继承 */
if( xInheritanceOccurred != pdFALSE )
{
taskENTER_CRITICAL();
{
UBaseType_t uxHighestWaitingPriority;
/* 互斥锁上的此任务阻塞导致另一个任务继承此任务的优先级。 现在此任务已超时,
优先级应再次取消继承,但只能低至等待相同互斥锁的下一个最高优先级任务。 */
uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
}
taskEXIT_CRITICAL();
}
}
#endif /* configUSE_MUTEXES */
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
}
} /*lint -restore */
}
/*-----------------------------------------------------------*/
总结:
获取信号量其实与队列的出队基本差不多的
- 获取信号量资源数uxMessagesWaiting(在二值信号量中只能是0/1,代表信号量为空或满)
- 判断信号量是否有资源uxSemaphoreCount >0 (有资源才能被获取)
- 如果信号量有资源,uxSemaphoreCount 计数值减一
- 如果信号量无资源
(1).如果设置的不等待,等待时间为0,则即可返回获取信号量失败的错误
(2).如果设置的等待时间为0-portMAX_DELAY,则判断任务是否超时,若未超时,即刻阻塞。
(3).如果设置的等待时间portMAX_DELAY,则任务一直阻塞,直到信号量有资源才会被唤醒。
2、释放信号量
xSemaphoreTake函数本质上调用的是xQueueGenericSend函数
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
相当于xSemaphoreGive()向xQueueGenericSend()函数中传入的参数为
-
xSemaphore:要释放的信号量
-
NULL:信号量不需要传递数据
-
semGIVE_BLOCK_TIME:释放信号量不能阻塞,(信号量计数值达最大值,在释放则直接返回错误)
-
queueSEND_TO_BACK:向队尾入队(这个不重要,因为信号量操作并不会拷贝数据)
xQueueGenericSend()函数在讲解队列的文章已经讲解过,可以看上一篇文章,不再赘述
总结:
1.判断信号量计数值是否达最大值1,计数值要等于0才能释放信号
2.若满足信号量释放条件,则计数值(uxMessagesWaiting )加1,并不会拷贝数据,此时计数值为1表示信号量有资源,如果有因为获取信号量而阻塞的任务,则需要将其唤醒(从xTasksWaitingToReceive列表中移除,将任务挂入就绪列表)。
3.若不满足信号量释放条件(计数值达最大值,二值信号量就是1),因为xSemaphoreGive()带入的参数为阻塞时间为0,则释放信号量不会阻塞,直接返回释放信号量失败。
2、计数信号量
计数值信号量也与二值信号量一样也是特殊的队列,二值信号量是长度为1的队列,而计数值信号量是长度大于0的队列,他们本质的区别就是应用场景不同二值信号量常用于同步,计数值信号量常用于事件计数、资源管理,其实如果限定计数值信号量计数值最大值只能为1则就等同于二值信号量。
由上图可知,计数值信号量与二值信号量相比,除了创建函数不同其他函数都相同,所以主要将计数值信号量的创建函数。
xSemaphoreCreateCounting本质上还是通过队列创建函数xQueueGenericCreate,创建一个长度为uxMaxCount,消息大小为0的特殊队列即计数值信号量,当然创建成功后,将计数值初值赋值成uxInitialCount,就是这么简单,然后其他获取、释放信号量等操作与二值信号量一模一样。
3、互斥信号量
前面的二值信号量/计数值信号量其实也可以实现互斥,但二值信号量存在优先级反转的缺点,而解决方法就是优先级继承,优先级继承是互斥信号量的特性,则互斥信号量的本质就是具有优先级继承的二值信号量。
1、优先级反转
什么叫做优先级反转,简单来说就是低优先级的任务霸占CPU资源,导致高优先级任务无法运行的情况:低优先级的任务持有一个被高优先级任务所需要的共享资源(低优先级给共享资源上锁,高优先级需要等待低优先级解锁共享资源,而在这期间正好一个中等优先级的任务打断低优先级任务去执行,则低优先级任务迟迟不能运行则不能给共享资源解锁,导致高优先级要一直等待(阻塞),而中等优先级的任务一直运行。
导致这种严重优先级反转的问题的根本原因在于持有锁的低优先级任务因为优先级低,而得不到执行,得不到执行的话,就无法解锁,无法解锁就导致高优先级的任务获取锁会失败,从而导致高优先级任务一直在阻塞状态。
优先级反转示意:
假设有三个任务A,B,C,优先级分别为低、中、高优先级,假设任务A先运行(中、高优先级的任务在阻塞)调用上锁函数(获取二值信号量,使用某个共享资源),然后任务B解除阻塞抢占任务A,任务B运行,最后任务C解除阻塞抢占任务B,任务C运行,任务C想要去获取锁(获取二值信号量,使用某个共享资源),问题是任务A还没有释放这个锁(释放二值信号量),则任务C想要获取这个锁就会失败进入阻塞状态,任务C进入阻塞态后,任务B中等优先级的任务一直执行,这样一来任务A就无法运行(无法去解锁),则任务C(优先级最高的任务会一直阻塞),则最终导致明明任务C的优先级最高反而得不到执行,而中等优先级的任务B一直执行,则就是所谓的优先级反转,这种情况在实时操作系统中是绝对不允许的。
所以解决方式就是优先级继承,不是说低优先级任务无法执行嘛,那我就在高优先级任务进入阻塞之前将低优先级任务的优先级提升至与高优先级一致,这样等高优先级任务进入阻塞之后,低优先级任务就能继承高优先级任务的优先级,这样低优先级任务就能尽快执行(从而解锁,让高优先级能够获取锁)。
2、优先级继承
优先级继承:暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。
当高优先级任务获取锁进入阻塞之前,会将低优先级任务的优先级提升至与高优先级任务一样,则等高优先级任务C进入阻塞之后,则低优先级任务A继承任务C的优先级,则任务A立马执行(任务B没机会执行),则任务A继续执行就能尽快开锁(释放信号量),这样就能极大的减少优先级反转带来的影响。
这里为什么说是减少优先级反转带来的影响,而不是消除优先级反转?
因为优先级反转概念是指一个低优先级的任务持有一个被高优先级任务所需要的共享资源,也就是说当因为任务A上锁之后,然后任务C来获取锁失败而进入阻塞态,在高优先级任务进入阻塞态的时候已经是优先级反转了,而优先级继承只是让任务A尽快执行(尽快解锁),让高优先级任务C尽快解除阻塞态,所以优先级继承只能是缩短优先级反转的时间。
3、源码解析
互斥信号量相当于是一个具有优先级继承机制的二值信号量,所以互斥信号量就是为了降低优先级反转所带来的影响。一般用于保护共享临界资源,从而实现独占式访问,而可以降低优先级反转带来的影响。
比如:独占使用串口,不可能两个任务同时向串口发送、接收数据,此时就可以使用互斥量来互斥的使用串口(另一个任务必须等待当前正在使用传递任务使用完串口才能使用)
- 注意:
优先级继承只是减轻优先级反转的影响 - 互斥信号量并不能用于中断中,因为互斥信号量具有优先级继承的机制,而中断不是任务,没有任务优先级;另外,中断服务函数中不能因为等待互斥量而进入阻塞态。
从之前消息队列的解析中可知,以队列出队为例,如果队列为空,那么在中断中尝试出队会直接返回出队错误,而不会陷入阻塞。
获取、释放信号量的函数与二值信号量是一样的但是函数里面有关于互斥信号量的条件编译。
在任务结构体TCB中,如果配置了使用互斥量的宏,那么TCB会多两个成员,如下:
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; // 任务的基础优先级,用于记录优先级继承前的任务优先级
UBaseType_t uxMutexesHeld; // 持有互斥量数量
#endif
1、互斥量创建
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;
}
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
pxNewQueue->u.xSemaphore.xMutexHolder = NULL;//互斥量的持有者初始化为空
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;//初始化队列类型
/* 如果是递归互斥量,互斥信号量的资源数初始化为0 */
pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;
/* 释放一次信号量,新建的互斥信号量是有资源的 */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
}
**其实xQueueCreateMutex()函数里面最终调用的还是队列创建函数xQueueGenericCreate(),且传入的参数与二值信号量一样,同样为队列长度为1的特殊队列,与二值信号量不同是里面还调用了prvInitialiseMutex函数。**在创建互斥信号量时候会释放一次信号量,表示一开始就有资源,而二值信号量则不会释放。
优先级继承发生在获取互斥量,解除优先级继承发生在释放互斥量。
2、获取互斥量
获取信号量的函数xSemaphoreTake实际是调用了xQueueSemaphoreTake,xQueueSemaphoreTake函数在二值信号量获取那里讲解过,不再赘述。只不过在函数内部根据判断当前是否使用互斥量去进行额外操作。
优先级继承发生在高优先级任务进入阻塞之前,调用了xTaskPriorityInherit()函数进行优先级继承。
xTaskPriorityInherit函数的主要内容:
#if ( configUSE_MUTEXES == 1 )
BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxMutexHolderTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL ) // 判断互斥量是否被持有
{ // 如果互斥锁持有者的当前优先级低于尝试获取互斥锁的优先级,那么发生优先级继承
if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
{
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 );
}
// 如果正在修改的任务处于就绪态,则需要将其移动到新列表中
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
{ // 从原来的就绪链表中移除
if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{ // 更新位图
portRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority, uxTopReadyPriority );
}
// 优先级继承
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
// 移动到新的就绪链表中
prvAddTaskToReadyList( pxMutexHolderTCB );
}
else
{ // 修改的任务不处于就绪态,那么只需要修改其优先级
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
}
/* Inheritance occurred. */
xReturn = pdTRUE;
}
else
{ // 如果持有互斥量的任务的当前优先级高于当前任务的优先级,但基础优先级低于当前任务的优先级,说明持有互斥量的任务已经发生了优先级继承。
if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
{
xReturn = pdTRUE;
}
}
}
return xReturn;
}
#endif /* configUSE_MUTEXES */
1. 判断互斥信号量是否被持有,然后就是判断如果互斥锁持有者的优先级低于尝试获取互斥锁的任务的优先级(高优先级任务),才要发送优先级继承,因为互斥锁持有者已经是高优先级任务了,就不需要发生优先级继承了。
2. 所谓继承优先级,就是该任务的优先级修改成与高优先级任务一样,然后将任务添加到优先级对应的就绪列表中(这样任务就可以以新的优先级参与调度)
3、释放互斥量
解除优先级继承发生了释放互斥量的地方,也就是xSemaphoreGive函数,本质上属于队列的入队函数,在前面消息队列讲解中有源码分析。
解除优先级继承藏在prvCopyDataToQueue()函数中,调用xTaskPriorityDisinherit()函数解除优先级继承。
// prvCopyDataToQueue函数
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
}
#endif /* configUSE_MUTEXES */
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL ) // 互斥量持有者不为空
{
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
// 该任务持有互斥量数量-1
( pxTCB->uxMutexesHeld )--;
// 互斥锁的持有者是否继承了另一个任务(高优先级任务)的优先级
if( pxTCB->uxPriority != pxTCB->uxBasePriority )
{
/* 仅在没有其他互斥锁的情况下取消继承 */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
{ // 将该任务从就绪链表中移除
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{ // 更新位图
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
pxTCB->uxPriority = pxTCB->uxBasePriority; // 恢复成基础优先级
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );
// 将任务插入到新的就绪链表
prvAddTaskToReadyList( pxTCB );
xReturn = pdTRUE;
}
}
}
return xReturn;
}
总结:
所谓优先级继承,其实就是低优先级任务继承高优先级任务的优先级
发生优先级继承:在高优先级任务获取信号量失败(低优先级任务已经持有信号量),进入阻塞态之前,将持有信号量的低优先级任务的优先级提升
解除优先级继承:在持有信号量的低优先级任务释放信号量的时候,将自己的优先级恢复到初始值(因为已经释放了信号量,完成了任务)
而改变任务优先级的本质就是任务改变任务所挂入的优先级对应的就绪链表。
所以互斥信号量与二值信号量的区别在于:
- 互斥信号量创建时就有资源(因为是专门针对互斥的),不需要手动释放一次信号量,而二值信号量则没有。
- 互斥信号量具有优先级继承的机制,能减少优先级反转带来的影响。
4、递归互斥信号量
1、互斥信号量的缺陷
1.并没有实现谁持有锁就由谁释放
也就是说,比如任务A想要去独占的使用串口资源(上锁),但是有一个任务不讲武德直接还没等任务A解锁(释放信号量),就偷偷去解锁,则串口资源就被失去保护,则任何任务都可以去使用串口(则就会打印的乱七八糟)。
2.不能递归上锁,会导致死锁
而递归互斥信号量则完美实现了这两点,而且递归互斥信号量也是基于互斥信号量的,所以一样具有优先级继承的机制。
2、递归互斥信号量解析
1、创建递归互斥信号量
创建递归互斥信号量与互斥信号量是一模一样的:xQueueCreateMutex函数
2、释放递归互斥信号量
xSemaphoreGiveRecursive函数,本质上是调用xQueueGiveMutexRecursive函数
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
// 判断当前递归互斥信号量的获取者是否为持有者
if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
{
traceGIVE_MUTEX_RECURSIVE( pxMutex );
// 更新递归互斥信号量的被递归获取计数器
( pxMutex->u.xSemaphore.uxRecursiveCallCount )--;
/* 判断递归互斥信号量的被获取次数是否为0,被获取次数为0,才真正释放 */
if( pxMutex->u.xSemaphore.uxRecursiveCallCount == ( UBaseType_t ) 0 )
{
// 释放信号量
( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
xReturn = pdPASS;
}
else
{ // 无法提供互斥锁,因为调用任务不是持有者
xReturn = pdFAIL;
traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
return xReturn;
}
#endif /* configUSE_RECURSIVE_MUTEXES */
1.想要释放递归互斥信号量必须是信号量的持有者(这样就实现了谁上锁就由谁来解锁)
2.让uxRecursiveCallCount减一,因为uxRecursiveCallCount表示该信号量上锁了多少次,当uxRecursiveCallCount等于0就说明递归互斥信号量释放了最后一次信号量,则就要真正去执行一次释放。
3、获取递归互斥信号量
xSemaphoreTakeRecursive本质上是调用xQueueTakeMutexRecursive函数
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
traceTAKE_MUTEX_RECURSIVE( pxMutex );
// 判断当前递归互斥量的获取者是否为持有者
if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
{
// 递归互斥量的被递归获取计数值加一,获取成功
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
xReturn = pdPASS;
}
else // 当前递归互斥信号量的获取者不是持有者
{
// 尝试获取信号量,根据设定的超时时间发生不同的情况,可能直接返回,也可能发生阻塞
xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );
// 判断是否成功
if( xReturn != pdFAIL )
{
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
}
else
{
traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
return xReturn;
}
#endif /* configUSE_RECURSIVE_MUTEXES */
由谁上锁就由谁解锁,递归上锁功能是依靠xMutexHolder(记录互斥量的持有者)、uxRecursiveCallCount(递归上锁深度)两个变量实现的。