5分钟学习笔记(FreeRTOS)(五)

📅 2026/7/8 3:04:17
5分钟学习笔记(FreeRTOS)(五)
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。保护共享资源用Mutex2.互斥量优先级继承Mutex 的优先级继承机制用于缓解优先级反转问题。那么什么是优先级反转呢优先级反转就是当一个低优先级任务持有某个 Mutex而高优先级任务也想获取这个 Mutex 时高优先级任务会因为资源被占用而进入阻塞状态。此时如果存在中优先级任务不断抢占低优先级任务就会导致低优先级任务迟迟无法运行并释放 Mutex高优先级任务也一直无法继续运行。为了解决这个问题FreeRTOS 的 Mutex 支持优先级继承。当高优先级任务阻塞等待某个 Mutex 时当前持有该 Mutex 的低优先级任务会临时继承等待任务中的最高优先级使它能够尽快运行并释放 Mutex。当它释放 Mutex 后它的优先级会恢复到原来的基础优先级。优先级继承是 Mutex 的特性二值信号量不具备这个机制。因此保护共享资源时应该优先使用 Mutex而不是二值信号量。一句话形容优先级继承就是谁挡住了高优先级任务谁就临时升到高优先级赶紧把锁释放。3.互斥量为什么不能当信号量使用原因 1Mutex 有所有权Mutex 是“谁 Take谁 Give”。例如xSemaphoreTake(uartMutex, portMAX_DELAY); /* 使用 UART */ xSemaphoreGive(uartMutex);这个 Mutex 是当前任务拿到的就应该由当前任务释放。但是二值信号量常见用法是中断Give,任务Take这个场景中Give 的不是同一个任务甚至是 ISR。这对二值信号量是正常用法但对 Mutex 不合适。原因 2Mutex 不能在中断中使用你不能在 ISR 里这样用 MutexxSemaphoreGive(uartMutex); // 不推荐/不应在ISR中使用 xSemaphoreTake(uartMutex, 0); // 不应在ISR中使用因为中断不是任务没有 Mutex 所有权概念。如果是中断通知任务应该用xSemaphoreGiveFromISR(binarySem, xHigherPriorityTaskWoken);或者更推荐vTaskNotifyGiveFromISR(taskHandle, xHigherPriorityTaskWoken);Mutex 是保护资源的不是通知事件的Mutex 解决的问题是这个资源一次只能一个任务用。例如多个任务同时 printf多个任务同时访问 I2C多个任务同时访问 SPI多个任务同时访问 Flash这时候用 Mutex。二值信号量解决的问题是某个事件发生了通知任务去处理。4多任务printf1. 多个任务同时 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) { } }