news 2026/6/4 12:56:28

FreeRTOS 手动移植教程(六):互斥量 —— 保护共享资源与优先级继承

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS 手动移植教程(六):互斥量 —— 保护共享资源与优先级继承

在上一篇文章中,我们学会了用信号量实现任务同步与资源计数。但当多个任务需要修改同一个共享变量时,二值信号量可能会引入优先级反转的隐患。本篇将引入 FreeRTOS 专门为此设计的互斥量(Mutex),剖析其优先级继承机制,并通过实验直观对比互斥量与信号量的行为差异。


一、为什么二值信号量不能做互斥?

二值信号量可以被任意任务给出(Give),即使这个任务并没有事先获取(Take)它。这在“中断通知任务”的场景中是合理的——中断本身并不申请资源,它只是通知事件发生。

但在多任务共享同一资源的场景中,正确的规则应该是:只有成功获取资源的任务,才有资格释放它。二值信号量无法强制执行这一规则,因此会带来一个严重问题——优先级反转

1.1 优先级反转的经典场景

假设系统中有三个任务:高优先级任务 H、中优先级任务 M、低优先级任务 L。

  • L 先获取了信号量,开始访问共享资源;
  • 随后 H 也尝试获取同一个信号量,于是被阻塞;
  • 此时 M 就绪,由于 M 优先级高于 L,内核调度 M 运行,L 被抢占但还没有释放信号量
  • H 被 M 无限期地阻塞,尽管 H 的优先级最高——这就是优先级反转

1.2 互斥量的解决方案

互斥量本质上是一种带有优先级继承机制的二进制信号量。当高优先级任务 H 因获取互斥量而被阻塞,且互斥量正被低优先级任务 L 持有时,内核会临时将 L 的优先级提升至与 H 相同,以防止 M 抢占 L。这样 L 可以尽快执行完毕并释放互斥量,H 得以继续运行。任务释放互斥量后,优先级自动恢复。


二、互斥量的关键 API

功能API 名称说明
创建互斥量xSemaphoreCreateMutex返回互斥量句柄,初始为“可用”状态
获取互斥量(任务)xSemaphoreTake与获取信号量共用同一个函数
释放互斥量(任务)xSemaphoreGive只有成功获取的任务才能释放,否则返回失败
删除互斥量vSemaphoreDelete释放互斥量占用的内存

重要:互斥量不允许在中断服务函数中使用(没有 FromISR 版本),因为中断中不应持有锁。这也是互斥量与信号量的关键区别之一。
使用互斥量前,必须在FreeRTOSConfig.h中将configUSE_MUTEXES设置为 1(第一篇的配置中已默认开启)。


三、实验:模拟优先级反转与互斥量的效果

3.1 实验设计

创建三个任务:(优先级 3)、(优先级 2)、(优先级 1),它们共享一个全局变量shared_counter

  • 低优先级任务 L:获取互斥量后,对共享资源进行耗时操作(模拟长时间临界区);
  • 中优先级任务 M:不断翻转一个 LED,指示它是否在运行;
  • 高优先级任务 H:同样尝试获取互斥量,操作共享变量。

我们先用二值信号量实现“互斥”,观察中优先级任务是否能在低优先级任务持有锁时继续运行(导致反转);然后换成互斥量,观察优先级继承的效果。

3.2 硬件准备

沿用之前的硬件:PA0、PA1 和 PC13 分别连接三个 LED。BSP 文件bsp_led.h/bsp_led.c中包含LED_InitAll()LED1_Toggle()(PA0)、LED2_Toggle()(PA1)、LED3_Toggle()(PC13),此处不再重复给出。

同时确保main()中调用了NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);


四、使用二值信号量的缺陷代码

#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"#include"semphr.h"#include"bsp_led.h"SemaphoreHandle_t xBinarySem;/* 共享资源 */volatileuint32_tshared_counter=0;/* 高优先级任务 H(优先级 3) */voidvHighTask(void*pvParameters){while(1){if(xSemaphoreTake(xBinarySem,portMAX_DELAY)==pdTRUE){shared_counter++;// 快速操作共享变量xSemaphoreGive(xBinarySem);}vTaskDelay(pdMS_TO_TICKS(100));}}/* 中优先级任务 M(优先级 2) */voidvMediumTask(void*pvParameters){while(1){LED2_Toggle();// 翻转 PA1,指示该任务在运行vTaskDelay(pdMS_TO_TICKS(200));}}/* 低优先级任务 L(优先级 1) */voidvLowTask(void*pvParameters){while(1){if(xSemaphoreTake(xBinarySem,portMAX_DELAY)==pdTRUE){LED1_Toggle();// 表示低优先级任务进入临界区// 模拟长时间操作,如写 EEPROMfor(volatileuint32_ti=0;i<2000000;i++);xSemaphoreGive(xBinarySem);}vTaskDelay(pdMS_TO_TICKS(50));}}intmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);LED_InitAll();/* 创建二值信号量,并先 Give 一次,使其处于可用状态 */xBinarySem=xSemaphoreCreateBinary();xSemaphoreGive(xBinarySem);// 初始可用xTaskCreate(vHighTask,"High",128,NULL,3,NULL);xTaskCreate(vMediumTask,"Medium",128,NULL,2,NULL);xTaskCreate(vLowTask,"Low",128,NULL,1,NULL);vTaskStartScheduler();while(1);}

运行现象:你会看到Medium任务控制的 LED(PA1)在Low任务进入临界区时依然持续闪烁,说明中优先级任务成功抢占了低优先级任务,导致高优先级任务长时间无法获取信号量——典型的优先级反转


五、替换为互斥量

将二值信号量替换为互斥量,只需修改创建和初始化部分,其余代码结构完全相同:

#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"#include"semphr.h"#include"bsp_led.h"SemaphoreHandle_t xMutex;volatileuint32_tshared_counter=0;/* 高优先级任务 H(优先级 3) */voidvHighTask(void*pvParameters){while(1){if(xSemaphoreTake(xMutex,portMAX_DELAY)==pdTRUE){shared_counter++;xSemaphoreGive(xMutex);}vTaskDelay(pdMS_TO_TICKS(100));}}/* 中优先级任务 M(优先级 2) */voidvMediumTask(void*pvParameters){while(1){LED2_Toggle();vTaskDelay(pdMS_TO_TICKS(200));}}/* 低优先级任务 L(优先级 1) */voidvLowTask(void*pvParameters){while(1){if(xSemaphoreTake(xMutex,portMAX_DELAY)==pdTRUE){LED1_Toggle();for(volatileuint32_ti=0;i<2000000;i++);xSemaphoreGive(xMutex);}vTaskDelay(pdMS_TO_TICKS(50));}}intmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);LED_InitAll();/* 创建互斥量:初始即为可用状态,无需额外 Give */xMutex=xSemaphoreCreateMutex();if(xMutex==NULL)while(1);xTaskCreate(vHighTask,"High",128,NULL,3,NULL);xTaskCreate(vMediumTask,"Medium",128,NULL,2,NULL);xTaskCreate(vLowTask,"Low",128,NULL,1,NULL);vTaskStartScheduler();while(1);}

运行现象对比:使用互斥量后,当Low任务持有互斥量且High任务开始等待时,内核会临时将Low任务的优先级提升到 3(与High相同)。此时Medium任务(优先级 2)无法抢占Low,只能等待。直到Low完成操作释放互斥量,High立即获得互斥量继续运行。你会观察到Medium任务的 LED 在Low持有互斥量期间停止闪烁(或出现明显的停顿),这直观地验证了优先级继承机制的作用。


六、互斥量的正确使用规则

  1. 在任务中使用,绝不在中断中使用
    互斥量没有 FromISR 版本,因为它可能引发优先级继承,这只能在任务上下文中完成。

  2. 谁获取,谁释放
    互斥量会检查释放者是否持有它。如果任务 A 获取了互斥量,却由任务 B 来释放,操作将失败。这就要求临界区代码的获取与释放必须成对出现,推荐在同一函数内完成。

  3. 避免在持有期间长时间阻塞
    持有互斥量的任务应尽快完成操作并释放,不要在临界区内调用vTaskDelay等可能长时间阻塞的函数,否则高优先级任务会被严重拖累。

  4. 递归互斥量
    如果一个任务可能需要多次获取同一个互斥量(例如嵌套函数调用),普通互斥量会导致死锁。此时应使用xSemaphoreCreateRecursiveMutex,并配合xSemaphoreTakeRecursive/xSemaphoreGiveRecursive使用。


七、总结

通过本篇的对比实验,我们清楚地认识到:

  • 二值信号量适合中断与任务同步,但不能可靠地用于多任务间的互斥访问;
  • 互斥量通过优先级继承机制,有效缓解了优先级反转问题,是保护共享资源的标准手段;
  • 互斥量的使用规则比信号量更严格,必须在任务中成对使用,且绝不能在中断中调用。

下一篇文章我们将学习 FreeRTOS 的软件定时器,了解如何在不占用额外硬件定时器的情况下,实现定时回调与周期性处理。


下一篇:FreeRTOS 软件定时器 —— 不占硬件 Timer 的定时回调实现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 12:54:40

政企网站建设全流程

在政企网站建设中&#xff0c;PageAdmin CMS是当前市场上在信创国产化适配和等保安全合规方面表现尤为突出的专业级内容管理系统。它不仅能高效完成建站&#xff0c;更能全面满足政务、国企及教育机构对安全、合规和集约化管理的严格要求。以下是基于PageAdmin CMS的政企网站建…

作者头像 李华
网站建设 2026/6/4 12:53:38

不止是初始化:聊聊PSINS工具箱里那些你可能忽略的“隐藏单位”

深入解析PSINS工具箱中的单位系统&#xff1a;从dph到ugpsHz的工程实践指南 如果你曾经在调试惯性导航算法时&#xff0c;对着代码里突然冒出的 dph 、 ugpsHz 或 mpspsh 这样的单位感到困惑&#xff0c;那么这篇文章就是为你准备的。这些看似晦涩的单位实际上是PSINS工具…

作者头像 李华
网站建设 2026/6/4 12:52:41

Arduino气体传感器仿真:Tinkercad电位器模拟与ADC数据映射实战

1. 项目概述与核心思路在嵌入式系统开发&#xff0c;尤其是物联网和智能硬件的前期验证阶段&#xff0c;传感器接口的调试与数据采集逻辑的验证是绕不开的环节。气体传感器&#xff0c;作为感知环境状态的关键“感官”&#xff0c;其输出信号的处理方式直接决定了整个系统的可靠…

作者头像 李华
网站建设 2026/6/4 12:51:23

C++多线程detach()传参避坑指南:为什么你的引用传了个寂寞?

C多线程detach()传参避坑指南&#xff1a;为什么你的引用传了个寂寞&#xff1f;在异步编程的世界里&#xff0c;C的std::thread为我们打开了多线程的大门&#xff0c;但detach()操作却像是一把双刃剑——它让子线程获得自由的同时&#xff0c;也埋下了不少隐患。许多开发者在使…

作者头像 李华
网站建设 2026/6/4 12:51:21

分布式卫星通信中STBC时间失准的低复杂度接收机设计与实现

1. 项目概述&#xff1a;分布式卫星通信中的STBC时间失准挑战在卫星通信领域&#xff0c;尤其是蓬勃发展的非静止轨道星座系统中&#xff0c;如何利用有限的频谱资源实现高可靠、高速率的全球覆盖&#xff0c;是业界持续探索的核心课题。空间分集技术&#xff0c;特别是空时分组…

作者头像 李华