FreeRTOS的学习系列文章目录
FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知
前言
信号量可以认为是队列的一种表达形式,他的存在给予了任务和任务,任务和中断之间的资源访问形式。
- 信号量常被用于控制对共享资源的访问和任务同步,可以对资源的变化进行计数,或者判断是否使用某资源等。
- 另外信号量还常用于任务同步,用于任务于任务或者中断与任务之间的同步。
FreeRTOS中具有非常多的信号量,比如计数信号量,二值信号量,互斥信号量和递归互斥信号量,其存在在某些情况下亦可以用队列的功能去替换。
1 互斥信号量简介
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。
互斥信号量使用和二值信号量相同的API操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的优先级翻转的影响降到最低。此时的低优先级和高优先级就是两个互斥应用,一个应用在资源使用完后就会归于另一个应用使用。
但是值得注意的是,优先级集成并不能完全的消除优先级翻转,他只是尽可能地降低优先级翻转带来的影响。硬实时(实时性要求很高)应用在设计时应尽可能避免优先级翻转的发生。
另外,互斥信号量不能用于中断服务中:
- 互斥信号量优先级继承机制,只能用在任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
2 互斥信号量的创建
互斥信号量的创建函数如下:
- xSemaphoreCreateMutex(),使用动态方法创建互斥信号量。
- xSemaphoreCreateMutexStatic(),使用静态方法创建互斥信号量
函数的定义(动态)如下:
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
可以发现,真正干事的是xQueueCreateMutex函数,该函数在queue中定义如下:
#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 /* configUSE_MUTEXES */
该函数通过xQueueGenericCreate函数创建了互斥信号量,并且在创建之后利用prvInitialiseMutex函数对互斥信号量进行了初始化,初始化函数中,除了对一些互斥信号量的类型做了赋值,还调用了xQueueGenericSend函数进行了信号量释放(也就是创建后就默认有效)。所以这与二值信号量也有所不同,二值信号量在创建好后,需要另外再将信号量进行释放。这点不同与两种信号量的应用场景不同也存在很大关系。相关初始化函数如下:
#if ( configUSE_MUTEXES == 1 )
static void prvInitialiseMutex( Queue_t * pxNewQueue )
{
if( pxNewQueue != NULL )
{
pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
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 */
3 互斥信号量的释放和获取
3.1 互斥信号量的释放
释放互斥信号量的时候和二值信号量、计数型信号量一样,都是用的函数xSemaphoreGive()(实际上完成信号量释放的是函数xQueueGenericSend())。不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。
在xQueueGenericSend函数中,释放信号量时会调用prvCopyDataToQueue。同样的,互斥信号量也有这样的操作,其中互斥信号量的优先级集成也是在该函数中完成的。截取部分关键代码如下:
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#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;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
- 如果互斥信号量的宏定义有效,则执行if语句中的代码。
- 如果当前信号量为互斥信号量,则执行if语句中的代码。
- 调用函数 xTaskPriorityDisinherit()处理互斥信号量的优先级继承问题。
- 互斥信号量释放以后,互斥信号量就不属于任何任务了,所以 pxMutexHolder 要指向
NULL。
接下来再看一下xTaskPriorityDisinherit是怎么具体的处理优先级继承的。代码如下:
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL )
{
//当一个任务获取到互斥信号量以后就会涉及到优先级继承的问题,正在释放互斥
//信号量的任务肯定是当前正在运行的任务 pxCurrentTCB。
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;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
- 函数的参数 pxMutexHolder 表示拥有此互斥信号量任务控制块,所以要先判断此互斥信号量是否已经被其他任务获取。
- 有的任务可能会获取多个互斥信号量,所以就需要标记任务当前获取到的互斥信号量个数,任务控制块结构体的成员变量 uxMutexesHeld 用来保存当前任务获取到的互斥信号量个数。任务每释放一次互斥信号量,变量 uxMutexesHeld 肯定就要减一。
- 判断是否存在优先级继承,如果存在的话任务的当前优先级肯定不等于任务的基优先级。
- 判断当前释放的是不是任务所获取到的最后一个互斥信号量,因为如果任务还获取了其他互斥信号量的话就不能处理优先级继承。优先级继承的处理必须是在释放最后一个互斥信号量的时候。
- 优先级继承的处理就是将任务的当前优先级降低到任务的基优先级,所以要把当前任务先从任务就绪表中移除。当任务优先级恢复为原来的优先级以后再重新加入到就绪表中。
- 如果任务继承来的这个优先级对应的就绪表中没有其他任务的话就将取消这个优先级的就绪态。
- 重新设置任务的优先级为任务的基优先级 uxBasePriority。
- 复位任务的事件列表项。
- 将优先级恢复后的任务重新添加到任务就绪表中。
- 返回 pdTRUE,表示需要进行任务调度。
3.2 互斥信号量的获取
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是xSemaphoreTake(实际执行信号量获取的函数是 xQueueSemaphoreTake),获取互斥信号量的过程也需要处理优先级继承的问题。与释放的函数相似的,xQueueSemaphoreTake中包含了处理互斥信号量的情况:
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
上述函数在获取互斥信号量成功后,需要标记互斥信号量的所有者,也就是给pxMutexHolder 赋值,pxMutexHolder 应 该 是 当 前 任 务 的 任 务 控 制 块 。 但 是 这 里 是 通 过 函数pvTaskIncrementMutexHeldCount来赋值的,此函数很简单,只是将任务控制块中的成员变量uxMutexesHeld加一,表示任务获取到了一个互斥信号量,最后此函数返回当前任务的任务控制块。
xQueueSemaphoreTake函数在处理好超时结构体时,会对互斥信号量在进行一次处理。
#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 ) */
调用函数vTaskPriorityInherit处理互斥信号量中的优先级继承问题,如果获取了互斥信号量的话,函数执行到此处,说明互斥信号量正在被其他任务占用。此函数与上述的xTaskPriorityDisinherit正好相反,此函数会判断当前任务的额任务优先级是否比正在拥有互斥信号量的那个任务的任务优先级高,如果是的话就会把拥有互斥信号量的那个低优先级任务的优先级调整为与当前任务相同的优先级!
在该函数中,有这么一个语句:pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
这里就不再详细说明该函数了。
4 总结
对于互斥信号量的获取函数中对于互斥信号量特别的处理可以理解为两部分:
- 该任务使用的互斥信号量,其他任务也需要使用,但是此时该任务使用互斥信号量是ok的,那么这种情况就是互斥信号量有效的情况,此时( pxCurrentTCB->uxMutexesHeld )++,并且会返回当前任务控制块给pxQueue->u.xSemaphore.xMutexHolder。
- 该任务使用的互斥信号量,其他任务也需要使用,但是此时该任务无法获取到要使用的互斥信号量,也就是互斥信号量被其他优先级的任务占用时,那么这种情况就是互斥信号量无效的情况,就会使用优先级继承了。通过xTaskPriorityInherit函数了,通过pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority,也就是将当前任务的优先级传递给任务想使用却被占用着的信号量的当前使用者(任务),从而实现了优先级继承。当然,前提是拥有互斥信号量的任务优先级比当前任务的优先级低才会操作,否则就没有必要了。
有了获取,自然就要释放,可以想象,释放函数的话势必是要把继承过的优先级恢复到原来那样才行。
对于互斥信号量的释放函数中对于互斥信号量特别的处理可以理解接下来几个步骤:
- 首先在prvCopyDataToQueue函数中存在相关互斥信号量的处理,采用xTaskPriorityDisinherit函数取消优先级继承,然后将pxQueue->u.xSemaphore.xMutexHolder = NULL;表示当前任务不在持有该互斥信号量。
- xTaskPriorityDisinherit函数首先会将( pxTCB->uxMutexesHeld )–表示该任务释放了某一个互斥信号量(每个任务都可能存在多个互斥信号量,这与获取的相关处理形成对比)。
- 如果当前任务的优先级不是创建任务时的基优先级大小,说明该任务已经进行过优先级继承了。需要取消之前的优先级继承操作。
- 取消优先级继承的操作,包括了取消继承得到的优先级任务列表的就绪态(如果继承来的优先级的列表中没有其他同优先级的任务),在就绪列表中删除该任务,将基优先级重新赋给该任务后,再重新添加进就绪列表,以及重新添加进事件列表。
值得注意的是,对于优先级继承的处理都需要判断是否是最后一个互斥信号量,这样才能保证任务的稳定性。
最后关于递归互斥信号量的内容不做过多的讲解了,递归互斥信号量相比于普通的互斥信号量是可以多次获取同一个互斥信号量的,不过多次获取后需要多次释放。