【FreeRTOS 教程 七】互斥锁与递归互斥锁
目录
一、FreeRTOS 互斥锁:
(1)互斥锁的基本介绍:
(2)互斥锁的优先级继承机制:
(3)互斥锁的使用限制:
二、FreeRTOS 递归互斥锁:
(1)递归互斥锁的基本介绍:
(2)简化优先级继承:
三、互斥锁与递归互斥锁 API :
(1)动态创建互斥锁:
(2)静态创建互斥锁:
(3)动态创建递归互斥锁:
(4)静态创建递归互斥锁:
四、信号量控制 API :
(1)获取指定的互斥锁的任务句柄:
(2)返回信号量计数:
(3)获取信号量:
(4)获取信号量(ISR):
(5)递归获取互斥锁信号量:
(6)释放信号量:
(7)递归释放互斥锁信号量:
(8)释放信号量(ISR):
(9)删除信号量:
五、互斥锁与递归互斥锁使用示例:
(1)动态创建互斥锁:
(2)静态创建互斥锁:
(3)动态创建递归互斥锁:
(4)静态创建递归互斥锁:
六、FreeRTOS教程示例代码下载:
一、FreeRTOS 互斥锁:
(1)互斥锁的基本介绍:
互斥锁是包含优先级继承机制的二进制信号量。鉴于二进制信号量是实现同步(任务之间或任务与中断之间)的更好方式,因此互斥锁更适合实现简单的相互排斥(即互斥)。
用于互斥时,互斥锁就像用于保护资源的令牌。当一个任务希望访问资源时,必须首先获得(“获取”)该令牌。使用完资源后,任务必须“返还”令牌,以便其他任务有机会访问相同的资源。
(2)互斥锁的优先级继承机制:
互斥锁使用相同的信号量访问 API 函数,因此也能指定阻塞时间。该阻塞时间表示一个任务试图“获取”互斥锁,而互斥锁无法立即使用时,任务应进入阻塞状态的最大“滴答”数。然而,与二进制信号量不同,互斥锁采用优先级继承机制。这意味着如果高优先级任务进入阻塞状态,同时尝试获取当前由低优先级任务持有的互斥锁(令牌),则持有令牌的任务的优先级会暂时提高到阻塞任务的优先级。这项机制旨在确保较高优先级的任务保持阻塞状态的时间尽可能短,从而最大限度减少已经发生的“优先级反转”现象。
(3)互斥锁的使用限制:
优先级继承无法解决优先级反转!只是在某些情况下将影响降至最低。硬实时应用程序的设计应从一开始就避免发生优先级反转。
不能从中断中使用互斥锁的原因是:
- 互斥锁使用的优先级继承机制要求 从任务中(而不是从中断中)拿走和放入互斥锁。
- 中断无法保持阻塞来等待一个被互斥锁保护的资源 变为可用。
使用互斥锁来保护对共享资源的访问。
二、FreeRTOS 递归互斥锁:
(1)递归互斥锁的基本介绍:
用户可对一把递归互斥锁重复加锁。只有用户为每个成功的 xSemaphoreTakeRecursive() 请求调用 xSemaphoreGiveRecursive() 后,互斥锁才会重新变为可用。例如,如果一个任务成功“加锁”相同的互斥锁 5 次, 那么任何其他任务都无法使用此互斥锁,直到任务也把这个互斥锁“解锁”5 次。
这种类型的信号量使用优先级继承机制,因此“加锁”一个信号量的任务必须在不需要此信号量时, 立即将信号量“解锁”。
不能从中断服务程序中使用类型是互斥锁的信号量。
不能从中断中使用互斥锁的原因是:
- 互斥锁使用的优先级继承机制要求从任务中(而不是从中断中)拿走和放入互斥锁。
- 中断无法保持阻塞来等待一个被互斥锁保护的资源变得可用。
(2)简化优先级继承:
FreeRTOS 实现了基本的优先级继承机制,旨在优化空间和执行周期。完全的优先级继承机制需要多得多的数据和处理器周期来确定任何时刻的继承优先级,特别是在任务同时占用超过一个互斥锁时 。
请牢记优先级继承机制的这些特定行为:
- 如果一个任务在占用一个互斥锁时没有先释放它已占用的互斥锁, 则可以进一步提升其继承优先级。
- 任务在释放其占有的所有互斥锁之前,一直保持最高继承优先级。 这与释放互斥锁的顺序无关。
- 如果多个互斥锁被占用,无论在任何一个被占用的互斥锁上等待的任务是否完成等待(超时), 则任务将保持最高继承优先级 。
三、互斥锁与递归互斥锁 API :
(1)动态创建互斥锁:
函数原型:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
作用:创建互斥锁,并返回一个该互斥锁可以引用的句柄。
- 中断服务例程中,不能使用互斥锁。
- configSUPPORT_DYNAMIC_ALLOCATION 和 configUSE_MUTEXES 必须同时在 FreeRTOSConfig.h 中设置为 1, xSemaphoreCreateMutex() 才可用。( configSUPPORT_DYNAMIC_ALLOCATION 也可以不定义,在这种情况下下,它将默认为 1。)
- 每个互斥锁需要少量 RAM,以此来保持互斥锁的状态。如果互斥锁是使用 xSemaphoreCreateMutex() 创建的,则所需的 RAM 将从 FreeRTOS 堆自动分配。如果互斥锁是使用 xSemaphoreCreateMutexStatic() 创建的,那么应用由应用程序写入器提供 RAM,但允许在编译时静态分配 RAM。
- 使用 xSemaphoreTake() 获取互斥锁,并使用 xSemaphoreGive() 给出互斥锁。
- xSemaphoreTakeRecursive() 和 xSemaphoreGiveRecursive() 只能在使用 xSemaphoreCreateRecursiveMutex() 创建的互斥锁上使用。
- 互斥锁和二进制信号量极为相似,但有一些细微差异:互斥锁具有优先级继承机制,但二进制信号量没有。因此,二进制信号量是实现同步的更好选择(任务之间或任务与中断之间),也是实施简单互斥方面的更好选择。
- 如果另一个更高优先级的任务尝试获取相同的互斥锁,则将暂时提高“获取”互斥锁的任务的优先级。拥有互斥锁的任务“继承”试图“获取”相同互斥锁的任务的优先级。这意味着必须始终“归还”互斥锁,否则优先级较高的任务将始终无法获得互斥锁,而优先级较低 而优先级较低的任务将永远无法“取消继承”优先级。更多有关优先级继承机制的信息,请参阅 FreeRTOS 互斥锁文档页面。
- 一旦获得二进制信号量,则无需要返回。因此,任务同步可以通过一个任务/中断持续释放信号量,而另外一个持续获得信号量来实现。请注意,可以使用直接任务通知以更有效的方式 实现相同功能。
- 互斥锁和二进制信号量的句柄都分配给 SemaphoreHandle_t 类型的变量,并且可以在任何接受该类型参数的任务级别(与中断安全相反)API 函数中使用。
返回值 | 条件 |
---|---|
互斥锁的句柄 | 如果已成功创建互斥锁型信号量 |
NULL | 如果由于无法分配保存互斥锁所需的内存而未创建互斥锁 |
用法示例:
SemaphoreHandle_t xSemaphore;
void vATask( void * pvParameters )
{
/* 创建一个互斥锁类型的信号量。 */
xSemaphore = xSemaphoreCreateMutex();
if( xSemaphore != NULL )
{
/* 信号量创建成功,并且现在可以使用。 */
}
}
(2)静态创建互斥锁:
函数原型:
SemaphoreHandle_t xSemaphoreCreateMutexStatic(
StaticSemaphore_t *pxMutexBuffer );
作用:创建互斥锁,并返回一个该互斥锁可以引用的句柄。
- configSUPPORT_STATIC_ALLOCATION 和 configUSE_MUTEXES 必须同时在 FreeRTOSConfig.h 中设置为 1, xSemaphoreCreateMutexStatic () 才可用。
- 每个互斥锁需要少量 RAM,以此来保持互斥锁的状态。如果互斥锁是使用 xSemaphoreCreateMutex() 创建的,则所需的 RAM 将从 FreeRTOS 堆自动分配。如果互斥锁是使用 xSemaphoreCreateMutexStatic() 创建的,那么应用由应用程序写入器提供 RAM,但允许在编译时静态分配 RAM。
- 其他说明同动态创建互斥锁。
参数/返回值 | 描述 |
---|---|
参数 | pxMutexBuffer:必须指向 StaticSemaphore_t 类型的变量,该变量将用于保存互斥锁型信号量的状态。 |
返回值 | 如果已成功创建互斥锁型信号量,则返回创建的互斥锁的句柄。如果因为 pxMutexBuffer 为 NULL 而未创建互斥锁,则返回 NULL。 |
用法示例:
SemaphoreHandle_t xSemaphore = NULL;
StaticSemaphore_t xMutexBuffer;
void vATask( void * pvParameters )
{
/* 不使用任何动态内存分配创建一个互斥信号量。
互斥锁的数据结构将保存到 xMutexBuffer 变量中。 */
xSemaphore = xSemaphoreCreateMutexStatic( &xMutexBuffer );
/* pxMutexBuffer 不为 NULL,因此预期句柄也不会为 NULL。 */
configASSERT( xSemaphore );
/* 任务代码的其余部分在这里。 */
}
(3)动态创建递归互斥锁:
函数原型:
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void)
作用:创建一个递归互斥锁,并返回一个可以引用该互斥锁的句柄。
- 不能在中断服务程序中使用递归互斥锁。configSUPPORT_DYNAMIC_ALLOCATION 和 configUSE_RECURSIVE_MUTEXES 必须在 FreeRTOSConfig.h 中设置为 1,xSemaphoreCreateRecursiveMutex() 才可用(configSUPPORT_DYNAMIC_ALLOCATION 也可以不定义,这种情况下默认为 1)。
- 每个递归互斥锁都需要少量 RAM,用于保存递归互斥锁的状态。如果互斥锁是使用 xSemaphoreCreateRecursiveMutex() 创建的,则所需的 RAM 将从 FreeRTOS 堆自动分配。如果一个递归互斥锁是使用 xSemaphoreCreateRecursiveMutexStatic() 创建的,那么 RAM 由应用程序写入器提供,这需要用到一个附加参数,但允许在编译时静态分配 RAM。
- 递归互斥锁分别使用 xSemaphoreTakeRecursive() 和 xSemaphoreGiveRecursive() API 函数“获取”和“释放”。不得使用 xSemaphoreTake() 和 xSemaphoreGive()。
- xSemaphoreCreateMutex() 和 xSemaphoreCreateMutexStatic() 用于创建非递归互斥锁。非递归互斥锁只能被一个任务获取一次,如果同一个任务想再次获取则会失败,因为当任务第一次释放互斥锁时,互斥锁就一直处于释放状态。
- 与非递归互斥锁相反,递归互斥锁可以被同一个任务获取很多次,获取多少次就需要释放多少次,此时才会返回递归互斥锁。
- 与非递归互斥锁一样,递归互斥锁采用优先级继承算法。如果另一个优先级更高的任务试图获得相同的互斥锁,则将暂时提高“获取”互斥锁的任务的优先级。拥有互斥锁的任务“继承”试图“获取”相同互斥锁的任务的优先级。这意味着必须始终“归还”互斥锁,否则优先级较高的任务将始终无法获得互斥锁,而优先级较低的任务将永远无法“取消继承”优先级。
返回值 | 条件 |
---|---|
创建的互斥锁的句柄 | 如果已成功创建递归互斥锁 |
NULL | 如果导致递归互斥锁没有创建的原因是 无法分配保存互斥锁所需的内存 |
用法示例:
SemaphoreHandle_t xMutex;
void vATask( void * pvParameters )
{
// 创建一个递归互斥锁。
xMutex = xSemaphoreCreateRecursiveMutex();
// 如果 xMutex 不为 NULL,表示递归互斥锁创建成功,并且现在可以使用。
if( xMutex != NULL )
{
/* 递归互斥锁已成功创建,
现在可以使用。 */
}
}
(4)静态创建递归互斥锁:
函数原型:
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic(
StaticSemaphore_t *pxMutexBuffer )
作用:创建一个递归互斥锁,并返回一个可以引用该互斥锁的句柄。
- 不能在中断服务程序中使用递归互斥锁。
- configUSE_RECURSIVE_MUTEXES 和 configSUPPORT_STATIC_ALLOCATION 都必须在 FreeRTOSConfig.h 中设置为 1,xSemaphoreCreateRecursiveMutexStatic() 才可用。
- 每个递归互斥锁都需要少量 RAM 递归互斥锁的状态。如果一个互斥锁是使用 xSemaphoreCreateRecursiveMutex() 创建的,则所需的 RAM 将从 FreeRTOS 堆自动分配。如果一个递归互斥锁是使用 xSemaphoreCreateRecursiveMutexStatic() 创建的,那么 RAM 由应用程序写入器提供,这需要用到一个附加参数,但允许在编译时静态分配 RAM。
- 其他说明同动态创建递归互斥锁。
参数/返回值 | 描述 |
---|---|
参数 | pxMutexBuffer:必须指向 StaticSemaphore_t 类型的变量,该变量将用于保存互斥锁型信号量的状态。 |
返回值 | 如果已成功创建递归互斥锁,则返回创建的互斥锁的句柄。如果因为 pxMutexBuffer 为 NULL 而未创建互斥锁,则返回 NULL。 |
用法示例:
SemaphoreHandle_t xSemaphore = NULL;
StaticSemaphore_t xMutexBuffer;
void vATask( void * pvParameters )
{
/* 不使用任何动态内存分配创建一个递归互斥锁。
互斥锁的数据结构将保存到 xMutexBuffer 变量中。 */
xSemaphore = xSemaphoreCreateRecursiveMutexStatic( &xMutexBuffer );
/* pxMutexBuffer 不为 NULL,因此预期句柄也不会为 NULL。 */
configASSERT( xSemaphore );
/* 任务代码的其余部分在这里。 */
}
四、信号量控制 API :
(1)获取指定的互斥锁的任务句柄:
函数原型:
TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );
- 必须在 FreeRTOSConfig.h 中将 INCLUDE_xSemaphoreGetMutexHolder 设置为 1, 此函数才可用。
- 返回保存函数参数指定的互斥锁的任务的句柄(若有)。
- xSemaphoreGetMutexHolder () 可以可靠地用于确定调用任务是否是互斥锁持有者,但如果由调用任务之外的任务持有互斥锁,则无法可靠地使用 xSemaphoreGetMutexHolder () 。这是因为 MUTEX 支架可能会在调用该函数的调用任务与测试该函数返回值之间更改。
- configUSE_MUTEXES 必须在 FreeRTOSConfig.h 中设置为 1,xSemaphoreGetMutexHolder() 才可用。
参数/返回值 | 描述 |
---|---|
xMutex | 正在查询的互斥体的句柄。 |
保存 xMutex 参数指定的互斥锁的任务的句柄。如果在 xMutex 参数中传递的信号量不是互斥锁型信号量,或者如果互斥锁可用,但未被任何任务保存,则返回 NULL。 |
(2)返回信号量计数:
函数原型:
UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
作用:返回信号量计数。
参数/返回 | 描述 |
---|---|
xSemaphore | 正在查询的信号量的句柄。 |
返回值 | 如果信号量是计数信号量,则返回信号量的当前计数值。如果信号量是二进制信号量,则当信号量可用时,返回 1,当信号量不可用时,返回 0。 |
(3)获取信号量:
函数原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );
- 用于获取信号量的宏。信号量必须在之前已经通过调用 xSemaphoreCreateBinary()、xSemaphoreCreateMutex() 或 xSemaphoreCreateCounting()创建。
- 不得从 ISR 调用此宏。如果需要,xQueueReceiveFromISR()可用来从中断中获取一个信号量, 尽管这不是正常操作。
- 信号量使用队列作为其底层机制,因此函数在某种程度上可互操作。
参数/返回值 | 描述 |
---|---|
xSemaphore | 正在获取的信号量的句柄——在创建信号量时获得。 |
xTicksToWait | 等待信号量变为可用的时间(以滴答为单位)。宏 portTICK_PERIOD_MS 可以将其转换为实际时间。可以用一个为零的阻塞时间来轮询信号量。如果 INCLUDE_vTaskSuspend 设置为 1,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时限制)。 |
返回值 | 如果获得信号量,则返回 pdTRUE 。如果 xTicksToWait 过期,信号量不可用,则返回 pdFALSE 。 |
用法示例:
SemaphoreHandle_t xSemaphore = NULL; // 定义一个信号量句柄变量,初始化为NULL。
/* 创建信号量的任务 */
void vATask( void * pvParameters )
{
/* 创建一个信号量来保护共享资源。由于我们使用信号量进行互斥,所以我们创建一个互斥信号量而不是二进制信号量。 */
xSemaphore = xSemaphoreCreateMutex(); // 创建一个互斥信号量。
}
/* 使用信号量的任务 */
void vAnotherTask( void * pvParameters )
{
/* ... 做其他事情。 */
if( xSemaphore != NULL ) // 检查信号量是否已经被创建。
{
/* 尝试获取信号量。如果信号量不可用,等待10个时钟周期看看它是否变得可用。 */
if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE ) // 尝试获取信号量,最多等待10个时钟周期。
{
/* 我们成功获取了信号量,现在可以访问共享资源。 */
/* ... */
/* 我们已经完成了对共享资源的访问。释放信号量。 */
xSemaphoreGive( xSemaphore ); // 释放信号量。
}
else
{
/* 我们未能获取信号量,因此无法安全地访问共享资源。 */
}
}
}
(4)获取信号量(ISR):
函数原型:
xSemaphoreTakeFromISR
(
SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken
)
- 可从 ISR 调用的 xSemaphoreTake() 版本。与 xSemaphoreTake() 不同,xSemaphoreTakeFromISR() 不允许指定阻塞时间。
参数/返回值 | 描述 |
---|---|
xSemaphore | 信号量被“获取”。信号量由 SemaphoreHandle_t 类型的变量引用,必须在使用之前显式创建。 |
pxHigherPriorityTaskWoken | 信号量可能阻塞一个或多个任务,等待给出信号量。调用 xSemaphoreTakeFromISR() 将使被阻塞的任务等待信号量离开阻塞状态。如果调用 API 函数导致任务离开阻塞状态,且未阻塞任务的优先级等于或高于当前正在执行的任务(被中断的任务),那么 API 函数将从内部把 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xSemaphoreTakeFromISR() 将 *pxHigherPriorityTaskWoken 设置为 pdTRUE ,则应在退出中断之前执行上下文切换。这将确保中断直接返回到最高优先级的就绪状态任务。该机制与 xQueueReceiveFromISR() 函数中使用的机制相同,读者可以参考 xQueueReceiveFromISR() 文档以获得进一步解释。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 是一个可选参数,可设置为 NULL 。 |
返回值 | 如果成功获取信号量,则返回 pdTRUE 。如果因为信号量不可用而未成功获取信号量,则返回 pdFALSE 。 |
(5)递归获取互斥锁信号量:
函数原型:
xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex,
TickType_t xTicksToWait );
- 递归地获得或“获取”一个互斥锁型信号量的宏。 此互斥锁必须已经事先通过调用 xSemaphoreCreateRecursiveMutex() 完成创建;
- 必须在 FreeRTOSConfig.h 中将 configUSE_RECURSIVE_MUTEXES 设置为 1, 此宏才可用。
- 不得在使用 xSemaphoreCreateMutex() 创建的互斥锁上使用此宏。
- 所有者可以反复“获取”递归使用的互斥锁。在所有者为每个成功的“获取”请求调用 xSemaphoreGiveRecursive() 之前,该互斥锁不会再次变得可用。例如, 如果一个任务成功地“获取”了同一个互斥锁 5 次, 那么任何其他任务都无法使用此互斥锁, 直到任务也把这个互斥锁“解锁”5 次。
参数 | 描述 |
---|---|
xMutex | 正在获得的互斥锁的句柄。这是由 xSemaphoreCreateRecursiveMutex() 返回的句柄。 |
xTicksToWait | 等待信号量变为可用的时间(以滴答为单位)。可以使用 portTICK_PERIOD_MS 宏将其转换为实际时间。可以用一个为零的阻塞时间来轮询信号量。如果任务已有信号量,则无论 xTicksToWait 的值是多少,xSemaphoreTakeRecursive() 都将立即返回。 |
返回值 | 如果获得信号量,则返回 pdTRUE ;如果 xTicksToWait 过期,信号量不可用,则返回 pdFALSE 。 |
用法示例:
SemaphoreHandle_t xMutex = NULL; // 定义一个信号量句柄变量,初始化为NULL。
// 创建互斥锁的任务
void vATask( void * pvParameters )
{
// 创建一个互斥锁来保护共享资源。
xMutex = xSemaphoreCreateRecursiveMutex(); // 创建一个可递归的互斥锁。
}
// 使用互斥锁的任务
void vAnotherTask( void * pvParameters )
{
// ... 执行其他任务。
if( xMutex != NULL ) // 检查互斥锁是否已经被创建。
{
// 尝试获取互斥锁。如果互斥锁不可用,等待10个时钟周期看看它是否变得可用。
if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE ) // 尝试获取互斥锁,最多等待10个时钟周期。
{
// 我们成功获取了互斥锁,现在可以访问共享资源。
// ...
// 由于代码的特性,可能会进一步调用 xSemaphoreTakeRecursive() 来获取同一个互斥锁。
// 在实际代码中,这些调用不会是简单的连续调用,而是可能嵌套在更复杂的调用结构中。
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ); // 再次获取互斥锁
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ); // 再次获取互斥锁
// 现在互斥锁已经被“获取”三次,所以在释放三次之前,其他任务无法获取它。
// 同样,在实际代码中,这些调用不会是简单的连续调用,而是可能嵌套在更复杂的调用结构中。
// 这里只是为了演示。
xSemaphoreGiveRecursive( xMutex ); // 释放互斥锁
xSemaphoreGiveRecursive( xMutex ); // 释放互斥锁
xSemaphoreGiveRecursive( xMutex ); // 释放互斥锁
// 现在其他任务可以获取互斥锁。
}
else
{
// 我们未能获取互斥锁,因此无法安全地访问共享资源。
}
}
}
(6)释放信号量:
函数原型:
xSemaphoreGive( SemaphoreHandle_t xSemaphore );
- 用于释放信号量的宏。释放前信号量必须已经通过调用 xSemaphoreCreateBinary()、xSemaphoreCreateMutex() 或 xSemaphoreCreateCounting()创建。
- 不得在 ISR 中使用此宏。可以参考 xSemaphoreGiveFromISR(),可以从 ISR 中使用的替代方案。
- 此宏也不得用于使用 xSemaphoreCreateRecursiveMutex() 创建的信号量。
参数/返回值 | 描述 |
---|---|
xSemaphore | 要释放的信号量的句柄。这是创建信号量时返回的句柄。 |
返回值 | 如果信号量被释放,则返回 pdTRUE 。如果发生错误,则返回 pdFALSE 。信号量是使用队列实现的。发布消息时,如果队列上没有空间,那么可能会发生错误,这表明最初未能正确获取信号量。 |
用法示例:
SemaphoreHandle_t xSemaphore = NULL; // 定义一个信号量句柄变量,初始化为NULL。
void vATask( void * pvParameters )
{
// 创建一个信号量来保护共享资源。由于我们使用信号量进行互斥,所以我们创建一个互斥信号量而不是二进制信号量。
xSemaphore = xSemaphoreCreateMutex(); // 创建一个互斥信号量。
if( xSemaphore != NULL ) // 检查信号量是否创建成功。
{
if( xSemaphoreGive( xSemaphore ) != pdTRUE ) // 尝试释放信号量。
{
// 我们预期这个调用会失败,因为我们不能在没有先“获取”信号量的情况下释放它!
}
// 获取信号量 - 如果信号量不可立即获得,则不阻塞。
if( xSemaphoreTake( xSemaphore, ( TickType_t ) 0 ) ) // 尝试立即获取信号量。
{
// 我们现在拥有信号量,可以访问共享资源。
// ...
// 我们已经完成了对共享资源的访问,所以可以释放信号量。
if( xSemaphoreGive( xSemaphore ) != pdTRUE ) // 释放信号量。
{
// 我们不预期这个调用会失败,因为我们必须已经获取了信号量才能到达这里。
}
}
}
}
(7)递归释放互斥锁信号量:
函数原型:
xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex )
- 递归地释放或“给出”一个互斥锁型信号量的宏。 此互斥锁必须已经事先通过调用 xSemaphoreCreateRecursiveMutex() 完成创建;
- 必须在 FreeRTOSConfig.h 中将 configUSE_RECURSIVE_MUTEXES 设置为 1, 此宏才可用。
- 不得在使用 xSemaphoreCreateMutex() 创建的互斥锁上使用此宏。
- 所有者可以反复“获取”递归使用的互斥锁。在所有者为每个成功的“获取”请求调用 xSemaphoreGiveRecursive() 之前,该互斥锁不会再次变得可用。例如, 如果一个任务成功地“获取”了同一个互斥锁 5 次, 那么任何其他任务都无法使用此互斥锁, 直到任务也把这个互斥锁“解锁”5 次。
参数/描述 | 说明 |
---|---|
xMutex | 正在释放或“给出”的互斥锁的句柄。这是由 xSemaphoreCreateRecursiveMutex() 返回的句柄。 |
返回值 | 如果成功给出信号量,则返回 pdTRUE 。 |
用法示例:
// 声明一个互斥量句柄,初始值为NULL,表示尚未创建互斥量。
SemaphoreHandle_t xMutex = NULL;
// 创建互斥量的任务
void vATask( void * pvParameters )
{
// 创建一个递归互斥量,用于保护共享资源。
// 递归互斥量允许同一个任务多次获取同一个互斥量,
// 但必须相应地多次释放它。
xMutex = xSemaphoreCreateRecursiveMutex();
}
// 使用互斥量的任务
void vAnotherTask( void * pvParameters )
{
// ... 执行其他任务逻辑。
// 检查互斥量是否已经创建。
if( xMutex != NULL )
{
// 尝试获取互斥量。如果互斥量当前不可用,
// 等待最多10个系统滴答(tick)时间,看看它是否会变为空闲。
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
{
// 无法获取互斥量,因此无法安全地访问共享资源。
}
}
}
(8)释放信号量(ISR):
函数原型:
xSemaphoreGiveFromISR
(
SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken
)
- 用于释放信号量的宏。释放前信号量必须已经 通过调用 xSemaphoreCreateBinary() 或 xSemaphoreCreateCounting() 创建。
- 互斥锁型信号量(那些调用 xSemaphoreCreateMutex() 创建的信号量) 不得与此宏一起使用。
- 此宏可在 ISR 中使用。
参数/返回 | 描述 |
---|---|
xSemaphore | 要释放的信号量的句柄。这是创建信号量时返回的句柄。 |
pxHigherPriorityTaskWoken | 如果给出信号量会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xSemaphoreGiveFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xSemaphoreGiveFromISR() 将此值设置为 pdTRUE ,则应在退出中断之前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL 。 |
返回值 | 如果成功给出信号量,则返回 pdTRUE ,否则返回 errQUEUE_FULL 。 |
用法示例:
#define LONG_TIME 0xffff // 定义一个常量,表示等待信号量的最长时间
#define TICKS_TO_WAIT 10 // 定义一个常量,表示等待的滴答数
SemaphoreHandle_t xSemaphore = NULL; // 声明一个信号量句柄,初始值为NULL
/* 重复执行的任务 */
void vATask( void * pvParameters )
{
/* 我们使用信号量进行同步,因此创建一个二进制信号量而不是互斥量。
必须确保在创建信号量之前中断不会尝试使用它! */
xSemaphore = xSemaphoreCreateBinary(); // 创建一个二进制信号量
for( ;; ) // 无限循环
{
/* 我们希望这个任务每10个定时器滴答执行一次。信号量在启动此任务之前创建。
阻塞等待信号量变为可用。 */
if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE ) // 如果成功获取信号量
{
/* 执行任务。 */
...
/* 任务执行完毕。返回循环顶部,在那里我们将阻塞信号量,直到再次执行。
注意,当以这种方式使用信号量与ISR进行同步时,无需“释放”信号量。 */
}
}
}
/* 定时器ISR */
void vTimerISR( void * pvParameters )
{
static unsigned char ucLocalTickCount = 0; // 静态变量,用于计数滴答数
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 用于指示是否唤醒了更高优先级的任务
/* 定时器滴答发生。 */
... 执行其他定时器功能。
/* 是否到了vATask()执行的时间? */
xHigherPriorityTaskWoken = pdFALSE; // 初始化为pdFALSE
ucLocalTickCount++; // 增加计数
if( ucLocalTickCount >= TICKS_TO_WAIT ) // 如果计数达到指定的滴答数
{
/* 通过释放信号量来解除任务的阻塞。 */
xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken ); // 释放信号量
/* 重置计数,以便在10个滴答时间后再次释放信号量。 */
ucLocalTickCount = 0;
}
/* 如果xHigherPriorityTaskWoken为true,则进行上下文切换。实际使用的宏是特定于端口的。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); // 请求上下文切换(如果需要)
}
(9)删除信号量:
函数原型:
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
- 删除信号量,包括互斥锁型信号量和递归信号量。
- 请勿删除已有阻塞任务的信号量(正在等待信号灯可用的阻塞状态任务)。
参数 | 描述 |
---|---|
xSemaphore | 被删除的信号量的句柄。 |
五、互斥锁与递归互斥锁使用示例:
(1)动态创建互斥锁:
- 每个任务在访问共享资源之前,都会尝试通过
xSemaphoreTake()
获取互斥锁。 - 如果获取成功,任务会修改共享资源,并通过
printf
打印当前共享资源的值。 - 任务完成对共享资源的操作后,通过
xSemaphoreGive()
释放互斥锁。 - 任务之间通过互斥锁确保对共享资源的访问是互斥的,避免了数据竞争问题。
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 动态创建互斥锁,通过互斥锁来同步对共享资源的访问,
* 并使用 printf 打印互斥锁的使用情况。
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
// 全局变量,作为共享资源
int sharedResource = 0;
// 互斥锁句柄
xSemaphoreHandle mutexHandle = NULL;
// 任务1函数
void Task1(void *pvParameters) {
while (1) {
// 尝试获取互斥锁
if (xSemaphoreTake(mutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取互斥锁成功,访问共享资源
sharedResource++;
printf("Task1: Shared Resource = %d\n", sharedResource);
// 释放互斥锁
xSemaphoreGive(mutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务2函数
void Task2(void *pvParameters) {
while (1) {
// 尝试获取互斥锁
if (xSemaphoreTake(mutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取互斥锁成功,访问共享资源
sharedResource++;
printf("Task2: Shared Resource = %d\n", sharedResource);
// 释放互斥锁
xSemaphoreGive(mutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 主函数
int main(void) {
Uart_Init(115200); // 初始化串口
DMA1_Init(); // 初始化 DMA(如果需要)
// 动态创建互斥锁
mutexHandle = xSemaphoreCreateMutex();
if (mutexHandle == NULL) {
printf("Failed to create mutex.\n");
while (1); // 错误处理
}else{
printf("Create a mutex dynamically.\n");
}
// 创建任务
xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动后,程序不会执行到这里
return 0;
}
(2)静态创建互斥锁:
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 静态创建互斥锁,通过互斥锁来同步对共享资源的访问,
* 并使用 printf 打印互斥锁的使用情况。
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
// 全局变量,作为共享资源
int sharedResource = 0;
// 静态分配的互斥锁控制块
StaticSemaphore_t mutexBuffer;
xSemaphoreHandle mutexHandle = NULL;
// 任务1函数
void Task1(void *pvParameters) {
while (1) {
// 尝试获取互斥锁
if (xSemaphoreTake(mutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取互斥锁成功,访问共享资源
sharedResource++;
printf("Task1: Shared Resource = %d\n", sharedResource);
// 释放互斥锁
xSemaphoreGive(mutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务2函数
void Task2(void *pvParameters) {
while (1) {
// 尝试获取互斥锁
if (xSemaphoreTake(mutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取互斥锁成功,访问共享资源
sharedResource++;
printf("Task2: Shared Resource = %d\n", sharedResource);
// 释放互斥锁
xSemaphoreGive(mutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 主函数
int main(void) {
Uart_Init(115200); // 初始化串口
DMA1_Init(); // 初始化 DMA(如果需要)
// 静态创建互斥锁
mutexHandle = xSemaphoreCreateMutexStatic(&mutexBuffer);
if (mutexHandle == NULL) {
printf("Failed to create mutex.\n");
while (1); // 错误处理
}
// 创建任务
xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动后,程序不会执行到这里
return 0;
}
(3)动态创建递归互斥锁:
- 每个任务在访问共享资源之前,都会尝试通过
xSemaphoreTakeRecursive()
获取递归互斥锁。 - 如果获取成功,任务会修改共享资源,并通过
printf
打印当前共享资源的值。 - 为了展示递归互斥锁的效果,任务中还模拟了嵌套调用,再次尝试获取同一个递归互斥锁。
- 每次获取递归互斥锁后,任务都会在完成操作后通过
xSemaphoreGiveRecursive()
释放互斥锁。
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 动态创建递归互斥锁,实现对共享资源的同步访问,
* 并展示递归互斥锁在嵌套调用中的效果。
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
// 全局变量,作为共享资源
int sharedResource = 0;
// 递归互斥锁句柄
xSemaphoreHandle recursiveMutexHandle = NULL;
// 任务1函数
void Task1(void *pvParameters) {
while (1) {
// 尝试获取递归互斥锁
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取递归互斥锁成功,访问共享资源
sharedResource++;
printf("Task1: Shared Resource = %d\n", sharedResource);
// 模拟嵌套调用
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
sharedResource++;
printf("Task1 (nested): Shared Resource = %d\n", sharedResource);
// 释放嵌套获取的互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 释放递归互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务2函数
void Task2(void *pvParameters) {
while (1) {
// 尝试获取递归互斥锁
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取递归互斥锁成功,访问共享资源
sharedResource++;
printf("Task2: Shared Resource = %d\n", sharedResource);
// 模拟嵌套调用
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
sharedResource++;
printf("Task2 (nested): Shared Resource = %d\n", sharedResource);
// 释放嵌套获取的互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 释放递归互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 主函数
int main(void) {
Uart_Init(115200); // 初始化串口
DMA1_Init(); // 初始化 DMA(如果需要)
// 动态创建递归互斥锁
recursiveMutexHandle = xSemaphoreCreateRecursiveMutex();
if (recursiveMutexHandle == NULL) {
printf("Failed to create recursive mutex.\n");
while (1); // 错误处理
}else{
printf("Create recursive mutex dynamically.\n");
}
// 创建任务
xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动后,程序不会执行到这里
return 0;
}
(4)静态创建递归互斥锁:
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 静态创建递归互斥锁,实现对共享资源的同步访问,
* 并展示递归互斥锁在嵌套调用中的效果。
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
// 全局变量,作为共享资源
int sharedResource = 0;
// 静态分配的递归互斥锁控制块
StaticSemaphore_t recursiveMutexBuffer;
xSemaphoreHandle recursiveMutexHandle = NULL;
// 任务1函数
void Task1(void *pvParameters) {
while (1) {
// 尝试获取递归互斥锁
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取递归互斥锁成功,访问共享资源
sharedResource++;
printf("Task1: Shared Resource = %d\n", sharedResource);
// 模拟嵌套调用
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
sharedResource++;
printf("Task1 (nested): Shared Resource = %d\n", sharedResource);
// 释放嵌套获取的互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 释放递归互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务2函数
void Task2(void *pvParameters) {
while (1) {
// 尝试获取递归互斥锁
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
// 获取递归互斥锁成功,访问共享资源
sharedResource++;
printf("Task2: Shared Resource = %d\n", sharedResource);
// 模拟嵌套调用
if (xSemaphoreTakeRecursive(recursiveMutexHandle, portMAX_DELAY) == pdTRUE) {
sharedResource++;
printf("Task2 (nested): Shared Resource = %d\n", sharedResource);
// 释放嵌套获取的互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 释放递归互斥锁
xSemaphoreGiveRecursive(recursiveMutexHandle);
}
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 主函数
int main(void) {
Uart_Init(115200); // 初始化串口
DMA1_Init(); // 初始化 DMA(如果需要)
// 静态创建递归互斥锁
recursiveMutexHandle = xSemaphoreCreateRecursiveMutexStatic(&recursiveMutexBuffer);
if (recursiveMutexHandle == NULL) {
printf("Failed to create recursive mutex.\n");
while (1); // 错误处理
}else{
printf("Create a recursive mutex statically.\n");
}
// 创建任务
xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动后,程序不会执行到这里
return 0;
}
六、FreeRTOS教程示例代码下载:
通过网盘分享的文件:FreeRTOS教程示例代码