【freertos】011-信号量、互斥量及优先级继承机制源码分析
前言
源码实现主要参考消息队列章节,因为底层源码是一样的,所以本章笔记侧重点在信号量、互斥量概念。
源码部分与消息队列重叠的函数不分析。
参考:李柱明博客
11.1 任务同步
同步,执行完一个再到下一个,一条逻辑流。
异步,执行者着这个的时候也可执行另外一个,不止一条互相独立的逻辑流。
资源保护,控制资源的被访问权限。
在一个多任务并发系统中,可能存在多任务对共享资源的并发访问,这样可能会导致数据不可控、非预期。
所以我们需要对共享资源进行保护,而任务同步可以实现对共享资源访问进行保护,维护数据一致性,产出结果达预期。
实现任务同步常见的组件有信号量、互斥量、锁等等。
同步和互斥的区别:
- 同步:按序访问,并非针对某个资源的保护,而是达到某个条件才继续执行。
- 互斥:指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性。但是互斥无法限制访问者对资源的访问顺序,所以访问是无序的。
11.2 信号量概念
信号量(Semaphore)也是实现任务间通信的机制的一种。
可实现任务间同步、临界资源的互斥访问。
信号量的核心其实就是一个非负值,表示当前资源数量:
- 为0时,表示没有资源,不能访问。
- 非0时,表示还有资源,可以访问。
但是在freertos内核,一个信号量除了核心的非负值外,还需要其他数据来维护当前信号量的特性、运作。
如读、写阻塞链表,实现其阻塞机制,信号量内部中断锁,实现其中断访问特性。
信号量细分:
- 二值信号量。其资源最多只有1个。
- 计数信号量。其资源最多不止1个。
- 递归信号量。同任务可重复获取。获取多少次就需要释放多少次,这个任务才能真正释放当前递归信号量。
11.3 二值信号量
11.3.1 二值信号量概念
二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥量其信号量最大都是1,只有0和1两种状态,使用逻辑也类似,但是也有区别,主要在内部实现特性和上层应用场景方面:
- 互斥量有优先级继承机制。(在互斥量章节详细介绍)
- 二值信号量存在优先级翻转缺点。
- 互斥量多用于资源保护。
- 互斥量多用于任务同步。
11.3.2 优先级翻转
例子运行条件:
- 创建3个任务Task1,Task2和Task3,优先级分别为3,2,1。也就是Task1的优先级最高。
- 任务Task1和Task3互斥访问串口打印printf,采用二值信号实现互斥访问。
- 起初Task3通过二值信号量正在调用printf,被任务Task1抢占,开始执行任务Task1。
运行过程描述如下:
- 任务Task1运行的过程需要调用函数printf,发现任务Task3正在调用,任务Task1会被挂起,等待Task3释放函数printf。
- 在调度器的作用下,任务Task3得到运行,Task3运行的过程中,由于任务Task2就绪,抢占了Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务Task1需要等待Task2执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务Task1等待低优先级任务Task2完成。所以这种情况被称之为优先级翻转问题。
- 任务Task2执行完毕后,任务Task3恢复执行,Task3释放互斥资源后,任务Task1得到互斥资源,从而可以继续执行。
该图源自安富莱
11.3.3 二值信号量运作机制
创建信号量:
- 为其信号量分为资源。
- 初始化信号量控制块及其资源初始个数。
获取信号量:
- 如果信号量为1,获取信号量成功,当前信号量改为0状态,在释放前,不能被获取,当前任务可以继续往下跑。
- 如果信号量为0,说明有任务已经占用当前信号量了,获取失败,要么阻塞,要么返回获取失败。
释放信号量:
- 如果信号量为1,说明当前信号量没有被其他任务占用,直接调用释放要么阻塞
semGIVE_BLOCK_TIME
个节拍,要么直接返回释放失败。 - 如果信号量为0,说明当前信号量已经被占用,通过释放后,当前信号量为1。
11.4 计数信号量
11.4.1 计数信号量概念
计数信号量的最大资源大于1,主要用于计数。
获取信号量,信号量值减1;释放信号量,信号量值加1。
计数信号通常用于两种情况:
-
计算事件:
- 在事件发生时,获取一个计数信号量,计数信号量的值减1。
- 当事件得到处理时,释放一个计数信号量,计数信号量的值加1。
- 通过最大信号量值和当前值得差值就知道当前还有多少个事件没有被处理。
-
资源管理:
- 计数信号量表示当前可用资源值。
- 获取信号量表示需要占用一个资源,信号量值减1。
- 当资源值为0时,表示没有空闲的支援可用。
- 释放信号量表示资源不用了,当前信号量加1.
该图片源自野火
11.4.2 计数信号量运作
和二值信号量类似,只是资源最大值不止1。
11.5 互斥量
11.5.1 互斥量概念
互斥量是包含优先级继承机制的二值信号量。
而二值信号量是实现同步(任务之间或任务与中断之间)的更好选择,互斥量是实现简单互斥的更好选择。
互斥量就像保护资源的令牌一样。
当一个任务希望访问该资源时,它必须首先获得令牌。
当它使用完该资源时,它必须“归还”令牌——允许其他任务访问相同的资源。
互斥锁不应该在中断中使用,因为:
- 它们包括优先级继承机制,这种机制只在互斥锁来自任务时才有意义,而不是在中断时。
- 中断不能阻塞以等待由互斥锁保护的资源变为可用。
11.5.2 优先级继承机制概念
优先级继承:高优先级任务TH在等待低优先级的任务TL继承占用的竞争资源时,为了使TH能够尽快获得调度运行,由操作系统把TL的优先级提高到TH的优先级,从而让TL以TH的优先级参与调度,尽快让TL执行并释放调TH欲获得的竞争资源,然后TL的优先级调整到继承前的水平,此时TH可获得竞争资源而继续执行。
在FreeRTOS操作系统中为了降低优先级翻转问题利用了优先级继承算法。
不过优先级继承也不能解决优先级反转。
它只是在某些情况下将其影响降到最低。
举个栗子:
三个任务:任务A优先级10,任务B优先级5,任务C优先级1。
在任务C占用互斥量时,任务A就绪,也需要该互斥量,此时任务C的优先级会继承任务A的优先级,从优先级1跃升到优先级10。就算当前任务B就绪了,也不能打断任务C,因为优先级比10底。
11.5.3 互斥量运作
和二值信号量类似,比二值信号量多个优先级继承机制。
11.6 递归互斥量
11.6.1 递归互斥量概念
就是互斥量具有递归性。
递归使用的互斥量可以被其所有者反复“获取”。
互斥量只有在所有者为每个成功的xSemaphoreTakeRecursive()
请求调用xSemaphoreGiveRecursive()
之后才会再次可用。
即是互斥量被同一个任务连续申请成功N次,就需要释放N次才算真正释放该互斥量。
互斥量类型的信号量不能在中断服务程序中使用。
因为:
- 互斥量包含了优先级继承机制,只有在互斥锁来自任务而不是中断时才有意义。
- 中断不能阻塞以等待由互斥锁保护的资源变为可用。
11.6.2 递归互斥量运作
参考互斥量运作机制,比互斥量多个递归性。
11.7 死锁概念
就是逻辑上获取一个已经被占用且逻辑上不可能被释放的锁而阻塞,永久阻塞。
避免死锁需要遵循的规则:
- 对共享资源操作前一定要获得锁;
- 完成操作后一定要释放锁;
- 尽量短时间占用锁;
- 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
11.8 创建信号量
11.8.1 创建二值信号量
xSemaphoreCreateBinary()
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
就是创建一个类型queueQUEUE_TYPE_BINARY_SEMAPHORE
、是队列成员为1、不含数据区的队列。
11.8.2 创建计数信号量
xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif
#if ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle = NULL;
if( ( uxMaxCount != 0 ) && /* 最大信号量不能少于1,这是常识 */
( uxInitialCount <= uxMaxCount ) ) /* 初始值也不能超过最大信号量值 */
{
/* 创建一个类型为queueQUEUE_TYPE_COUNTING_SEMAPHORE、队列成员为uxMaxCount、且不含数据区的队列 */
xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
/* 初始化当前可用资源值 */
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
traceCREATE_COUNTING_SEMAPHORE();
}
else
{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
}
else
{
configASSERT( xHandle );
mtCOVERAGE_TEST_MARKER();
}
return xHandle;
}
#endif
11.8.3 创建互斥量
xSemaphoreCreateMutex()
:
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
#if ( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 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;
}
#endif
#if ( configUSE_MUTEXES == 1 )
static void prvInitialiseMutex( Queue_t * pxNewQueue )
{
if( pxNewQueue != NULL )
{
/* 在调用xQueueGenericCreate()创建队列的时候,默认都是队列,联合体初始化的是QueuePointers_t xQueue。
所以需要在这里初始化回SemaphoreData_t xSemaphore,这个成员非常重要,是实现优先级继承机制和递归互斥量的必要数据。
用于互斥量。 */
/* 互斥量持有者。初始化为NULL */
pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
/* 类型标记为互斥量 */
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* 递归互斥量使用,初始为0 */
pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
/* 创建后,默认为开锁状态 */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
#endif /* configUSE_MUTEXES */
11.8.4 创建递归互斥量
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) )
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
#endif
11.8.5 信号量控制块数据结构图
11.8.6 互斥量控制块数据结构图
11.9 获取信号量
获取和释放信号量都是区分任务和中断调用的API的,其主要区别也是中断调用是不能阻塞。
这里主要分析任务调用。(中断调用,按区别理解下就可以了)
二值信号量、计数信号量、互斥量都是使用xSemaphoreTake()
获取信号量。
而递归互斥量使用xSemaphoreTakeRecursive()
获取互斥量。
11.9.1 xSemaphoreTake()
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
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
/* 参数校验 */
configASSERT( ( pxQueue ) );
/* 信号量是不带数据区的 */
configASSERT( pxQueue->uxItemSize == 0 );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
/* 调度器挂起是不能以阻塞式调用 */
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/* 循环方式。是实现阻塞机制的逻辑方式 */
for( ; ; )
{
taskENTER_CRITICAL(); /* 进入临界 */
{
/* 备份下当前信号量值 */
const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
/* 信号量大于0,说明还有资源,可以被获取占用 */
if( uxSemaphoreCount > ( UBaseType_t ) 0 )
{
traceQUEUE_RECEIVE( pxQueue );
/* 信号量减1 */
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) /* 互斥量类型 */
{
/* 保存当前互斥量持有者。且持有者也保存占用互斥量数。 */
pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
/* 如果有任务阻塞在当前信号量的获取阻塞链表中,就解锁一个,让其写入。 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
/* 把这个解除阻塞的任务从当前队列的写阻塞链表中解除,
并把该任务从延时链表或挂起链表中恢复到就绪链表或挂起的就绪链表中 */
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
/* 解锁的任务比当前任务优先级更加高,需要触发任务调度。 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 退出临界 */
taskEXIT_CRITICAL();
return pdPASS; /* 返回获取成功 */
}
else /* 如果信号量为空,没有可用资源。互斥量的话需要处理优先级继承机制。 */
{
if( xTicksToWait == ( TickType_t ) 0 ) /* 不阻塞 */
{
#if ( configUSE_MUTEXES == 1 )
{
/* 参数校验。不阻塞是不会因当前获取而发生优先级继承的。 */
configASSERT( xInheritanceOccurred == pdFALSE );
}
#endif /* configUSE_MUTEXES */
/* 退出临界,返回获取失败。 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE ) /* 首次循环,需要开始计时阻塞 */
{
/* 获取当前系统节拍 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE; /* 标记已经开始计时 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
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(); /* 退出临界 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* if ( configUSE_MUTEXES == 1 ) */
/* 当前任务进入阻塞,从就绪链表中抽离,插入到延时链表中,并把该任务插入当前信号量的获取阻塞链表中 */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue ); /* 就锁队列 */
if( xTaskResumeAll() == pdFALSE ) /* 恢复调度器 */
{
/* 如果在恢复调度器时没有调度过,这里必须手动触发一次调度。 */
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
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; /* 返回获取失败 */
}
else /* 在超时了,退出前发现有资源,可以进入下一个循环获取 */
{
mtCOVERAGE_TEST_MARKER();
}
}
} /*lint -restore */
}
11.9.2 xSemaphoreTakeRecursive()
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
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() ) /* 如果递归互斥量已经被占用了,持有者是当前任务的话,可以获取递归互斥量成功 */
{
/* 递归深度加1 */
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
xReturn = pdPASS; /* 返回获取成功 */
}
else /* 递归互斥量没有被占用,或者递归互斥量已经被占用,但是持有者不是当前任务 */
{
/* 获取互斥量处理 */
xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );
if( xReturn != pdFAIL ) /* 获取成功 */
{
/* 递归深度加1 */
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
}
else
{
traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
return xReturn;
}
#endif
11.10 释放信号量
获取和释放信号量都是区分任务和中断调用的API的,其主要区别也是中断调用是不能阻塞。
这里主要分析任务调用。(中断调用,按区别理解下就可以了)
二值信号量、计数信号量、互斥量都是使用xSemaphoreGive()
获取信号量。
而递归互斥量使用xSemaphoreGiveRecursive()
获取互斥量。
注意:互斥量和递归互斥量只有持有者才有权限释放。
11.10.1 xSemaphoreGive()
参考消息队列章节。
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
11.10.2 xSemaphoreGiveRecursive()
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
#endif
#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 );
/* 递归深度建1 */
( pxMutex->u.xSemaphore.uxRecursiveCallCount )--;
/* 递归深度为0,说明已经被完全释放了 */
if( pxMutex->u.xSemaphore.uxRecursiveCallCount == ( UBaseType_t ) 0 )
{
/* 需要真正释放这个递归互斥量 */
( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 返回成功 */
xReturn = pdPASS;
}
else /* 递归互斥量没有被占用,或者递归互斥量已经被占用,但是持有者不是当前任务 */
{
/* 返回释放失败 */
xReturn = pdFAIL;
traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
return xReturn;
}
#endif
11.11 删除信号量
vSemaphoreDelete()
用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。
#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
11.12 优先级继承机制主要源码
注意:当持有者持有多个互斥量时,不能通过单个互斥量来解除或者重置优先级继承的优先级,只能选择忽略。这种情况也会存在优先级翻转。
优先级继承机制主要数据:Queue_t->u->xQueue
。
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /* 当前互斥量的持有者 */
UBaseType_t uxRecursiveCallCount; /* 当前互斥量被递归调用的深度 */
} SemaphoreData_t;
11.12.1 优先级继承
在互斥量被其它任务占用,当前高优先级任务因为该互斥量而进入阻塞时,会发生优先继承,互斥量持有者的任务优先级会跃升到阻塞在当前接收阻塞链表中最高,且比持有者高的任务优先级。
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 )
{
/* 持有者的事件节点值没有被其它IPC占用(如事件组组件),方可设置为优先级相关的值 */
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 );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果持有者处于就绪态,则需要将其移到新的就绪链表中 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) /* 解除任务状态 */
{
/* 更新任务优先级位图 */
portRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 优先级继承:更新优先级 */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
/* 重新插入对应就绪链表 */
prvAddTaskToReadyList( pxMutexHolderTCB );
}
else /* 持有者不在就绪态 */
{
/* 直接更新优先级即可 */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
}
traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );
/* 继承成功 */
xReturn = pdTRUE;
}
else /* */
{
if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
{
/* 持有者当前优先级比当前任务高,但是持有者基优先级比当前任务优先级低,也是算是继承成功过 */
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
#endif /* configUSE_MUTEXES */
11.12.2 解除优先级继承
互斥量持有者真正释放互斥量时,方可解除优先级继承
正常由持有者解除:xTaskPriorityDisinherit()
:
#if ( configUSE_MUTEXES == 1 )
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 );
( pxTCB->uxMutexesHeld )--;
/* 继承判断 */
if( pxTCB->uxPriority != pxTCB->uxBasePriority ) /* 继承过 */
{
/* 只有当持有者这个任务不再持有任何互斥量时,才能解除优先级继承。
因为只解除当前的互斥量,但是当前优先级继承可能继承的是其它互斥量的,所以不能直接解除。 */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ) /* 持有者不再占有任何互斥量 */
{
/* 解除任务状态 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 更新优先级位图 */
portRESET_READY_PRIORITY( pxTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
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;
}
#endif /* configUSE_MUTEXES */
11.12.3 重置优先级继承
获取互斥量阻塞阻塞超时时会检查重置优先级继承。
高优先级任务阻塞超时而解除:
- 这种情况不是解除阻塞,而是重置阻塞。
- 获取获取互斥量阻塞链表任务中的最高优先级:
prvGetDisinheritPriorityAfterTimeout()
- 重置优先级继承:
vTaskPriorityDisinheritAfterTimeout()
prvGetDisinheritPriorityAfterTimeout()
:
#if ( configUSE_MUTEXES == 1 )
static UBaseType_t prvGetDisinheritPriorityAfterTimeout( const Queue_t * const pxQueue )
{
UBaseType_t uxHighestPriorityOfWaitingTasks;
/* 当前互斥量中有任务阻塞在获取互斥量阻塞链表中 */
if( listCURRENT_LIST_LENGTH( &( pxQueue->xTasksWaitingToReceive ) ) > 0U )
{
/* 获取获取互斥量阻塞链表任务中的最高优先级 */
uxHighestPriorityOfWaitingTasks = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) listGET_ITEM_VALUE_OF_HEAD_ENTRY( &( pxQueue->xTasksWaitingToReceive ) );
}
else /* 当前互斥量没有阻塞获取的任务 */
{
uxHighestPriorityOfWaitingTasks = tskIDLE_PRIORITY; /* 最低 */
}
return uxHighestPriorityOfWaitingTasks;
}
#endif /* configUSE_MUTEXES */
vTaskPriorityDisinheritAfterTimeout()
:
- 只有持有者只持有当前互斥量才能重置优先级继承,因为如果持有者持有多个互斥量时,并不能只参考当前互斥量来重置优先级。
#if ( configUSE_MUTEXES == 1 )
void vTaskPriorityDisinheritAfterTimeout( TaskHandle_t const pxMutexHolder,
UBaseType_t uxHighestPriorityWaitingTask )
{
TCB_t * const pxTCB = pxMutexHolder;
UBaseType_t uxPriorityUsedOnEntry, uxPriorityToUse;
const UBaseType_t uxOnlyOneMutexHeld = ( UBaseType_t ) 1;
if( pxMutexHolder != NULL )
{
/* 参数校验,持有者必须持有互斥量 */
configASSERT( pxTCB->uxMutexesHeld );
/* 当前互斥量持有者基优先级低于当前互斥量中阻塞获取链表任务中的最高优先级 */
if( pxTCB->uxBasePriority < uxHighestPriorityWaitingTask )
{
uxPriorityToUse = uxHighestPriorityWaitingTask; /* 下一个继承的优先级 */
}
else
{
uxPriorityToUse = pxTCB->uxBasePriority; /* 需要解除继承 */
}
/* 目标优先级和当前优先级不一致,需要重置优先级 */
if( pxTCB->uxPriority != uxPriorityToUse )
{
/* 只有持有者只持有当前互斥量才能重置优先级继承,因为如果持有者持有多个互斥量时,并不能只参考当前互斥量来重置优先级 */
if( pxTCB->uxMutexesHeld == uxOnlyOneMutexHeld )
{
/* 确保持有者不是当前任务。否则可能就是一个死锁的逻辑。断言得了 */
configASSERT( pxTCB != pxCurrentTCB );
traceTASK_PRIORITY_DISINHERIT( pxTCB, uxPriorityToUse );
/* 备份持有者优先级 */
uxPriorityUsedOnEntry = pxTCB->uxPriority;
/* 更新持有者优先级 */
pxTCB->uxPriority = uxPriorityToUse;
/* 持有者的事件节点值没有被其它IPC占用(如事件组组件),方可设置为优先级相关的值 */
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 ) uxPriorityToUse );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果持有者处于就绪态,就需要更新就绪链表 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) /* 解除任务状态 */
{
/* 更新就绪任务位图 */
portRESET_READY_PRIORITY( pxTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 重新插入就绪链表 */
prvAddTaskToReadyList( pxTCB );
}
else /* 不是就绪态就不用管 */
{
mtCOVERAGE_TEST_MARKER();
}
}
else /* 持有者持有多个互斥量,也不需要重置优先级继承 */
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */