目录
1. 信号量概述
1.1 信号量概念
1.2 4种信号量
1.2.1 二值信号量
1.2.2 计数信号量
1.2.3 互斥信号量
1.2.4 递归互斥信号量
1.3 信号量相关控制结构
1.3.1 队列结构
1.3.2 任务结构
2. 二值信号量
2.1 创建二值信号量
2.2 获取二值信号量
2.2.1 任务级获取
2.2.2 中断级获取
2.3 释放二值信号量
2.3.1 任务级释放
2.3.2 中断级释放
3. 计数信号量
3.1 创建计数信号量
3.2 获取计数信号量
3.3 释放计数信号量
4. 互斥信号量
4.1 优先级翻转
4.1.1 优先级翻转现象
4.1.2 优先级继承原理
4.2 创建互斥信号量
4.3 获取互斥信号量
4.4 释放互斥信号量
5. 递归互斥信号量
5.1 创建递归互斥信号量
5.2 获取递归互斥信号量
5.3 释放递归互斥信号量
1. 信号量概述
1.1 信号量概念
① 信号量是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问
② 从本质上说,信号量是一个非负整数值,该数值对应有效的资源数量。所有成功获取信号量的任务都会将该整数减1,当整数值为0时,所有试图获取信号量的任务都会获取失败或进入阻塞状态
1.2 4种信号量
1.2.1 二值信号量
① 二值信号量通常用于互斥访问或同步,但是由于不具备优先级继承功能,所以更适合用于任务间或任务与中断间的同步
② 二值信号量在实现上是一个长度为1的队列,且队列项长度为0。当前队列中的消息数量,就标识了信号量的数量
说明:基于消息队列实现信号量,可以减小代码体积
1.2.2 计数信号量
① 计数信号量在实现上是一个长度可以不为1的队列,且队列项长度为0
② 在创建计数信号量时,可以同时指定信号量初始值
③ 计数信号量通常用于如下2个场景,
a. 事件计数
事件每发生一次就释放一次信号量,此时计数信号量的初始值应为0
b. 资源管理
信号量值标识当前可用资源个数,使用资源的任务需要先获取信号量,使用完成后释放信号量,此时计数信号量的初始值应为资源总数
1.2.3 互斥信号量
① 互斥信号量是拥有优先级继承机制的二值信号量
② 因为拥有优先级继承机制,互斥信号量更适合用于资源互斥访问
③ 互斥信号量不能用于中断服务函数中
1.2.4 递归互斥信号量
① 递归互斥信号量在互斥信号量的基础上,增加了递归持有特性,即持有互斥递归信号量的任务可以再次获取该信号量
② 递归释放的次数要与递归获取的次数相匹配
③ 互斥信号量也不能用于中断服务函数中
说明:互斥信号量与递归互斥信号量不能用于ISR中,是因为ISR中没有任务优先级的概念,无法正确处理优先级继承,详见后文
1.3 信号量相关控制结构
1.3.1 队列结构
① pcHead
在互斥信号量 & 递归互斥信号量中,被宏定义为uxQueueType,并被初始化为queueQUEUE_IS_MUTEX(NULL),用于标识当前队列类型
说明:为什么不通过ucQueueType判断队列类型呢 ?
ucQueueType成员被包含在configUSE_TRACE_FACILITY条件编译选项中,而该编译选项是不一定被开启的
② pcTail
在互斥信号量 & 递归互斥信号量中,被宏定义为pxMutexHolder,标识当前持有信号量的任务。初始值为NULL,即没有被任务持有
③ uxRecursiveCallCount
在递归互斥信号量中,标识当前信号量被递归获取的次数
④ uxMessagesWaiting
当前队列中的消息数量,在信号量中,就是当前可用的信号量数量
⑤ uxLength
队列长度,在信号量中,就是当前信号量的最大个数
⑥ uxItemSize
队列项长度,在信号量中,队列项长度必须为0
1.3.2 任务结构
在TCB_t结构中,如下2个字段与信号量有关,
① uxBasePriority
在互斥 & 递归互斥信号量中,记录任务的原始优先级,用于实现优先级继承机制
② uxMutexesHeld
在递归 & 互斥递归信号量中,记录该任务持有的递归 & 互斥递归信号量总数,只有当该计数减少到0时,才会恢复该任务的优先级
2. 二值信号量
2.1 创建二值信号量
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE )
创建二值信号量,就是创建队列长度为1,队列项长度为0,类型为queueQUEUE_TYPE_BINARY_SEMAPHORE的队列
说明1:函数返回值
由于信号量是基于队列实现的,所以实际返回的是队列句柄。但是在semphr.h头文件中,将队列句柄类型重定义为信号量句柄,所以函数返回值类型为SemaphoreHandle_t
说明2:对应静态创建版本
与队列类似,信号量的创建也可以使用静态分配的内存,函数如下,
#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore ) \
xQueueGenericCreateStatic( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, \
queueQUEUE_TYPE_BINARY_SEMAPHORE )
此时需要传递一个指向StaticSemaphore_t类型的变量的地址
说明3:旧版本创建二值信号量函数
#define vSemaphoreCreateBinary( xSemaphore ) \
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) );\
} \
}
旧版本创建二值信号量函数与新版本有2处不同,
① 调用vSemaphoreCreateBinary时,需要将信号量句柄作为参数传递给函数
② 旧版本在创建完信号量之后会释放一次,也就是旧版本初始状态的二值信号量是可以获取的,而新版本初始状态的二值信号量是不可获取的
这点修改主要是作者为二值信号量预设的使用场景就是任务之间或任务和中断之间的同步
说明4:删除信号量
#define vSemaphoreDelete( xSemaphore ) \
vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
对于各种信号量,删除信号量操作都是直接调用vQueueDelete函数删除队列。由于在FreeRTOS中,并不提倡不断动态创建 & 删除内核对象,所以删除队列的实现也是很简陋的(详见消息队列章节笔记)
2.2 获取二值信号量
2.2.1 任务级获取
#define xSemaphoreTake( xSemaphore, xBlockTime ) \
xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), \
NULL, ( xBlockTime ), pdFALSE )
任务级获取二值信号量,就是任务级获取消息
2.2.2 中断级获取
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) \
xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, \
( pxHigherPriorityTaskWoken ) )
中断级获取二值信号量,就是中断级获取消息
说明:从实现中可知,在获取信号量时,仅关心消息(也就是信号量)的个数,而不是实际的消息内容
2.3 释放二值信号量
2.3.1 任务级释放
#define semGIVE_BLOCK_TIME ( ( TickType_t ) 0U )
#define xSemaphoreGive( xSemaphore ) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, \
semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
任务级释放二值信号量,就是任务级发送消息,其中发送等待时间为0
2.3.2 中断级释放
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) \
xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), \
( pxHigherPriorityTaskWoken ) )
中断级释放二值信号量,就是中断级发送消息
注意:在中断中,只能使用二值信号量和计数信号量,不能使用互斥 & 递归互斥信号量
3. 计数信号量
3.1 创建计数信号量
// uxMaxCount:计数信号量最大值
// uxInitialCount:计数信号量初始值
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \
xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
// 创建队列长度为uxMaxCount,队列项长度为0的队列
xHandle = xQueueGenericCreate( uxMaxCount,
queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
// 如果队列创建成功,设置计数信号量初始值
// 也就是当前队列中的可用消息数量
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
}
return xHandle;
}
说明:创建计数信号量也有相应的静态内存版本
#define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, \
pxSemaphoreBuffer ) \
xQueueCreateCountingSemaphoreStatic( ( uxMaxCount ), ( uxInitialCount ), \
( pxSemaphoreBuffer ) )
3.2 获取计数信号量
与二值信号量相同
3.3 释放计数信号量
与二值信号量相同
4. 互斥信号量
4.1 优先级翻转
4.1.1 优先级翻转现象
① 导致优先级翻转的本质原因是不同优先级的任务需要获取相同的二值信号量
② 如果低优先级的L任务已经获取了二值信号量,那么高优先级的任务H获取二值信号量将失败,进而被阻塞
其实此时已经发生了优先级翻转,也就是低优先级任务L会在高优先级任务H之前运行
③ 如果有一个中等优先级且不需要获取该二值信号量的任务M,则会恶化优先级翻转的情况
由于任务M不需要持有该信号量,且可以抢占任务L,则会导致高优先级任务H在中低优先级任务M和L之后运行
4.1.2 优先级继承原理
① 当高优先级任务H想要获取互斥信号量,并且判断出信号量目前被更低优先级的任务L占有时,则暂时将任务L的优先级提升到与任务H优先级相同
此时,中等优先级的任务M将无法抢占原先的低优先级任务L,这样就可以确保任务L尽快完成并释放信号量
② 当任务L释放互斥信号量时,将任务L的优先级还原
说明1:优先级继承机制只能缓解优先级翻转现象
根据上述分析,即使拥有优先级继承机制,高优先级任务H也必须等待低优先级任务L运行完成并释放互斥信号量
说明2:由于RTOS中需要避免优先级翻转,因此在系统设计时就要考虑可能导致优先级翻转的场景,避免不同优先级的任务持有同一个互斥信号量
4.2 创建互斥信号量
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1,
uxMutexSize = ( UBaseType_t ) 0;
// 创建队列长度为1,队列项长度为0的队列
pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength,
uxMutexSize, ucQueueType );
prvInitialiseMutex( pxNewQueue );
return pxNewQueue;
}
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
// 初始化互斥 & 递归互斥信号量的持有者与类型
pxNewQueue->pxMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
// 递归互斥信号量在该成员上进行递归获取计数
pxNewQueue->u.uxRecursiveCallCount = 0;
// 释放一次互斥信号量
// 互斥信号量的初始状态为可用
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U,
queueSEND_TO_BACK );
}
}
4.3 获取互斥信号量
获取互斥信号量仍然使用xSemaphoreTake函数,要点在于处理优先级继承(实际处理在xQueueGenericReceive函数中),这里分2种情况讨论,
① 可以获取互斥信号量
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
// 设置互斥 & 递归互斥信号量的持有者
pxQueue->pxMutexHolder =
( int8_t * ) pvTaskIncrementMutexHeldCount();
}
}
#endif /* configUSE_MUTEXES */
// 增加当前任务获取互斥 & 递归互斥信号量的个数
// 并返回当前任务句柄
void *pvTaskIncrementMutexHeldCount( void )
{
if( pxCurrentTCB != NULL )
{
( pxCurrentTCB->uxMutexesHeld )++;
}
return pxCurrentTCB;
}
② 不可获取互斥信号量
#if ( configUSE_MUTEXES == 1 ) // 获取不到互斥信号量的分支
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
// 进入临界段
taskENTER_CRITICAL();
{
// 对互斥 & 递归互斥信号量持有者进行优先级继承处理
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
}
#endif
void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
if( pxMutexHolder != NULL )
{
// 只有当前进程优先级高于互斥 & 递归互斥信号量持有者时,
// 才需要进行优先级继承操作
if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
{
// 如果持有者任务的事件列表项排序值没有在被使用的话,
// 则使用当前任务优先级修改该值
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 ) pxCurrentTCB->uxPriority );
}
if( listIS_CONTAINED_WITHIN(
&( pxReadyTasksLists[ pxTCB->uxPriority ] ),
&( pxTCB->xStateListItem ) ) != pdFALSE )
{
// 如果持有者任务在就绪列表
// 则将该任务从当前就绪列表移除,并在继承优先级之后,
// 加入新的就绪列表
if( uxListRemove( &( pxTCB->xStateListItem ) ) ==
( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
pxTCB->uxPriority = pxCurrentTCB->uxPriority;
prvAddTaskToReadyList( pxTCB );
}
else
{
// 如果持有者任务不在就绪列表,则仅继承优先级
pxTCB->uxPriority = pxCurrentTCB->uxPriority;
}
}
}
}
说明1:互斥 & 递归互斥信号量不能在中断ISR中使用
因为中断ISR中没有任务优先级的概念,所以无法正确进行优先级继承处理
说明2:对事件列表项排序值的使用
① 在FreeRTOS的事件标志组机制中会以代码中的方式使用该值,此时无法修改该值,详见相关章节笔记
② 事件列表项排序值的另一种更常见用法,是将任务加入内核对象的等待列表时进行排序,优先级越高,事件列表项排序值越小,则任务越在内核对象等待列表的队首位置
一个任务在获取互斥信号量之后,也可能被阻塞在其他内核对象中(e.g. 再次获取另一个信号量),因此其事件列表项可能被加入内核对象的等待队列
从原理上说,在进行优先级继承的过程中,应该以新的事件列表项排序值重新加入内核对象等待队列,但是代码中并未进行此操作
4.4 释放互斥信号量
释放互斥信号量仍然使用xSemaphoreGive函数,要点也在优先级继承处理,实际在prvCopyDataToQueue函数中实现
// 队列项长度为0,说明是处理信号量的分支
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
// 处理优先级继承
xReturn = xTaskPriorityDisinherit(
( void * ) pxQueue->pxMutexHolder );
// 将互斥 & 递归互斥信号量的持有者置为NULL
pxQueue->pxMutexHolder = NULL;
}
}
#endif /* configUSE_MUTEXES */
}
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL )
{
// 互斥 & 递归互斥信号量只能由持有者释放
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
// 减少当前任务持有的互斥 & 递归互斥信号量计数
( pxTCB->uxMutexesHeld )--;
// 只有确实发生过优先级继承,此处才需要恢复优先级
if( pxTCB->uxPriority != pxTCB->uxBasePriority )
{
// 只有当持有者任务持有的互斥 & 递归互斥信号量个数为0时,
// 才恢复任务优先级
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
{
// 释放互斥 & 递归互斥信号量的一定是持有者,
// 且持有者任务当前一定正在运行
// 将该任务从当前就绪列表移出,恢复优先级后
// 重新加入对应原始优先级的就绪列表
if( uxListRemove( &( pxTCB->xStateListItem ) ) ==
( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
pxTCB->uxPriority = pxTCB->uxBasePriority;
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ),
( TickType_t ) configMAX_PRIORITIES -
( TickType_t ) pxTCB->uxPriority );
prvAddTaskToReadyList( pxTCB );
// 标识进行了恢复优先级的操作
// 外部需要触发一次任务调度
// 恢复优先级一定是降低了当前任务的优先级
// 所以要唤醒正在等待的高优先级任务(假设他还在等待的话)
xReturn = pdTRUE;
}
}
}
return xReturn;
}
5. 递归互斥信号量
5.1 创建递归互斥信号量
#define xSemaphoreCreateRecursiveMutex() \
xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
可见初始化递归互斥信号量和初始化互斥信号量的操作是一样的,只是注册时的信号量类型不同
5.2 获取递归互斥信号量
#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 );
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() )
{
// 如果该递归互斥信号量已经被当前任务持有过,
// 则增加递归持有计数
( pxMutex->u.uxRecursiveCallCount )++;
xReturn = pdPASS;
}
else
{
// 如果该递归互斥信号量的持有者不是当前任务,
// 可能没有被任务持有,或者已经被其他任务持有,
// 则进行一次正常的获取操作,如果获取成功,则增加递归持有计数
xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait,
pdFALSE );
if( xReturn != pdFAIL )
{
( pxMutex->u.uxRecursiveCallCount )++;
}
}
return xReturn;
}
5.3 释放递归互斥信号量
#define xSemaphoreGiveRecursive( xMutex ) \
xQueueGiveMutexRecursive( ( xMutex ) )
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
// 只有递归互斥信号量的持有者才能释放
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() )
{
// 减少递归持有计数
( pxMutex->u.uxRecursiveCallCount )--;
// 当递归持有计数为0时,实际释放信号量
// 在xQueueGenericSend函数中会处理优先级继承
if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
{
( void ) xQueueGenericSend( pxMutex, NULL,
queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
return xReturn;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)