递归互斥锁:允许一个任务获取多次锁,而不会导致任务挂起。
多层嵌套的函数调用中保护同一个资源
在一个复杂的软件模块中,可能存在多层函数调用,而最内层和最外层的函数都需要访问同一个共享资源(如外设、全局数据结构)。为了防止在调用过程中被其他任务抢占并破坏数据,每一层都可能需要加锁。
-
问题所在:如果外层函数已经获取了普通互斥锁,然后调用了内层函数。内层函数再次尝试获取同一个锁时,会因为锁已被当前任务持有而阻塞,导致整个调用链卡死,形成死锁。
-
解决方案:使用递归互斥锁,内层函数可以安全地再次获取锁,而不会阻塞。
SemaphoreHandle_t xRecursiveMutex;
// 底层硬件访问函数
void HAL_UART_WriteByte(uint8_t data) {
// 内层函数也需要加锁,确保操作的原子性
if (xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE) {
// 执行实际的UART寄存器操作
// USART_SendData(USART1, data);
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
// 上层逻辑函数
void vSendMessage(char *msg) {
// 外层函数先获取锁
if (xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE) {
// 发送字符串的每个字节
while (*msg) {
// 调用内层函数,它会再次获取同一个锁
HAL_UART_WriteByte(*msg++);
}
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
NOTE:
void HAL_UART_WriteByte(uint8_t data) 担心其他的地方会调用这个接口,导致最上层的没有xSemaphoreTakeRecursive没有锁住公共部分。这个时候就需要递归互斥锁。
递归函数中需要加锁
这是最典型、最直观的应用场景。当一个函数直接或间接地调用自身(递归)时,如果该函数内部需要获取同一个互斥锁来保护共享资源,就必须使用递归互斥锁。
-
问题所在 :如果在递归函数中使用普通互斥锁,第一次调用时成功获取了锁,但在锁还未释放时,函数又调用了自身。第二次调用会尝试再次获取同一个锁,因为该锁已被当前任务持有,普通互斥锁会导致任务永久阻塞,即死锁。
-
解决方案:递归互斥锁允许同一个任务多次"加锁",只需确保"解锁"次数与"加锁"次数相同即可。
// 假设有一个递归互斥锁句柄
SemaphoreHandle_t xRecursiveMutex;
// 递归函数,例如遍历树形结构
void vRecursiveFunction(int depth) {
// 每次进入函数都获取锁
if (xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE) {
// --- 临界区开始 ---
// 安全地访问或修改受保护的共享数据结构
// ...
// --- 临界区结束 ---
if (depth > 0) {
// 递归调用自身,此时会再次获取锁,但不会死锁
vRecursiveFunction(depth - 1);
}
// 每次退出函数都释放锁
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}