FreeRTOS 学习仓库:https://gitee.com/killerp/free-rtos_-study
在深入理解了消息队列后,信号量也就很容易学习了。因为信号量就是使用消息队列实现的。
信号量是特殊的消息队列,其只部分利用了队列结构体,信号量没有队列存储区域,所以信号量不能用来传递任务间的数据。但可以利用消息队列的其他特点如:对共享数据的保护,阻塞等待机制等,实现任务之间的同步,对共享数据的互斥访问。
不同类型的信号量有各自的用途。
二值信号量
二值信号量只有 0 和 1 两个状态,通常用于任务之间、任务和中断之间的同步。二值信号量初始化时为0,当任务or中断释放二值信号量,信号量值为1,接收任务解除阻塞进入就绪。通常是一方固定释放信号量,一方固定读取信号量,实现双方的同步。
互斥信号量
互斥信号量主要用于对共享数据的互斥访问,也就是保证任务对资源的独占性,在同一时刻,一个共享数据只能被一个任务访问。
所以互斥信号量在初始化时,需要初始化为1,表示资源的存在。
任务获取互斥信号信号成功后,就能放心地操作资源。其他任务获取互斥信号就会失败,而进入阻塞。通常情况下,多个任务访问一个共享资源,每个任务都需要先获取互斥信号量,在处理完数据后,释放互斥信号量。这与二值信号量不一样。
递归互斥信号量
递归互斥信号量是在互斥信号的基础上,允许拥有信号量的任务递归地获取/释放信号量。
计数信号量
计数信号量类似于互斥信号量,用于对资源的保护,通常该资源可被若干个任务同时拥有。
信号量的实现依赖于队列,在FreeRTOS中,仅用一个semphr.h头文件,通过宏定义来实现信号量的创建,发送,接收。
创建二值信号量 实际上就是创建一个长度为1,大小为0的队列 ,此时信号量的值为0
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
首先了解简单的优先级反转:
A的优先级为1,B为10,C为5,A获取到信号量,正在操作资源。此时中断导致任务调度,切换到B执行,B因为请求信号量失败而进入阻塞。此时发生任务调度,C运行知道让出CPU,A继续运行释放信号量,最后B才能继续运行。
假设没有C任务,那么A、B的运行是正常的。A首先获取资源,把资源处理完成后再由B下一步处理,这种情况下,A、B执行的时间是可确定的,符合实时操作系统的要求。
但是第三者C的加入,使得任务A、B的执行时间不确定了。C有时会抢占A的CPU,使A、B的执行时间变得长;有时又不止一个C、可能有其他D、E、F、G来抢占A,所以A、B的执行时间变得不可确定,这对实时操作系统是灾难性的。
解决的办法是使用优先级继承:即把B的优先级暂时复制给A,这样C就无法插足A、B之间的感情了。需要注意的是,任务可能发生多次的优先级反转,所以优先级继承一定要继承最高优先级。
由于互斥信号量的优先级继承是发生在信号量获取的过程中,所以获取信号量的实现需要关注优先级继承逻辑。代码如下
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
BaseType_t xInheritanceOccurred = pdFALSE; //优先级继承是否发送的标志
configASSERT( ( pxQueue ) );
//uxItemSize为0时,队列才表示为信号量
configASSERT( pxQueue->uxItemSize == 0 );
//进入循环
for( ; ; )
{
//进入临界区
taskENTER_CRITICAL();
{
//uxMessagesWaiting 就是信号量的值
const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
//检查信号量是否有效
if( uxSemaphoreCount > ( UBaseType_t ) 0 )
{
traceQUEUE_RECEIVE( pxQueue );
//信号量有效、数值减一
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
{
//若是互斥信号量,还需要做优先级继承
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
//记录当前信号量的拥有者
pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//检测是否有任务阻塞在信号量释放队列,若有则恢复任务
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 )
{
{
//若优先级反转已经发生,那么调用者必须有一个阻塞时间,故此时xInheritanceOccurred必须为false
configASSERT( xInheritanceOccurred == pdFALSE );
}
//不需要等待直接退出
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
//初始化超时结构体
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue ); //锁定队列,因为需要操作链表
//超时时间未到
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
//信号量为空
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
{
//若是互斥信号量、在进入阻塞前、需要做一次优先级继承
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//将当前任务挂起到等待链表
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue ); //解锁队列
//当前任务进入等待、恢复任务调度,切换下一个任务运行
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
//超时时间未到且信号量有效了,重新进入循环获取信号量,这种情况可能是其他任务释放了信号量从而唤醒当前任务
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else //阻塞时间到了 此时任务已被内核移出等待队列
{
/* Timed out. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
//若信号量仍然为空,则退出阻塞
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
{
//若发生了优先级反转,需要恢复任务的优先级
if( xInheritanceOccurred != pdFALSE )
{
taskENTER_CRITICAL();
{
UBaseType_t uxHighestWaitingPriority;
//获取队列等待链表中的最高优先级、
uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
//恢复队列的优先级为该最高优先级or任务自身优先级
vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
}
taskEXIT_CRITICAL();
}
}
traceQUEUE_RECEIVE_FAILED( pxQueue );
//返回获取失败
return errQUEUE_EMPTY;
}
else //阻塞时间到了,但同时信号量也有效,重新进入循环获取信号量
{
mtCOVERAGE_TEST_MARKER();
}
}
} /*lint -restore */
}
释放信号量就是向队列发送一个空消息、且不需要进入阻塞,队列满说明信号量有效,所以不需要阻塞。同时若是互斥信号量,则任务会恢复到原来的优先级。
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
在中断中释放信号量与任务中的区别是:进入临界区需要保存中断状态,以及在中断中不能进入阻塞,且队列锁定时不能操作队列的链表。
BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue,
BaseType_t * const pxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus; //保存中断状态
Queue_t * const pxQueue = xQueue;
configASSERT( pxQueue );
//用于信号量的释放,故为0
configASSERT( pxQueue->uxItemSize == 0 );
//通常一个互斥信号量不会在中断中释放、而是是任务中。因为互斥信号量是资源的锁,在中断中一般不会访问共享的资源。
configASSERT( !( ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) && ( pxQueue->u.xSemaphore.xMutexHolder != NULL ) ) );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
//进入临界区,保存中断状态
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
//检测是否能继续释放信号量
if( uxMessagesWaiting < pxQueue->uxLength )
{
const int8_t cTxLock = pxQueue->cTxLock;
traceQUEUE_SEND_FROM_ISR( pxQueue );
//信号量值+1
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
//队列未锁,可以操作链表
if( cTxLock == queueUNLOCKED )
{
{
//将等待信号量的任务恢复就绪
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
//需要退出中断后进行任务调度
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
configASSERT( cTxLock != queueINT8_MAX );
//队列锁定 标记
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
}
xReturn = pdPASS;
}
else //队列满,无法释放信号量
{
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
xReturn = errQUEUE_FULL;
}
}
//退出临界区 恢复中断状态
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
a、优先级继承
任务获取互斥信号量时,进入阻塞,需要优先级继承,信号量的拥有者继承当前任务的优先级(若当前任务优先级较高)。
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);
}
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;
}
b、重置优先级
当等待获取互斥信号的任务阻塞时间到达,需要退出等待时,调用以下函数,重新设置信号量拥有者的优先级。
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); //必须拥有锁
//uxPriorityToUse是新的优先级。新的优先级应该是等待锁的任务中优先级最高的那个,这样才能保证最高优先级的任务能在锁释放后第一时间得到运行
if (pxTCB->uxBasePriority < uxHighestPriorityWaitingTask)
{
uxPriorityToUse = uxHighestPriorityWaitingTask;
}
else
{
uxPriorityToUse = pxTCB->uxBasePriority;
}
/* Does the priority need to change? */
//优先级需要修改
if (pxTCB->uxPriority != uxPriorityToUse)
{
if (pxTCB->uxMutexesHeld == uxOnlyOneMutexHeld)
{
configASSERT(pxTCB != pxCurrentTCB);
traceTASK_PRIORITY_DISINHERIT(pxTCB, uxPriorityToUse);
//保存旧优先级
uxPriorityUsedOnEntry = pxTCB->uxPriority;
//设置新的优先级
pxTCB->uxPriority = uxPriorityToUse;
//设置事件节点值
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();
}
}
c、恢复优先级
任务释放互斥信号,自己将恢复到原始的任务优先级。
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)--; //拥有互斥信号的数量-1
//优先级不同则出现了优先级反转
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);
/* 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. */
//todo 返回真使任务调度
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}