当前位置: 首页 > 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.rkmt.cn/news/1387938.html

相关文章:

  • 记一次Android进程native内存泄漏分析
  • 冷门实用插件盘点,大幅缩减作图时长
  • 2026年AI+智慧教育全场景应用解决方案白皮书
  • 构建现代AI智能体:从LangChain、LangGraph到MCP的实战指南
  • AI辅助开发工作流实践:代码审查、测试与文档自动化
  • 2026年4月2205双相钢圆棒厂商推荐,2205不锈钢圆棒/904L不锈钢圆棒,2205双相钢圆棒品牌哪家好 - 品牌推荐师
  • 2026年4月套膜机产品推荐,打包缠膜一体机/行李包装机/自动缠膜机/摇臂缠膜机/自动缠绕机/包装机,套膜机制造商如何选 - 品牌推荐师
  • Unity游戏翻译深度解析:XUnity.AutoTranslator原理与优化实战
  • Python数据类型转换实战:隐式陷阱、显式代价与结构迁移
  • AI 对话流量新赛道:搜极星 GEO 品牌监测全维度解读
  • 2026年5月上海搬家公司推荐:五个口碑搬家服务专业评测价格适用场景 - 品牌推荐
  • WebStorm提交Gitee失败:31mlncorrect错误与access token认证详解
  • AI智能体规模化运维:从上下文污染到系统防劣化的工程实践
  • 预计2032年全球TPU纱线市场规模将达到1.73亿美元
  • C#调用Windows API获取窗口文本的底层原理与工程实践
  • DeepSeek LeetCode 2659.将数组清空 Java实现
  • 构建数据管道深度监控体系:从质量契约到工程实践
  • 新手必看财务报表!财务报表编制基础指南
  • 联发科设备深度解锁:从零开始掌握mtkclient-gui的实用指南
  • C++11 跨平台文件模糊搜索工具 — 设计与实现详解
  • Claude Code权限配置实战:基于模式信任与安全边界的AI助手自动化
  • Burp插件实战:AES+RSA混合加解密流量处理指南
  • LLM成本优化实战:从提示词到缓存,97%成本削减策略详解
  • RV1126 SDK编译避坑指南:从源码到镜像,手把手解决那些官方文档没说的坑
  • hyper-v中的windows 10虚拟机无法开启增强会话模式的罕见情况及原因分析
  • 【最新 v2.7.5】Windows 版 OpenClaw 一键包:2026 年程序员 / 运营 / 行政都在偷偷用的提效暗器
  • 50行Python实现Anthropic Claude Advisor工具调用:AI规划与本地执行的工程实践
  • 构建能成交的AI销售代理:从对话管理到RAG落地的实战指南
  • 昇腾CANN开源竞赛,从参赛到获奖的实战攻略
  • 保姆级教程:在Windows上从零跑通TASSEL 5.0的GWAS分析(附示例数据避坑指南)