互斥量大纲
https://blog.youkuaiyun.com/SeventeenChen/article/details/155699344?spm=1011.2415.3001.5331
这一章的核心在于解决“多个任务争夺同一个资源”时可能引发的冲突问题,并介绍了FreeRTOS如何通过优先级继承机制来解决“优先级反转”这一严重的实时性问题。
13.1 互斥量的使用场合

13.2 互斥量函数
互斥量是一种特殊的二进制信号量。
使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量
0. 定义
要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:
#define configUSE_MUTEXES 1
1. 创建

SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();
/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
2.获取Take

/* 获得 */
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
3.释放Give

/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
4.删除
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
5. 完整使用示例:保护串口打印
场景描述: 假设我们有两个任务 Task1 和 Task2,它们都想通过串口发送数据。如果不加锁,它们的数据会混杂在一起(例如 He1ll2o)。我们使用互斥量来保证打印的完整性。
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h" // 必须包含这个头文件
#include <stdio.h>
/* 1. 定义一个互斥量句柄 (全局变量) */
SemaphoreHandle_t xSerialMutex = NULL;
/* 模拟底层的串口发送函数(共享资源) */
void Serial_Print(char *str) {
// 这里并没有锁,只是纯粹的发送逻辑
printf("%s", str);
}
/* 2. 封装一个带锁的打印函数 (推荐做法) */
/* 这样任务只需要调用这个函数,不用关心锁的细节 */
void ThreadSafe_Print(char *msg) {
// 检查互斥量是否创建成功
if (xSerialMutex != NULL) {
// --- 尝试获取锁 ---
// 参数2: portMAX_DELAY 表示如果锁被占用,我会一直等到它释放
if (xSemaphoreTake(xSerialMutex, portMAX_DELAY) == pdTRUE) {
// +++ 进入临界区 +++
printf("Got Mutex! "); // 调试信息
Serial_Print(msg); // 真正操作共享资源
// +++ 退出临界区 +++
// --- 释放锁 ---
xSemaphoreGive(xSerialMutex);
} else {
// 获取失败的处理(如果在规定时间内没等到)
}
}
}
/* 任务 1 */
void vTask1(void *pvParameters) {
for (;;) {
ThreadSafe_Print("Task 1 is running...\r\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 休息1秒
}
}
/* 任务 2 */
void vTask2(void *pvParameters) {
for (;;) {
ThreadSafe_Print("Task 2 is running...\r\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 休息1秒
}
}
/* 主函数 */
int main(void) {
// 硬件初始化...
/* 3. 创建互斥量 */
xSerialMutex = xSemaphoreCreateMutex();
//检查互斥量是否创建成功(判空检查)。
if (xSerialMutex != NULL) {
// 创建成功,启动任务
xTaskCreate(vTask1, "Task1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task2", 1000, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
}
// 如果运行到这里,说明内存不够,创建失败
for(;;);
}
6. 三个关键避坑指南

13.3.1 什么是“优先级反转”?(The Problem)
优先级继承 (Priority Inheritance) 是实时操作系统(RTOS)中互斥量(Mutex)特有的一个机制,用来解决**“优先级反转” (Priority Inversion)** 问题。
要理解“优先级继承”,我们必须先看懂它试图解决的那个“灾难现场”——优先级反转。
定义

根因:Manager 的代码里根本没有 xSemaphoreTake() 这一行(或者他请求的是别的资源)。
提问1




提问2
提问3



提问4
跳转13.7注意事项2
场景: 任务 A(拿锁)和 任务 B(等锁)都是优先级 2。 危机: 突然来了一个 任务 C(优先级 3)
如果任务 B 等待锁这件事不急,可以等任务C运行完再运行
任务C:任务中不需要锁,cpu运行完,给任务A运行
任务中需要锁,发现锁被占用,进入阻塞状态,直到任务A锁被释放
13.3.2 优先级继承(Priority Inheritance)
2. 什么是“优先级继承”?(The Solution)
优先级继承 (Priority Inheritance) 是实时操作系统(RTOS)中互斥量(Mutex)特有的一个机制,用来解决**“优先级反转” (Priority Inversion)** 问题。

3. 优先级继承的三个关键点

4. 总结图解
| 场景 | 谁在运行? | 为什么? | 评价 |
| 无继承 (反转) | Manager (中) | Manager 比 Intern (低) 强,Intern 出不来,Boss 被迫等 Manager。 | 灾难,系统实时性崩塌。 |
| 有继承 | Intern (变身高) | Intern 继承了 Boss 的特权,Manager 无法插队,Intern 快速解锁让位给 Boss。 | 安全,符合预期。 |
5. 一句话总结
优先级继承就是:当“大佬”被“小弟”挡路时,“小弟”会被赋予“御赐金牌”(临时高优先级),以便他在没人敢打扰的情况下迅速把路让开,然后交还金牌。
6.优先级继承该怎么编程

示范代码
这段代码展示了如何正确创建和使用互斥量。你不需要手动调用“提升优先级”的函数,RTOS 会帮你做。
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/* 1. 定义句柄 */
SemaphoreHandle_t xMutexHandle = NULL;
/* 模拟一个耗时的低优先级任务 (Intern) */
void vTaskIntern(void *pvParameters)
{
for(;;)
{
/* 获取锁 */
xSemaphoreTake(xMutexHandle, portMAX_DELAY);
// --- 临界区开始 ---
// 假设这里 Intern 在处理很慢的数据
// 如果此时 Boss 来了并试图拿锁,FreeRTOS 会在这里
// 自动瞬间把 Intern 的优先级提升到和 Boss 一样高!
printf("Intern: Working...\n");
// 模拟耗时操作,给 Boss 和 Manager 出现的机会
for(int i=0; i<1000000; i++) { __asm("nop"); }
printf("Intern: Done.\n");
// --- 临界区结束 ---
/* 释放锁 */
// 此时 FreeRTOS 会自动把 Intern 的优先级降回原来的水平
xSemaphoreGive(xMutexHandle);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
/* 模拟高优先级任务 (Boss) */
void vTaskBoss(void *pvParameters)
{
for(;;)
{
vTaskDelay(pdMS_TO_TICKS(10)); // 让 Intern 先运行一会儿拿锁
printf("Boss: I need the key!\n");
/* 试图获取锁 */
// 只要这一行执行,发现锁被 Intern 拿着,
// FreeRTOS 内核就会立即触发优先级继承机制
xSemaphoreTake(xMutexHandle, portMAX_DELAY);
printf("Boss: Finally got the key!\n");
xSemaphoreGive(xMutexHandle);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void)
{
// 初始化硬件...
/* 2. 关键点:必须使用 CreateMutex 而不是 CreateBinary */
xMutexHandle = xSemaphoreCreateMutex();
if(xMutexHandle != NULL)
{
// 创建任务:Boss 优先级为 3,Intern 优先级为 1
xTaskCreate(vTaskBoss, "Boss", 1000, NULL, 3, NULL);
xTaskCreate(vTaskIntern, "Intern", 1000, NULL, 1, NULL);
// 如果还有一个 Manager (优先级2),也在这里创建...
vTaskStartScheduler();
}
for(;;);
}
7.锁的时间不能过长



13.4 递归锁
递归锁 (Recursive Mutex) 是 FreeRTOS 中一种非常实用但容易混淆的机制。
简单来说,它是为了解决**“自己把自己锁死” (Self-Deadlock)** 这个问题而设计的
1. 为什么需要递归锁?(场景:自己调自己)
假设你写了一个库函数,为了保证线程安全,你使用了普通互斥量。

2. 递归锁的解决方案

3. 专用 API 函数
| 动作 | 普通互斥量函数 (❌不要用) | 递归互斥量函数 (✅必须用) |
| 创建 | xSemaphoreCreateMutex | xSemaphoreCreateRecursiveMutex |
| 获取 | xSemaphoreTake | xSemaphoreTakeRecursive |
| 释放 | xSemaphoreGive | xSemaphoreGiveRecursive |
原型如下
/* 创建一个递归锁,返回它的句柄。*
* 此函数内部会分配互斥量结构体*
* 返回值: 返回句柄,非NULL表示成功*
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
*/ 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );
*/ 获得 */
BaseType_t xSemaphoreTakeRecursive(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
4.示范代码
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xRecursiveMutex;
void Function_B() {
// 再次申请锁:因为是同一个任务,这里会立即成功,计数器 +1
if (xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE) {
printf("Function B entered!\n");
// ... 处理任务 ...
// 释放锁:计数器 -1
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
void Function_A() {
// 第一次申请锁:成功,计数器 = 1
if (xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE) {
printf("Function A entered!\n");
// 在持有锁的情况下调用 Function_B
// 如果是普通锁,这里就死机了;递归锁则安全
Function_B();
printf("Back in Function A!\n");
// 释放锁:计数器变为 0,锁彻底释放
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
int main(void) {
// 1. 创建递归互斥量
xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
if (xRecursiveMutex != NULL) {
// 创建任务调用 Function_A ...
}
}
5. 什么时候该用递归锁?

6.提问1
两套逻辑


13.7 常见问题
使用互斥量的两个任务是相同优先级时的注意事项

注意事项一:千万不要使用“轮询”模式 (Polling)
这是同优先级任务中最容易犯的低级错误。

注意事项二:优先级继承机制“失效”

如果任务 B 等待锁这件事不急,可以等任务C运行完再运行
任务C:任务中不需要锁,cpu运行完,给任务A运行
任务中需要锁,发现锁被占用,进入阻塞状态,直到任务A锁被释放
注意事项三:当心“乒乓效应” (Thrashing)

总结
对于同优先级任务使用互斥量:
-
必须阻塞:
Take时一定要给等待时间,绝不要用0轮询。 -
效率优先: 只要一方阻塞,另一方就会独占 CPU,这其实是好事(能更快释放锁)。
-
警惕频繁切换: 如果锁竞争太激烈,考虑修改软件架构。
13.8作为初学者,要掌握互斥量的知识点





755

被折叠的 条评论
为什么被折叠?



