FreeRTOS 实战:互斥量与优先级继承——从代码到现象完全解析
引言
在嵌入式实时操作系统(RTOS)中,多个任务共享资源(如串口、全局变量、外设等)时,必须采取同步机制防止数据错乱。互斥量(Mutex)是专门为解决“互斥访问”而设计的同步对象,它不仅能保护共享资源,还具有优先级继承特性,能有效缓解优先级反转问题。
本文将基于一个 STM32 + FreeRTOS(CMSIS‑RTOS V2)的实际工程,通过三个不同优先级的任务争夺同一个互斥量,直观演示任务的调度顺序、互斥量的保护作用以及优先级继承的触发过程。代码全部开源,可直接在开发板上运行测试。
一、工程代码展示
下面的代码是从freertos.c中摘取的核心部分,包含三个任务(高、中、低优先级)和一个互斥量。低优先级任务长时间占用互斥量,高优先级任务等待互斥量,中优先级任务仅做打印展示。
/* 包含头文件 */ #include "FreeRTOS.h" #include "task.h" #include "main.h" #include "cmsis_os.h" #include <stdio.h> /* 互斥量句柄和属性 */ osMutexId_t Mutex01Handle; const osMutexAttr_t Mutex01_attributes = { .name = "Mutex01" }; /* 任务句柄和属性 */ osThreadId_t task_HHandle, task_MHandle, task_LHandle; const osThreadAttr_t task_H_attributes = { .name = "task_H", .stack_size = 128*4, .priority = osPriorityLow2 }; const osThreadAttr_t task_M_attributes = { .name = "task_M", .stack_size = 128*4, .priority = osPriorityLow1 }; const osThreadAttr_t task_L_attributes = { .name = "task_L", .stack_size = 128*4, .priority = osPriorityLow }; /* 任务函数声明 */ void Task_H_Func(void *argument); void Task_M_Func(void *argument); void Task_L_Func(void *argument); /* 初始化函数中创建互斥量和任务 */ void MX_FREERTOS_Init(void) { Mutex01Handle = osMutexNew(&Mutex01_attributes); // 创建互斥量 task_HHandle = osThreadNew(Task_H_Func, NULL, &task_H_attributes); task_MHandle = osThreadNew(Task_M_Func, NULL, &task_M_attributes); task_LHandle = osThreadNew(Task_L_Func, NULL, &task_L_attributes); } /* 高优先级任务:获取互斥量,占用1秒,释放,然后延迟1秒 */ void Task_H_Func(void *argument) { for (;;) { osMutexAcquire(Mutex01Handle, osWaitForever); printf("High Task get mutex, start\r\n"); HAL_Delay(1000); // 模拟占用资源 printf("High Task give mutex, end\r\n"); osMutexRelease(Mutex01Handle); osDelay(1000); // 主动让出CPU } } /* 中优先级任务:只打印,不访问互斥量 */ void Task_M_Func(void *argument) { for (;;) { printf("Middle Task use cpu, but do nothing\r\n"); osDelay(1000); } } /* 低优先级任务:获取互斥量,占用3秒,释放,延迟1秒 */ void Task_L_Func(void *argument) { for (;;) { osMutexAcquire(Mutex01Handle, osWaitForever); printf("Low Task get mutex, start\r\n"); HAL_Delay(3000); printf("Low Task give mutex, end\r\n"); osMutexRelease(Mutex01Handle); osDelay(1000); } }注:代码中默认还有一个
defaultTask用于翻转 LED,不影响互斥量演示,故未完全列出。
二、关键知识点
1. 任务优先级
CMSIS‑RTOS V2 中优先级数值越大优先级越高:
osPriorityLow= 1osPriorityLow1= 2osPriorityLow2= 3
因此:task_H (3) > task_M (2) > task_L (1)。
2. 互斥量 vs 二进制信号量
| 特性 | 互斥量 (Mutex) | 二进制信号量 (Semaphore) |
|---|---|---|
| 用途 | 保护共享资源(互斥访问) | 任务同步 / 事件通知 |
| 优先级继承 | 支持 | 不支持 |
| 谁可以释放 | 只能由获取者释放 | 任何任务或中断均可释放 |
| 适用场景 | 资源保护 | 轻量级同步 |
本代码中使用互斥量,并在注释中保留了二进制信号量的创建,但实际并未使用。
3. 优先级反转与优先级继承
优先级反转:高优先级任务等待低优先级任务持有的资源,而中优先级任务(不访问资源)抢占 CPU,导致高优先级任务迟迟无法运行。
优先级继承:当高优先级任务等待低优先级任务持有的互斥量时,低优先级任务会临时提升到高优先级任务的级别,防止中优先级任务抢占,从而缩短高优先级任务的等待时间。
三、代码运行行为分析
场景模拟
启动后:三个任务均就绪,调度器优先运行task_H。
task_H获取互斥量 → 打印 →
HAL_Delay(1000)忙等 → 释放互斥量 → 打印 →osDelay(1000)阻塞。task_H 阻塞后:调度器运行次高优先级任务task_M→ 打印 →
osDelay(1000)阻塞。task_M 阻塞后:调度器运行task_L→ 获取互斥量 → 打印 → 开始
HAL_Delay(3000)。在 task_L 忙等期间,1秒后 task_H 从
osDelay中唤醒,立即抢占 CPU。task_H 尝试获取互斥量→ 被 task_L 持有 → task_H 进入阻塞态(等待互斥量)。
优先级继承生效:task_L 的优先级被临时提升到与 task_H 相同(osPriorityLow2)。
task_L 继续执行完
HAL_Delay(3000)→ 释放互斥量 → task_H 获得互斥量并继续执行。
实际串口输出示例
注意:如果使用二进制信号量代替互斥量,则不会发生优先级继承,中优先级任务可能在低优先级任务持有锁期间抢占 CPU,导致高优先级任务等待时间无限延长。
四、常见疑问解答
Q1:为什么 task_H 抢占后没有立即打印“High Task get mutex”?
A:因为 task_H 抢占后马上执行osMutexAcquire,发现互斥量已被 task_L 占用,于是立即进入阻塞态,不会执行到printf。只有当 task_L 释放互斥量,task_H 获得锁后,才会打印"High Task get mutex, start"。
Q2:如果我把 task_H 的osDelay(1000)改为HAL_Delay(1000)会怎样?
A:HAL_Delay是忙等待,不会让出 CPU。那么 task_H 释放互斥量后会继续忙等 1 秒,期间仍然占用 CPU,导致 task_M 和 task_L 得不到运行。这相当于高优先级任务“霸占”CPU,系统实时性变差。推荐使用osDelay主动阻塞。
Q3:如果将互斥量换成二进制信号量,代码需要修改哪里?
A:将osMutexAcquire/Release替换为osSemaphoreAcquire/Release,并删除互斥量创建,改用osSemaphoreNew(1,1,NULL)。但二进制信号量会导致优先级反转,不推荐用于资源保护。
五、总结
| 知识点 | 本示例体现 |
|---|---|
| 互斥量的创建 | osMutexNew(&Mutex01_attributes) |
| 获取/释放 | osMutexAcquire/osMutexRelease |
| 任务优先级 | osPriorityLow2>osPriorityLow1>osPriorityLow |
| 阻塞与唤醒 | osDelay让出 CPU,osMutexAcquire等待锁时阻塞 |
| 优先级继承 | task_L 持有锁时被临时提升,避免被 task_M 抢占 |
| 临界区保护 | 对printf和HAL_Delay的访问被互斥量保护,不会交错 |
通过本例,您可以掌握 FreeRTOS 互斥量的基本用法,理解优先级继承的实际效果,并能够在自己的项目中正确使用互斥量保护共享资源。
