递归信号量,见文知义,递归嘛,就是可以重复获取调用的,本来按照信号量的特性,每获取一次可用信号量个数就会减少一个,但是递归则然, 对于已经获取递归互斥量的 任务可以重复获取该递归互斥量, 该任务拥有递归信号量的所有权。 任务成功获取几次递归互斥量, 就要返还几次,在此之前递归互斥量都处于无效状态, 其他任务无法获取, 只有持有递归信号量的任务才能获取与释放。
递归互斥信号量可以看作是特殊的互斥信号量,与互斥信号量不同的是,递归互斥信号量在被获取后,可以被其持有者重复获取,当然递归互斥信号量的持有者需要释放递归互斥信号量与之获取递归互斥信号量相同的次数,递归互斥信号量才算被释放。
递归互斥信号量与互斥信号量一样,也具备优先级继承机制,因此也不能在中断服务函数中使用递归互斥信号量。
FreeRTOS 提供了互斥信号量的一些相关操作函数,其中常用的互斥信号量相关 API 函数, 如下表所示:
创建递归互斥信号量
xSemaphoreCreateRecursiveMutex() 动态方式创建递归互斥信号量
此函数用于使用动态方式创建递归互斥信号量,创建递归互斥信号量所需的内存,由FreeRTOS 从 FreeRTOS 管理的堆中进行分配。该函数实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示:
/**
* semphr.h
* @code{c}
* SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
* @endcode
*
* 创建一个新的递归互斥锁类型的信号量实例,并返回一个可以引用新递归互斥锁的句柄。
*
* 在FreeRTOS内部实现中,递归互斥锁使用一块内存来存储互斥锁结构。如果使用 `xSemaphoreCreateRecursiveMutex()` 创建递归互斥锁,则所需的内存在 `xSemaphoreCreateRecursiveMutex()` 函数内部自动动态分配。(参见 https://www.FreeRTOS.org/a00111.html)。如果使用 `xSemaphoreCreateRecursiveMutexStatic()` 创建递归互斥锁,则应用程序编写者必须提供互斥锁将使用的内存。因此,`xSemaphoreCreateRecursiveMutexStatic()` 允许在不使用任何动态内存分配的情况下创建递归互斥锁。
*
* 使用此宏创建的互斥锁可以通过 `xSemaphoreTakeRecursive()` 和 `xSemaphoreGiveRecursive()` 宏访问。不能使用 `xSemaphoreTake()` 和 `xSemaphoreGive()` 宏。
*
* 递归使用的互斥锁可以被所有者多次“获取”。互斥锁在所有者对每次成功的“获取”请求都调用 `xSemaphoreGiveRecursive()` 之前不会再次可用。例如,如果一个任务成功“获取”同一个互斥锁5次,那么在它也“释放”互斥锁5次之前,互斥锁将不可用给其他任务。
*
* 这种类型的信号量使用优先级继承机制,因此一个任务“获取”互斥锁后必须始终在不再需要互斥锁时“释放”互斥锁。
*
* 互斥锁类型的信号量不能在中断服务例程中使用。
*
* 请参见 `xSemaphoreCreateBinary()`,它提供了另一种实现,可用于纯同步(其中一个任务或中断总是“释放”信号量,另一个总是“获取”信号量)并在中断服务例程中使用。
*
* @return 返回创建的互斥锁信号量的句柄。应为 `SemaphoreHandle_t` 类型。
*
* 示例用法:
* @code{c}
* SemaphoreHandle_t xSemaphore;
*
* void vATask( void * pvParameters )
* {
* // 在调用 xSemaphoreCreateMutex() 之前不能使用信号量。
* // 这是一个宏,所以直接传递变量。
* xSemaphore = xSemaphoreCreateRecursiveMutex();
*
* if( xSemaphore != NULL )
* {
* // 互斥锁创建成功。
* // 现在可以使用互斥锁。
* }
* }
* @endcode
* \defgroup xSemaphoreCreateRecursiveMutex xSemaphoreCreateRecursiveMutex
* \ingroup Semaphores
*/
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) )
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
#endif
函数 xSemaphoreCreateRecursiveMutex()实际上是调用了函数xQueueCreateMutex()创建了一个递归互斥信号量,这与互斥信号量是大致相同的。
xSemaphoreCreateRecursiveMutexStatic() 静态方式创建递归互斥信号
/**
* semphr.h
* @code{c}
* SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer );
* @endcode
*
* 创建一个新的递归互斥锁类型的信号量实例,并返回一个可以引用新递归互斥锁的句柄。
*
* 在FreeRTOS内部实现中,递归互斥锁使用一块内存来存储互斥锁结构。如果使用 `xSemaphoreCreateRecursiveMutex()` 创建递归互斥锁,则所需的内存在 `xSemaphoreCreateRecursiveMutex()` 函数内部自动动态分配。(参见 https://www.FreeRTOS.org/a00111.html)。如果使用 `xSemaphoreCreateRecursiveMutexStatic()` 创建递归互斥锁,则应用程序编写者必须提供互斥锁将使用的内存。因此,`xSemaphoreCreateRecursiveMutexStatic()` 允许在不使用任何动态内存分配的情况下创建递归互斥锁。
*
* 使用此宏创建的互斥锁可以通过 `xSemaphoreTakeRecursive()` 和 `xSemaphoreGiveRecursive()` 宏访问。不能使用 `xSemaphoreTake()` 和 `xSemaphoreGive()` 宏。
*
* 递归使用的互斥锁可以被所有者多次“获取”。互斥锁在所有者对每次成功的“获取”请求都调用 `xSemaphoreGiveRecursive()` 之前不会再次可用。例如,如果一个任务成功“获取”同一个互斥锁5次,那么在它也“释放”互斥锁5次之前,互斥锁将不可用给其他任务。
*
* 这种类型的信号量使用优先级继承机制,因此一个任务“获取”互斥锁后必须始终在不再需要互斥锁时“释放”互斥锁。
*
* 互斥锁类型的信号量不能在中断服务例程中使用。
*
* 请参见 `xSemaphoreCreateBinary()`,它提供了另一种实现,可用于纯同步(其中一个任务或中断总是“释放”信号量,另一个总是“获取”信号量)并在中断服务例程中使用。
*
* @param pxMutexBuffer 必须指向一个类型为 `StaticSemaphore_t` 的变量,该变量将用于保存递归互斥锁的数据结构,从而避免动态内存分配。
*
* @return 如果递归互斥锁成功创建,则返回一个引用创建的递归互斥锁的句柄。如果 `pxMutexBuffer` 为 `NULL`,则返回 `NULL`。
*
* 示例用法:
* @code{c}
* SemaphoreHandle_t xSemaphore;
* StaticSemaphore_t xMutexBuffer;
*
* void vATask( void * pvParameters )
* {
* // 递归信号量在创建之前不能使用。这里使用 `xSemaphoreCreateRecursiveMutexStatic()` 创建一个递归互斥锁。
* // 将 `xMutexBuffer` 的地址传递给函数,该地址将保存互斥锁的数据结构——因此不会尝试动态内存分配。
* xSemaphore = xSemaphoreCreateRecursiveMutexStatic( &xMutexBuffer );
*
* // 由于没有进行动态内存分配,`xSemaphore` 不会为 `NULL`,因此不需要检查它。
* }
* @endcode
* \defgroup xSemaphoreCreateRecursiveMutexStatic xSemaphoreCreateRecursiveMutexStatic
* \ingroup Semaphores
*/
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) )
#define xSemaphoreCreateRecursiveMutexStatic( pxStaticSemaphore ) xQueueCreateMutexStatic( queueQUEUE_TYPE_RECURSIVE_MUTEX, ( pxStaticSemaphore ) )
#endif /* configSUPPORT_STATIC_ALLOCATION */
函数 xSemaphoreCreateRecursiveMutexStatuc()实际上是调用了函数 xQueueCreateMutexStatic()创建了一个递归互斥信号量,这与互斥信号量是大致相同的。
xSemaphoreTakeRecursive() 获取递归互斥信号量
/**
* semphr.h
* @code{c}
* xSemaphoreTakeRecursive(
* SemaphoreHandle_t xMutex,
* TickType_t xBlockTime
* );
* @endcode
*
* 递归获取(或“占有”)一个互斥信号量类型的宏。
* 该互斥信号量必须通过调用 xSemaphoreCreateRecursiveMutex() 创建。
*
* 必须在 FreeRTOSConfig.h 中将 configUSE_RECURSIVE_MUTEXES 设置为 1,才能使用此宏。
*
* 此宏不能用于通过 xSemaphoreCreateMutex() 创建的互斥信号量。
*
* 递归使用的互斥信号量可以被所有者多次“占有”。互斥信号量在所有者调用 xSemaphoreGiveRecursive() 的次数与成功“占有”请求的次数相同时才会再次可用。例如,如果一个任务成功“占有”同一个互斥信号量 5 次,则该互斥信号量在任务也“释放”该互斥信号量 5 次之前不会对任何其他任务可用。
*
* @param xMutex 互斥信号量的句柄。这是通过 xSemaphoreCreateRecursiveMutex() 返回的句柄。
*
* @param xBlockTime 等待信号量变为可用的时间(以滴答数为单位)。可以使用宏 portTICK_PERIOD_MS 将其转换为实际时间。阻塞时间为零可用于轮询信号量。如果任务已经拥有该信号量,则无论 xBlockTime 的值如何,xSemaphoreTakeRecursive() 都会立即返回。
*
* @return 如果成功获取信号量,则返回 pdTRUE。如果 xBlockTime 到期而信号量仍未可用,则返回 pdFALSE。
*
* 使用示例:
* @code{c}
* SemaphoreHandle_t xMutex = NULL;
*
* // 创建互斥信号量的任务。
* void vATask( void * pvParameters )
* {
* // 创建一个互斥信号量来保护共享资源。
* xMutex = xSemaphoreCreateRecursiveMutex();
* }
*
* // 使用互斥信号量的任务。
* void vAnotherTask( void * pvParameters )
* {
* // ... 执行其他操作。
*
* if( xMutex != NULL )
* {
* // 尝试获取互斥信号量。如果互斥信号量不可用,等待 10 个滴答时间看看它是否变为空闲。
* if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
* {
* // 我们能够获取互斥信号量,现在可以访问共享资源。
*
* // ...
* // 由于代码的性质,进一步调用 xSemaphoreTakeRecursive() 占有同一个互斥信号量。在实际代码中,这些调用不会只是连续的调用,而是可能嵌套在更复杂的调用结构中。
* xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
* xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
*
* // 现在互斥信号量已经被“占有”了三次,因此在也“释放”三次之前不会对其他任务可用。在实际代码中,这些调用也不会只是连续的调用,而是可能嵌套在更复杂的调用结构中。这只是为了说明目的。
* xSemaphoreGiveRecursive( xMutex );
* xSemaphoreGiveRecursive( xMutex );
* xSemaphoreGiveRecursive( xMutex );
*
* // 现在其他任务可以获取互斥信号量。
* }
* else
* {
* // 我们无法获取互斥信号量,因此无法安全地访问共享资源。
* }
* }
* }
* @endcode
* \defgroup xSemaphoreTakeRecursive xSemaphoreTakeRecursive
* \ingroup Semaphores
*/
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
#endif
函 数 xSemaphoreTakeRecursive() 实际 上是调 用 了函 数xQueueTakeMutexRecursice(),函数 xQueueTakeMutexRecursive()在 queue.c 文件中有定义,具体的代码如下所示:
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex,
TickType_t xTicksToWait )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
// 记录进入 xQueueTakeMutexRecursive 函数的事件
traceENTER_xQueueTakeMutexRecursive( xMutex, xTicksToWait );
// 断言互斥锁句柄不为空
configASSERT( pxMutex );
/* 关于互斥锁的注释,参见 xQueueGiveMutexRecursive() 中的相关注释。 */
// 记录获取互斥锁的事件
traceTAKE_MUTEX_RECURSIVE( pxMutex );
// 检查当前任务是否已经持有该互斥锁
if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
{
// 增加递归调用计数
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
// 返回成功
xReturn = pdPASS;
}
else
{
// 尝试获取互斥锁
xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );
// 如果成功获取互斥锁
if( xReturn != pdFAIL )
{
// 增加递归调用计数
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
}
else
{
// 记录获取互斥锁失败的事件
traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
// 记录返回 xQueueTakeMutexRecursive 函数的事件
traceRETURN_xQueueTakeMutexRecursive( xReturn );
// 返回结果
return xReturn;
}
#endif /* configUSE_RECURSIVE_MUTEXES */
-
1.进入函数:
-
记录进入
xQueueTakeMutexRecursive
函数的事件traceENTER_xQueueTakeMutexRecursive
。
-
-
参数检查:
-
通过
configASSERT
确保互斥锁句柄pxMutex
不为空。
-
-
递归检查:
-
检查当前任务是否已经持有该互斥锁,通过比较
pxMutex->u.xSemaphore.xMutexHolder
和xTaskGetCurrentTaskHandle()
。-
如果当前任务已经持有互斥锁,增加递归调用计数
pxMutex->u.xSemaphore.uxRecursiveCallCount
并返回pdPASS
。
-
-
-
尝试获取互斥锁:
-
如果当前任务未持有互斥锁,调用
xQueueSemaphoreTake
尝试获取互斥锁。-
如果成功获取互斥锁,增加递归调用计数并返回
pdPASS
。 -
如果失败,记录失败事件
traceTAKE_MUTEX_RECURSIVE_FAILED
并返回pdFAIL
。
-
-
-
记录返回事件:
-
记录返回
xQueueTakeMutexRecursive
函数的事件traceRETURN_xQueueTakeMutexRecursive
。
-
-
返回结果:
-
返回操作结果
xReturn
。
-
xSemaphoreGiveRecursive()释放递归互斥信号量
此函数用于释放递归互斥信号量,函数 xSemaphoreGiveRecursive()实际上是一个宏定义,在 semphr.h 文件中有定义,具体的代码如下所示:
/**
* semphr.h
*
* @code{c}
* xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex );
* @endcode
*
* 用于递归释放(或“给予”)一个互斥锁类型的信号量的宏。
* 该互斥锁必须之前通过调用 xSemaphoreCreateRecursiveMutex() 创建。
*
* 必须在 FreeRTOSConfig.h 中将 configUSE_RECURSIVE_MUTEXES 设置为 1 才能使用此宏。
*
* 此宏不能用于通过 xSemaphoreCreateMutex() 创建的互斥锁。
*
* 递归使用的互斥锁可以由所有者多次“获取”。互斥锁在所有者对每次成功的“获取”请求都调用 xSemaphoreGiveRecursive() 之前不会再次可用。例如,如果一个任务成功地“获取”同一个互斥锁 5 次,那么在该任务也“释放”该互斥锁 5 次之前,互斥锁将不可用给其他任务。
*
* @param xMutex 要释放或“给予”的互斥锁的句柄。这是 xSemaphoreCreateMutex() 返回的句柄。
*
* @return 如果信号量被释放,则返回 pdTRUE。
*
* 示例用法:
* @code{c}
* SemaphoreHandle_t xMutex = NULL;
*
* // 创建互斥锁的任务
* void vATask( void * pvParameters )
* {
* // 创建互斥锁以保护共享资源
* xMutex = xSemaphoreCreateRecursiveMutex();
* }
*
* // 使用互斥锁的任务
* void vAnotherTask( void * pvParameters )
* {
* // ... 执行其他操作
*
* if( xMutex != NULL )
* {
* // 尝试获取互斥锁。如果互斥锁不可用,等待 10 个节拍看它是否变得可用。
* if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
* {
* // 我们能够获取互斥锁,现在可以访问共享资源。
*
* // ...
* // 由于代码的性质,进一步调用 xSemaphoreTakeRecursive() 获取同一个互斥锁。在实际代码中这些调用不会是连续的,而是可能嵌套在更复杂的调用结构中。
* xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
* xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
*
* // 互斥锁已经被“获取”三次,因此在“释放”三次之前将不可用给其他任务。实际代码中这些调用可能是调用栈展开时发生的。这里只是为了演示目的。
* xSemaphoreGiveRecursive( xMutex );
* xSemaphoreGiveRecursive( xMutex );
* xSemaphoreGiveRecursive( xMutex );
*
* // 现在其他任务可以获取互斥锁。
* }
* else
* {
* // 我们无法获取互斥锁,因此不能安全地访问共享资源。
* }
* }
* }
* @endcode
* \defgroup xSemaphoreGiveRecursive xSemaphoreGiveRecursive
* \ingroup Semaphores
*/
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
#endif
函 数 xSemaphoreGiveRecursive() 实际 上是调 用 了函 数xQueueGiveMutexRecursice(),函数 xQueueGiveMutexRecursive()在 queue.c 文件中有定义,具体的代码如下所示:
#if ( configUSE_RECURSIVE_MUTEXES == 1 )
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
traceENTER_xQueueGiveMutexRecursive( xMutex ); // 进入函数的跟踪记录
configASSERT( pxMutex ); // 断言互斥锁句柄不为空
/* 如果当前任务是持有互斥锁的任务,则 pxMutex->u.xSemaphore.xMutexHolder 不会在此任务之外改变。
* 如果当前任务不是持有互斥锁的任务,则 pxMutex->u.xSemaphore.xMutexHolder 永远不可能巧合地等于任务的句柄,
* 因此不需要互斥访问来测试 pxMutex->u.xSemaphore.xMutexHolder 变量。 */
if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
{
traceGIVE_MUTEX_RECURSIVE( pxMutex ); // 递归释放互斥锁的跟踪记录
/* 如果 pxMutex->u.xSemaphore.xMutexHolder 等于任务句柄,则 pxMutex->u.xSemaphore.uxRecursiveCallCount 不可能为零,
* 因此不需要进行下溢检查。此外,pxMutex->u.xSemaphore.uxRecursiveCallCount 只能由互斥锁持有者修改,
* 而且只能有一个持有者,因此不需要互斥访问来修改 pxMutex->u.xSemaphore.uxRecursiveCallCount 成员。 */
( 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 ); // 递归释放互斥锁失败的跟踪记录
}
traceRETURN_xQueueGiveMutexRecursive( xReturn ); // 返回函数的跟踪记录
return xReturn;
}
#endif /* configUSE_RECURSIVE_MUTEXES */
-
进入函数并进行跟踪记录:
-
记录函数的进入,便于调试和性能分析。
-
-
断言互斥锁句柄不为空:
-
确保传入的互斥锁句柄
xMutex
不为空。如果为空,程序会在这里中断。
-
-
检查当前任务是否是互斥锁的持有者:
-
获取当前任务的句柄
xTaskGetCurrentTaskHandle()
并与互斥锁的持有者pxMutex->u.xSemaphore.xMutexHolder
进行比较。 -
如果当前任务是互斥锁的持有者,继续执行后续操作;否则,跳转到失败处理部分。
-
-
如果是持有者,递减递归调用计数:
-
递减互斥锁的递归调用计数
pxMutex->u.xSemaphore.uxRecursiveCallCount
。
-
-
如果递归调用计数为0,释放互斥锁:
-
检查递归调用计数是否为0。
-
如果为0,调用
xQueueGenericSend
函数释放互斥锁。这将解除任何可能正在等待访问互斥锁的任务的阻塞。 -
如果不为0,调用
mtCOVERAGE_TEST_MARKER
函数进行覆盖率测试标记。
-
-
返回成功并记录:
-
设置返回值
xReturn
为pdPASS
,表示成功。 -
记录函数的返回。
-
返回
xReturn
。
-
-
如果不是持有者,返回失败并记录:
-
设置返回值
xReturn
为pdFAIL
,表示失败。 -
记录递归释放互斥锁失败。
-
记录函数的返回。
-
返回
xReturn
。
-