5分钟学习笔记(FreeRTOS)(五)
2026/7/8 3:04:00 网站建设 项目流程

1.互斥量

互斥量 Mutex 主要用于保护共享资源,实现对临界资源的独占访问。



当一个任务成功获取互斥量后,该任务获得互斥量的所有权,此时互斥量处于被占用状态,其他任务不能再获取该互斥量,只能阻塞等待或超时返回。

互斥量和二值信号量区别之一就是:

互斥量有所有权。 二值信号量没有所有权概念。

谁 Take 到 Mutex,谁就是 owner。

只有 owner 应该 Give 这个 Mutex。

当持有互斥量的任务访问完共享资源后,应释放互斥量,使互斥量重新变为可用状态。互斥量应该由持有它的任务释放,不能由其他任务随意释放。


普通互斥量不支持同一任务重复获取。如果需要同一个任务多次获取同一个锁,应使用递归互斥量,并且 Take 多少次就必须 Give 多少次。

1)互斥信号量

普通 Mutex 创建:

SemaphoreHandle_t printMutex; printMutex = xSemaphoreCreateMutex();

使用:

xSemaphoreTake(printMutex, portMAX_DELAY); /* 访问共享资源 */ xSemaphoreGive(printMutex);

如果同一个任务已经拿到了这个普通 Mutex,又再次执行:

xSemaphoreTake(printMutex, portMAX_DELAY);

就可能把自己阻塞住,造成死锁。

比如:

void TaskA(void *argument) { xSemaphoreTake(printMutex, portMAX_DELAY); // 已经拿到 mutex 了 xSemaphoreTake(printMutex, portMAX_DELAY); // 这里可能死锁 xSemaphoreGive(printMutex); xSemaphoreGive(printMutex); }

普通互斥量不适合这种重复加锁。

2)递归互斥量

递归互斥量创建:

SemaphoreHandle_t recursiveMutex; recursiveMutex = xSemaphoreCreateRecursiveMutex();

递归获取:

xSemaphoreTakeRecursive(recursiveMutex, portMAX_DELAY); xSemaphoreTakeRecursive(recursiveMutex, portMAX_DELAY);

释放时也要释放相同次数:

xSemaphoreGiveRecursive(recursiveMutex); xSemaphoreGiveRecursive(recursiveMutex);

也就是说:

普通 Mutex:
同一个任务不应该重复 Take。

递归 Mutex:
同一个任务可以重复 Take,但必须 Give 相同次数。

互斥量通过优先级继承机制,缓解优先级反转问题。

因为 Mutex 的优先级继承主要解决一种典型情况:

低优先级任务拿着 Mutex
高优先级任务等待这个 Mutex
中优先级任务一直抢占低优先级任务
导致高优先级任务迟迟拿不到资源

这就是优先级反转。

用了 Mutex 后:

高优先级任务等待 Mutex ↓ 低优先级任务临时继承高优先级 ↓ 低优先级任务尽快运行并释放 Mutex ↓ 高优先级任务拿到 Mutex

互斥量不能在中断中使用

互斥量是给任务用的,不给 ISR 用。

不要在中断里写:

xSemaphoreTake(uartMutex, 0); xSemaphoreGive(uartMutex);

原因:1.Mutex 有所有权和优先级继承机制;2.中断不是任务;3.中断没有“任务所有权”概念;

中断通知任务,用:任务通知、二值信号量 FromISR 或队列 FromISR。

保护共享资源,用:Mutex


2.互斥量优先级继承

Mutex 的优先级继承机制用于缓解优先级反转问题。那么什么是优先级反转呢

优先级反转就是:当一个低优先级任务持有某个 Mutex,而高优先级任务也想获取这个 Mutex 时,高优先级任务会因为资源被占用而进入阻塞状态。此时如果存在中优先级任务不断抢占低优先级任务,就会导致低优先级任务迟迟无法运行并释放 Mutex,高优先级任务也一直无法继续运行。


为了解决这个问题,FreeRTOS 的 Mutex 支持优先级继承。当高优先级任务阻塞等待某个 Mutex 时,当前持有该 Mutex 的低优先级任务会临时继承等待任务中的最高优先级,使它能够尽快运行并释放 Mutex。当它释放 Mutex 后,它的优先级会恢复到原来的基础优先级。

优先级继承是 Mutex 的特性,二值信号量不具备这个机制。因此保护共享资源时应该优先使用 Mutex,而不是二值信号量。

一句话形容优先级继承就是:谁挡住了高优先级任务,谁就临时升到高优先级,赶紧把锁释放。


3.互斥量为什么不能当信号量使用

原因 1:Mutex 有所有权

Mutex 是“谁 Take,谁 Give”。

例如:

xSemaphoreTake(uartMutex, portMAX_DELAY); /* 使用 UART */ xSemaphoreGive(uartMutex);

这个 Mutex 是当前任务拿到的,就应该由当前任务释放。

但是二值信号量常见用法是:中断Give,任务Take

这个场景中,Give 的不是同一个任务,甚至是 ISR。
这对二值信号量是正常用法,但对 Mutex 不合适。


