互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一把钥匙, 当任务想要访问共享资源的时候就必须先获得这把钥匙,当访问完共享资源以后就必须归还这把钥匙,这样其他的任务就可以拿着这把钥匙去访问资源。
互斥信号量使用和二值信号量相同的 API 操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的机制。当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的减少了高优先级任务处于阻塞态的时间,并且将“优先级翻转”的影响降到最低。
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中,原因如下:
(1) 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
(2) 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
FreeRTOS 提供了互斥信号量的一些相关操作函数,其中常用的互斥信号量相关 API 函数,如下表所示:
创建互斥信号量
xSemaphoreCreateMutex() 动态方式创建互斥信号量
此函数用于使用动态方式创建互斥信号量,创建互斥信号量所需的内存,由 FreeRTOS 从FreeRTOS 管理的堆中进行分配。该函数实际上是一个宏定义,在 semphr.h 中有定义,具体的代码如下所示:
/*** semphr.h* * 创建一个新的互斥锁类型的信号量实例,并返回一个句柄,通过该句柄可以引用新创建的互斥锁。** 在 FreeRTOS 内部实现中,互斥锁信号量使用一块内存来存储互斥锁结构。如果使用 `xSemaphoreCreateMutex()` 创建互斥锁,则所需的内存会自动在 `xSemaphoreCreateMutex()` 函数内部动态分配。(参见 https://www.FreeRTOS.org/a00111.html)。如果使用 `xSemaphoreCreateMutexStatic()` 创建互斥锁,则应用程序编写者必须提供内存。因此,`xSemaphoreCreateMutexStatic()` 允许在不使用任何动态内存分配的情况下创建互斥锁。** 使用此函数创建的互斥锁可以通过 `xSemaphoreTake()` 和 `xSemaphoreGive()` 宏进行访问。不能使用 `xSemaphoreTakeRecursive()` 和 `xSemaphoreGiveRecursive()` 宏。** 此类型的信号量使用优先级继承机制,因此一个任务“获取”互斥锁后必须始终在不再需要该互斥锁时“释放”它。** 互斥锁类型的信号量不能在中断服务例程中使用。** 参见 `xSemaphoreCreateBinary()`,它提供了一种可以在纯同步场景中使用的替代实现(其中一个任务或中断总是“释放”信号量,另一个总是“获取”信号量),并且可以在中断服务例程中使用。** @return 如果互斥锁成功创建,则返回指向创建的信号量的句柄。如果堆内存不足无法分配互斥锁数据结构,则返回 NULL。** 示例用法:* @code{c}* SemaphoreHandle_t xSemaphore;** void vATask( void * pvParameters )* {* // 在调用 xSemaphoreCreateMutex() 之前不能使用信号量。* // 这是一个宏,所以直接传递变量。* xSemaphore = xSemaphoreCreateMutex();** if( xSemaphore != NULL )* {* // 互斥锁创建成功。* // 现在可以使用互斥锁。* }* }* @endcode* \defgroup xSemaphoreCreateMutex xSemaphoreCreateMutex* \ingroup Semaphores*/
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_MUTEXES == 1 ) )#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
函 数 xSemaphoreCreateMutex() 实 际 上 是 调 用 了 函 数xQueueCreateMutex(),函数 xQueueCreateMutex()在 queue.c 文件中有定义,具体的代码如下所示
#if ( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
/*** 创建一个新的互斥锁类型的信号量实例。** @param ucQueueType 信号量类型,通常是 `queueQUEUE_TYPE_MUTEX`。** @return 如果互斥锁成功创建,则返回指向创建的信号量的句柄。如果创建失败,则返回 NULL。*/
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{QueueHandle_t xNewQueue;const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;// 记录进入 xQueueCreateMutex 函数的事件traceENTER_xQueueCreateMutex( ucQueueType );// 调用通用队列创建函数,创建一个长度为 1、项目大小为 0 的队列xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );// 初始化互斥锁prvInitialiseMutex( ( Queue_t * ) xNewQueue );// 记录返回 xQueueCreateMutex 函数的事件traceRETURN_xQueueCreateMutex( xNewQueue );// 返回创建的互斥锁句柄return xNewQueue;
}#endif /* configUSE_MUTEXES */
函数 xQueueCreateMutex
,用于创建一个互斥锁。具体功能如下:
-
记录进入函数的事件:
-
调用
traceENTER_xQueueCreateMutex(ucQueueType)
函数,记录进入xQueueCreateMutex
函数的事件。这通常用于调试和跟踪程序的执行流程。
-
-
创建队列:
-
定义常量
uxMutexLength
和uxMutexSize
,分别设置为1和0。这表示创建的队列长度为1,每个项目的大小为0。 -
调用
xQueueGenericCreate(uxMutexLength, uxMutexSize, ucQueueType)
函数,创建一个长度为1、项目大小为0的队列。这个队列实际上是一个互斥锁。
-
-
初始化互斥锁:
-
调用
prvInitialiseMutex((Queue_t *) xNewQueue)
函数,初始化创建的互斥锁。prvInitialiseMutex
函数负责设置互斥锁的初始状态,确保其可以正常使用。
-
-
记录返回函数的事件:
-
调用
traceRETURN_xQueueCreateMutex(xNewQueue)
函数,记录返回xQueueCreateMutex
函数的事件。这同样用于调试和跟踪程序的执行流程。
-
-
返回互斥锁句柄:
-
•
返回创建的互斥锁句柄
xNewQueue
。这个句柄可以在其他地方用于操作互斥锁。
-
互斥信号量的就是一个队列长度为 1 的队列, 且队列项目的大小为 0, 而队列的非空闲项目数量就是互斥信号量的资源数。 函数 xQueueCreateMutex()还会调用函数 prvInitialiseMutex()对互斥信号量进行初始化,函数 prvInitialiseMutex()在 queue.c 文件中有定义,具体的代码如下所示:
#if ( configUSE_MUTEXES == 1 )static void prvInitialiseMutex( Queue_t * pxNewQueue ){if( pxNewQueue != NULL ){/** 队列创建函数会为通用队列正确设置所有队列结构成员,* 但此函数是在创建互斥锁。覆盖那些需要不同设置的成员——* 特别是优先级继承所需的信息。*/pxNewQueue->u.xSemaphore.xMutexHolder = NULL; // 设置互斥锁持有者为 NULLpxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; // 设置队列类型为互斥锁/** 以防这是一个递归互斥锁。*/pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0; // 设置递归调用计数为 0traceCREATE_MUTEX( pxNewQueue ); // 记录创建互斥锁的事件/** 使互斥锁处于预期的初始状态。*/( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK ); // 发送一个空项到队列}else{traceCREATE_MUTEX_FAILED(); // 记录创建互斥锁失败的事件}}#endif /* configUSE_MUTEXES */
xSemaphoreCreateMutexStatic()静态方式创建互斥信号量
/*** semphr.h* @code{c}* SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );* @endcode** 创建一个新的互斥锁类型的信号量实例,并返回一个句柄,通过该句柄可以引用新创建的互斥锁。** 在 FreeRTOS 内部实现中,互斥锁信号量使用一块内存来存储互斥锁结构。如果使用 `xSemaphoreCreateMutex()` 创建互斥锁,* 则所需的内存会在 `xSemaphoreCreateMutex()` 函数内部自动动态分配。(参见 https://www.FreeRTOS.org/a00111.html)* 如果使用 `xSemaphoreCreateMutexStatic()` 创建互斥锁,则应用程序编写者必须提供内存。因此,`xSemaphoreCreateMutexStatic()`* 允许在不使用任何动态内存分配的情况下创建互斥锁。** 使用此函数创建的互斥锁可以通过 `xSemaphoreTake()` 和 `xSemaphoreGive()` 宏进行访问。不能使用 `xSemaphoreTakeRecursive()`* 和 `xSemaphoreGiveRecursive()` 宏。** 这种类型的互斥锁使用优先级继承机制,因此“获取”互斥锁的任务必须始终在不再需要互斥锁时“释放”互斥锁。** 互斥锁类型的信号量不能在中断服务例程中使用。** 参见 `xSemaphoreCreateBinary()`,它提供了另一种实现,可以用于纯同步(其中一个任务或中断总是“释放”信号量,另一个总是“获取”信号量),* 并且可以在中断服务例程中使用。** @param pxMutexBuffer 必须指向一个类型为 `StaticSemaphore_t` 的变量,该变量将用于保存互斥锁的数据结构,从而避免动态内存分配。** @return 如果互斥锁成功创建,则返回指向创建的互斥锁的句柄。如果 `pxMutexBuffer` 为 `NULL`,则返回 `NULL`。** 示例用法:* @code{c}* SemaphoreHandle_t xSemaphore;* StaticSemaphore_t xMutexBuffer;** void vATask( void * pvParameters )* {* // 在使用互斥锁之前必须先创建它。`xMutexBuffer` 传递给 `xSemaphoreCreateMutexStatic()`,* // 因此不会尝试动态内存分配。* xSemaphore = xSemaphoreCreateMutexStatic( &xMutexBuffer );** // 由于没有进行动态内存分配,`xSemaphore` 不可能为 `NULL`,因此不需要检查它。* }* @endcode* \defgroup xSemaphoreCreateMutexStatic xSemaphoreCreateMutexStatic* \ingroup Semaphores*/#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configUSE_MUTEXES == 1 ) )#define xSemaphoreCreateMutexStatic( pxMutexBuffer ) xQueueCreateMutexStatic( queueQUEUE_TYPE_MUTEX, ( pxMutexBuffer ) )
#endif
函数 xSemaphoreCreateMutexStatic()实际上是调用了函数xQueueCreateMutexStatic(), 而函数 xQueueCreateMutexStatic()在 queue.c 文件中有定义,其函数内容与函数 xQueueCreateMutex()是类似的,只是将动态创建队列的函数替换成了静态创建队列的函数。
#if ( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType,StaticQueue_t * pxStaticQueue ){QueueHandle_t xNewQueue;const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;traceENTER_xQueueCreateMutexStatic( ucQueueType, pxStaticQueue ); // 记录进入 xQueueCreateMutexStatic 函数的事件/* 防止编译器在 configUSE_TRACE_FACILITY 不等于 1 时发出未使用参数的警告。 */( void ) ucQueueType;/* 调用 xQueueGenericCreateStatic 函数创建一个静态互斥锁队列。 */xNewQueue = xQueueGenericCreateStatic( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );/* 初始化新创建的互斥锁队列。 */prvInitialiseMutex( ( Queue_t * ) xNewQueue );traceRETURN_xQueueCreateMutexStatic( xNewQueue ); // 记录从 xQueueCreateMutexStatic 函数返回的事件return xNewQueue; // 返回新创建的互斥锁队列的句柄}#endif /* configUSE_MUTEXES */
-
记录进入事件:
-
traceENTER_xQueueCreateMutexStatic( ucQueueType, pxStaticQueue );
:记录进入xQueueCreateMutexStatic
函数的事件。
-
-
防止编译器警告:
-
( void ) ucQueueType;
:防止编译器在configUSE_TRACE_FACILITY
不等于1时发出未使用参数的警告。
-
-
创建静态队列:
-
xNewQueue = xQueueGenericCreateStatic( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );
:调用xQueueGenericCreateStatic
创建一个长度为1、大小为0的静态队列。
-
-
初始化互斥锁队列:
-
prvInitialiseMutex( ( Queue_t * ) xNewQueue );
:初始化新创建的互斥锁队列。
-
-
记录返回事件:
-
traceRETURN_xQueueCreateMutexStatic( xNewQueue );
:记录从xQueueCreateMutexStatic
函数返回的事件。
-
-
返回互斥锁队列句柄:
-
return xNewQueue;
:返回新创建的互斥锁队列的句柄。
-