递归互斥锁

递归互斥锁

什么是递归互斥锁 递归互斥锁允许同一个任务获取相同的互斥锁,而不会发生死锁。这在一个任务需要在不同的函数中多次锁定相同资源时很有用。 如何工作? 获取锁(xSemaphoreTakeRecursive()): 当一个任务第一次调用此函数时,如果互斥锁是空闲的,它将获得互斥锁,并将锁计数(lock c

什么是递归互斥锁

递归互斥锁允许同一个任务获取相同的互斥锁,而不会发生死锁。这在一个任务需要在不同的函数中多次锁定相同资源时很有用。
如何工作?

  1. 获取锁(xSemaphoreTakeRecursive()):
    • 当一个任务第一次调用此函数时,如果互斥锁是空闲的,它将获得互斥锁,并将锁计数(lock count)设置为1
    • 如果该任务再次调用此函数,那么锁计数会增加。
  2. 释放锁(xSemaphoreGiveRecursive()):
    • 当任务调用此函数时,锁计数会减少。
    • 只有当锁计数降到0时,互斥锁才会被完全释放,变得对其他任务可用。

换句话说,同一个任务需要对xSemaphoreTakeRecursive()xSemaphoreGiveRecursive()调用相同的次数,以完全释放互斥锁。


为什么要用递归互斥锁

  • 假设有这样一种情况
    有一个任务,有两个资源( A和B ),那么这个任务会首先获得 A ,获得 A 处理之后,还需要获得 B 进行处理,处理完 B 后回到 A ,释放 B , 然后再释放 A ,再回到自己的任务。
    18. 递归互斥锁-20240616204007790.webp

如果资源非常多,按照以前的方式则需要创建非常多的互斥锁,但是用递归互斥锁,我们只需要创建一个就可以。

官方函数文档:

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();
}

因为使用普通互斥锁,有时开发者会忘了已经获取过互斥锁,再次尝试获取则容易导致死锁(任务永久阻塞、系统卡死、资源无法释放)。
所以递归互斥锁应运而生,开发者无需关心自己之前在哪里获取过递归互斥锁,只需要在同一任务函数的开始和结束部分,分别获取和释放递归互斥锁就可以。
综上所述,递归互斥锁是为了让代码逻辑更清晰,更好维护。

Comment