什么是递归互斥锁
递归互斥锁允许同一个任务获取相同的互斥锁,而不会发生死锁。这在一个任务需要在不同的函数中多次锁定相同资源时很有用。
如何工作?
- 获取锁(
xSemaphoreTakeRecursive()
):- 当一个任务第一次调用此函数时,如果互斥锁是空闲的,它将获得互斥锁,并将锁计数(
lock count
)设置为1
。 - 如果该任务再次调用此函数,那么锁计数会增加。
- 当一个任务第一次调用此函数时,如果互斥锁是空闲的,它将获得互斥锁,并将锁计数(
- 释放锁(
xSemaphoreGiveRecursive()
):- 当任务调用此函数时,锁计数会减少。
- 只有当锁计数降到
0
时,互斥锁才会被完全释放,变得对其他任务可用。
换句话说,同一个任务需要对
xSemaphoreTakeRecursive()
和xSemaphoreGiveRecursive()
调用相同的次数,以完全释放互斥锁。
为什么要用递归互斥锁
- 假设有这样一种情况
有一个任务,有两个资源( A和B ),那么这个任务会首先获得 A ,获得 A 处理之后,还需要获得 B 进行处理,处理完 B 后回到 A ,释放 B , 然后再释放 A ,再回到自己的任务。
如果资源非常多,按照以前的方式则需要创建非常多的互斥锁,但是用递归互斥锁,我们只需要创建一个就可以。
官方函数文档:
Recursive Mutexes
[[FreeRTOS_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf#page=279&selection=22,0,22,17|FreeRTOS_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide, page 279]]
xSemaphoreCreateRecursiveMutex()
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
函数说明:
- 创建一个递归互斥锁。
- RAM needed.
参数:
- 无
返回值:
NULL:
堆内存不足,无法创建递归互斥锁。Any other value:
递归互斥锁成功创建。
注意事项:
- 只能用
xSemaphoreTakeRecursive()
函数获取( Take )递归互斥锁。 - 可以嵌套调用
xSemaphoreTakeRecursive()
函数。因此,一旦任务成功获取互斥锁,同一个任务依然会成功再次获取这个互斥锁。 - 如果要使用递归互斥锁,那么任务需要对
xSemaphoreTakeRecursive()
和xSemaphoreGiveRecursive()
进行相同次数的调用,以确保互斥锁对其他任务可用。 - 与标准互斥锁一样,递归互斥锁一次只能由单个任务 持有/获取。
xSemaphoreTakeRecursive():
#include "FreeRTOS.h"
#include "semphr.h"
BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex, TickType_t xTicksToWait );
函数说明:
- 获得递归互斥锁。
参数:
xMute:
准备获取的互斥锁。xTicksToWait:
超时等待时间。
返回值:
pdPASS:
成功获得递归互斥锁。pdFAIL:
获取递归互斥锁失败。
xSemaphoreGiveRecursive():
#include "FreeRTOS.h"
#include "semphr.h"
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex );
函数说明:
- 释放递归互斥锁。
参数:
xMute:
准备释放的互斥锁。
返回值:
pdPASS:
成功释放递归互斥锁。pdFAIL:
释放递归互斥锁失败。
示例代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/semphr.h"
// 创建递归互斥锁句柄
static SemaphoreHandle_t recursive_mutex;
// 开发者不需要关心之前在哪里获取过递归互斥锁,只要在当前“读”操作中,获取然后释放锁就可以
void write_sd()
{
xSemaphoreTakeRecursive(recursive_mutex, portMAX_DELAY);
// write operation
xSemaphoreGiveRecursive(recursive_mutex);
}
// 开发者不需要关心之前在哪里获取过递归互斥锁,只要在当前“写”操作中,获取然后释放锁就可以
void read_sd()
{
xSemaphoreTakeRecursive(recursive_mutex, portMAX_DELAY);
// read operation
xSemaphoreGiveRecursive(recursive_mutex);
}
void task_1(void *pvPara)
{
while (true) {
printf("[TASK 1]: Task 1 beign.\n");
write_sd();
}
}
void task_2(void *pvPara)
{
// 因为两个任务的优先级一样,所以要在 Task 2 一开始进行阻塞,保证 Task 1 可以运行
vTaskDelay(pdMS_TO_TICKS(300));
while (true) {
printf("[TASK 2]: Task 2 begin.\n");
read_sd();
}
}
void app_main(void)
{
// 创建递归互斥锁
recursive_mutex = xSemaphoreCreateRecursiveMutex();
vTaskSuspendAll();
xTaskCreate(task_1, "Task 1", 2048, NULL, 1, NULL);
xTaskCreate(task_2, "Task 2", 2048, NULL, 1, NULL);
xTaskResumeAll();
}
因为使用普通互斥锁,有时开发者会忘了已经获取过互斥锁,再次尝试获取则容易导致死锁(任务永久阻塞、系统卡死、资源无法释放)。
所以递归互斥锁应运而生,开发者无需关心自己之前在哪里获取过递归互斥锁,只需要在同一任务函数的开始和结束部分,分别获取和释放递归互斥锁就可以。
综上所述,递归互斥锁是为了让代码逻辑更清晰,更好维护。