当前位置: 首页 > news >正文

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= 1

  • osPriorityLow1= 2

  • osPriorityLow2= 3

因此:task_H (3) > task_M (2) > task_L (1)

2. 互斥量 vs 二进制信号量

特性互斥量 (Mutex)二进制信号量 (Semaphore)
用途保护共享资源(互斥访问)任务同步 / 事件通知
优先级继承支持不支持
谁可以释放只能由获取者释放任何任务或中断均可释放
适用场景资源保护轻量级同步

本代码中使用互斥量,并在注释中保留了二进制信号量的创建,但实际并未使用。

3. 优先级反转与优先级继承

  • 优先级反转:高优先级任务等待低优先级任务持有的资源,而中优先级任务(不访问资源)抢占 CPU,导致高优先级任务迟迟无法运行。

  • 优先级继承:当高优先级任务等待低优先级任务持有的互斥量时,低优先级任务会临时提升到高优先级任务的级别,防止中优先级任务抢占,从而缩短高优先级任务的等待时间。


三、代码运行行为分析

场景模拟

  1. 启动后:三个任务均就绪,调度器优先运行task_H

  2. task_H获取互斥量 → 打印 →HAL_Delay(1000)忙等 → 释放互斥量 → 打印 →osDelay(1000)阻塞。

  3. task_H 阻塞后:调度器运行次高优先级任务task_M→ 打印 →osDelay(1000)阻塞。

  4. task_M 阻塞后:调度器运行task_L→ 获取互斥量 → 打印 → 开始HAL_Delay(3000)

  5. 在 task_L 忙等期间,1秒后 task_H 从osDelay中唤醒,立即抢占 CPU。

  6. task_H 尝试获取互斥量→ 被 task_L 持有 → task_H 进入阻塞态(等待互斥量)。

  7. 优先级继承生效:task_L 的优先级被临时提升到与 task_H 相同(osPriorityLow2)。

  8. 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)会怎样?

AHAL_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 抢占
临界区保护printfHAL_Delay的访问被互斥量保护,不会交错

通过本例,您可以掌握 FreeRTOS 互斥量的基本用法,理解优先级继承的实际效果,并能够在自己的项目中正确使用互斥量保护共享资源。

http://www.jsqmd.com/news/888471/

相关文章:

  • AI智能体构建:从概念到工程实践的完整指南
  • 告别多模型集成噩梦:DMXAPI如何用“改两行配置”统一调用DeepSeek、豆包等大模型
  • 冷门实用插件盘点,大幅缩减作图时长
  • AI重塑IT文档工作流:从日志到专业报告与SOP的自动化实践
  • Python数据类型转换实战:隐式陷阱、显式代价与结构迁移
  • 2025-2026年北京家庭定制游旅行社推荐:TOP5口碑产品评测三代同行避拥挤性价比高注意事项 - 品牌推荐
  • 版图新手避坑指南:画电阻时,为什么你的LVS总报错?(附蛇形连线实战)
  • AMD Ryzen 7 3800X + VMware 15.1.0 保姆级教程:手把手带你搞定macOS Catalina虚拟机(含避坑指南)
  • 2026年4月2205双相钢圆棒厂商推荐,2205不锈钢圆棒/904L不锈钢圆棒,2205双相钢圆棒品牌哪家好 - 品牌推荐师
  • awk入门
  • 昇腾CANN社区治理:一个PR从提交到合并的全过程
  • 2026年4月套膜机产品推荐,打包缠膜一体机/行李包装机/自动缠膜机/摇臂缠膜机/自动缠绕机/包装机,套膜机制造商如何选 - 品牌推荐师
  • 利用AI编程助手30分钟快速上手陌生代码库的方法论
  • Unity游戏翻译深度解析:XUnity.AutoTranslator原理与优化实战
  • Unity微信小游戏实战:突破首包限制与WXSS兼容性难题
  • 线程任务执行报错后,线程会不会挂掉,Java线程池
  • 多平台同稿如何一键改写?5款AI文案工具对比帮你避坑
  • Python TDD实战入门:从red-green-refactor到高覆盖率测试套件
  • Git 给 main 分支打 Tag(版本标记)完整教程
  • 昇腾CANN开源竞赛,从参赛到获奖的实战攻略
  • UOS系统维护实战:用一条命令批量清理旧内核与无用依赖,为你的系统‘瘦身’
  • 2026年5月上海搬家公司推荐:五个口碑搬家服务专业评测价格适用场景 - 品牌推荐
  • AI智能体规模化运维:从上下文污染到系统防劣化的工程实践
  • WebStorm提交Gitee失败:31mlncorrect错误与access token认证详解
  • ops-transformer的MoE算子,让混合专家模型训练快5倍
  • 源代码论文分享|基于Java的企业OA管理系统的设计与实现!
  • 保姆级教程:在Windows上从零跑通TASSEL 5.0的GWAS分析(附示例数据避坑指南)
  • linux配置DNS主从服务器的实验步骤
  • API 接口自动化测试详细图文教程学习系列22--结合Pytest框架使用3-分组、跳过执行和参数化处理
  • PTA L1-005 考试座位号:用C语言结构体搞定考场查询系统(附完整代码)