原因 2:Mutex 不能在中断中使用

你不能在 ISR 里这样用 Mutex:

xSemaphoreGive(uartMutex); // 不推荐/不应在ISR中使用 xSemaphoreTake(uartMutex, 0); // 不应在ISR中使用

因为中断不是任务,没有 Mutex 所有权概念。

如果是中断通知任务,应该用:

xSemaphoreGiveFromISR(binarySem, &xHigherPriorityTaskWoken);

或者更推荐:

vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken);

Mutex 是保护资源的,不是通知事件的

Mutex 解决的问题是:这个资源一次只能一个任务用。

例如:

多个任务同时 printf
多个任务同时访问 I2C
多个任务同时访问 SPI
多个任务同时访问 Flash

这时候用 Mutex。

二值信号量解决的问题是:某个事件发生了,通知任务去处理。

4多任务printf

1. 多个任务同时 printf 会怎样?

比如有两个任务:

TaskA: printf("TaskA: motor start\r\n"); TaskB: printf("TaskB: adc value = 123\r\n");

如果没有 Mutex 保护,可能输出成这样:

TaskA: moTaskB: adc value = tor start 123

或者:

TaskA: motor TaskB: adc value = 123 start

因为printf()不是一个瞬间完成的动作。

它内部可能经历:

格式化字符串 ↓ 写入缓冲区 ↓ 调用串口发送 ↓ 一个字节一个字节发出去

在这个过程中,FreeRTOS 可能发生任务切换。

所以 TaskA 还没打印完,TaskB 就插进来了,最终串口输出就会乱。

2. 正确做法:用普通 Mutex 保护 printf

应该设计一个统一的打印接口:

SemaphoreHandle_t printfMutex; void Debug_Init(void) { printfMutex = xSemaphoreCreateMutex(); } void Debug_Printf(const char *str) { if(xSemaphoreTake(printfMutex, portMAX_DELAY) == pdPASS) { printf("%s", str); xSemaphoreGive(printfMutex); } }

如果Debug_Printf拿到 printfMutex 后不释放
其他任务再想 printf
都会阻塞在 xSemaphoreTake()
整个打印功能相当于被 Debug_Printf锁死

普通 Mutex 要求 Take 和 Give 成对出现,不建议同一任务重复获取同一个 Mutex。

3.如果同一个任务因为函数嵌套需要多次获取同一个锁,应使用递归互斥量,并且 TakeRecursive 几次就必须 GiveRecursive 几次。

比如:

void Debug_Printf(const char *str) { xSemaphoreTakeRecursive(debugMutex, portMAX_DELAY); printf("%s", str); xSemaphoreGiveRecursive(debugMutex); } void Motor_PrintStatus(void) { xSemaphoreTakeRecursive(debugMutex, portMAX_DELAY); Debug_Printf("motor status\r\n"); xSemaphoreGiveRecursive(debugMutex); }

5.串口调试打印示例

#include "FreeRTOS.h" #include "semphr.h" #include "usart.h" #include <stdio.h> #include <stdarg.h> #include <string.h> #define DEBUG_PRINTF_BUF_SIZE 256 static SemaphoreHandle_t g_debug_printf_mutex = NULL; static char g_debug_printf_buf[DEBUG_PRINTF_BUF_SIZE]; /* Debug_Printf 初始化 */ void Debug_Printf_Init(void) { g_debug_printf_mutex = xSemaphoreCreateMutex(); if(g_debug_printf_mutex == NULL) { /* Mutex 创建失败,通常是 FreeRTOS heap 不够 */ Error_Handler(); } } /* 统一打印接口 */ void Debug_Printf(const char *fmt, ...) { va_list args; int len; if(g_debug_printf_mutex == NULL) { return; } /* 获取 Mutex。 如果其他任务正在 printf,本任务会阻塞等待。 */ if(xSemaphoreTake(g_debug_printf_mutex, portMAX_DELAY) == pdPASS) { va_start(args, fmt); len = vsnprintf(g_debug_printf_buf, DEBUG_PRINTF_BUF_SIZE, fmt, args); va_end(args); if(len > 0) { if(len > DEBUG_PRINTF_BUF_SIZE) { len = DEBUG_PRINTF_BUF_SIZE; } /* 串口发送。 这里用阻塞发送做演示。 实际工程可以改成 DMA 发送。 */ HAL_UART_Transmit(&huart1, (uint8_t *)g_debug_printf_buf, len, 100); } /* 打印完成后必须释放 Mutex。 否则其他任务会一直无法 printf。 */ xSemaphoreGive(g_debug_printf_mutex); } }

使用示例:

void TaskA(void *argument) { while(1) { Debug_Printf("TaskA running\r\n"); vTaskDelay(pdMS_TO_TICKS(1000)); } } void TaskB(void *argument) { while(1) { Debug_Printf("TaskB value = %d\r\n", 123); vTaskDelay(pdMS_TO_TICKS(500)); } }

初始化位置:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); Debug_Printf_Init(); MX_FREERTOS_Init(); osKernelStart(); while(1) { } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询