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

【FreeRTOS】二值信号量vs互斥量核心差异

目录

一、先搞懂基本概念:什么是二值信号量?什么是互斥量?

二、核心区别:从 “行为特性” 看差异

关键区别详解:

a.“所有权” 是核心差异

b.优先级反转问题的处理(重点)

1.使用二值信号量

2.使用互斥量

三、使用示例

3.1二值信号量的使用方法

1.创建二值信号量

2.获取信号量(等待事件)

3.释放信号量(发送事件)

3.2互斥量的使用方法

1.创建互斥量

2.获取互斥量(加锁)

3.释放互斥量(解锁)

3.3使用时的核心注意事项

1.二值信号量的注意事项

2.互斥量的注意事项

3.两者共有的注意事项

总结

四、怎么选?用场景判断


对于学习FreeRTOS 来说,二值信号量和互斥量确实容易混淆,因为它们底层机制相似,但用途和行为有本质区别。我们可以从 “功能用途”“核心特性”“使用场景” 三个维度来理解它们的区别和联系。

一、先搞懂基本概念:什么是二值信号量?什么是互斥量?

  1. 二值信号量(Binary Semaphore

可以理解为一个 “开关”,只有两种状态:“有信号”(值为 1)和“无信号”(值为 0)。

  • 当一个任务调用 xSemaphoreGive() 时,信号量变为 “有信号”(值 1);

  • 当一个任务调用 xSemaphoreTake() 时,信号量变为 “无信号”(值 0);

  • 核心作用:“同步”(让一个任务等待另一个任务 / 事件的发生)。

  1. 互斥量(Mutex

可以理解为一个 “钥匙”,用于保护共享资源 (比如一个传感器、一段内存、一个外设)。

  • 当一个任务需要访问共享资源时,先 “拿钥匙”(xSemaphoreTake());

  • 用完后必须 “还钥匙”(xSemaphoreGive());

  • 核心作用:“互斥”(确保同一时间只有一个任务能访问共享资源)。

二、核心区别:从 “行为特性” 看差异

关键区别详解:

a.“所有权” 是核心差异

  • 二值信号量:比如任务 A 调用 xSemaphoreGive() 使信号量变为 1,任务 B 可以调用 xSemaphoreTake() 拿走(变为 0),之后任务 C 也可以调用 xSemaphoreGive() 再次使它变为 1—— 谁都可以 “给”,谁都可以 “拿”,没有归属。

  • 互斥量:比如任务 A 调用 xSemaphoreTake() 拿到互斥量(“拿到钥匙”),那么只有任务 A能调用 xSemaphoreGive() 释放它。如果任务 B 试图释放任务 A 持有的互斥量,会导致错误(行为未定义)。

b.优先级反转问题的处理(重点)

这是互斥量最关键的特性,也是最难理解的点,举个例子:

  1. 假设有高优先级任务 H、中优先级任务 M、低优先级任务 L。

三个任务在 “竞争访问同一个共享资源” 时,采用了不同的 “资源保护工具”—— 要么用二值信号量,要么用互斥量。

1.使用二值信号量

这里的逻辑是:

为了防止多个任务同时访问共享资源(比如一块内存、一个传感器),三个任务(L、M、H)约定用二值信号量作为 “资源占用的标志”:

  • 谁要访问资源,先调用 xSemaphoreTake() 拿信号量(拿到了就表示 “资源可用”,没拿到就等);

  • 用完资源后,调用 xSemaphoreGive() 释放信号量(告诉其他任务 “资源空闲了”)。

但因为二值信号量没有 “优先级继承” 特性,就会出现问题:

  1. 低优先级任务 L 先拿到二值信号量,开始访问资源;

  2. 此时高优先级任务 H 就绪,也想拿信号量访问资源,但信号量被 L 占用,H 只能等待;

  3. 中优先级任务 M(优先级比 L 高、比 H 低)也就绪了 —— 因为 M 的优先级比 L 高,会直接抢占 L 的 CPU(L 被打断,暂停访问资源);

  4. M 运行时,H 依然在等信号量,但 L 被 M 打断后迟迟不能释放信号量,导致 H(最高优先级)反而被 M(中优先级)和 L(低优先级)“拖累”,这就是优先级反转。

一句话:三个任务都用二值信号量来 “抢资源”,但二值信号量保护不了优先级,导致反转。

2.使用互斥量

这里的逻辑是:

三个任务(L、M、H)约定改用互斥量作为 “资源占用的标志”,规则和信号量类似(拿锁 -> 用资源 -> 放锁),但互斥量有 “优先级继承” 特性,能解决问题:

  1. 低优先级任务 L 先拿到互斥量,开始访问资源;

  2. 高优先级任务 H 就绪,想拿互斥量但被 L 占用,H 开始等待;

  3. 此时 FreeRTOS 检测到 “高优先级任务 H 在等低优先级任务 L 持有的互斥量”,会临时把 L 的优先级提升到和 H 一样高;

  4. 中优先级任务 M 就绪后,因为 M 的优先级低于 “被临时提升后的 L”,所以 M 无法抢占 L,L 能继续不受干扰地访问资源,用完后释放互斥量;

  5. 互斥量释放后,H 就能立刻拿到互斥量访问资源,避免了优先级反转。

一句话:三个任务都用互斥量来 “抢资源”,互斥量通过 “临时提升低优先级任务 L 的优先级”,阻止了 M 抢占 L,让 H 能及时拿到资源。

三、使用示例

二值信号量和互斥量在 FreeRTOS 底层都基于信号量机制实现,它们的核心操作(xSemaphoreTake() 获取、xSemaphoreGive() 释放)的函数接口完全相同。

可以理解为:

  • 二值信号量是 “最简单的信号量”(计数只能是 0 或 1);

  • 互斥量是 “增强版的二值信号量”,增加了 “所有权” 和 “优先级继承” 特性,专门用于互斥场景。

3.1二值信号量的使用方法

二值信号量核心用于同步(如 “事件通知”“任务唤醒”),使用流程分为 3 步:创建、获取(等待信号)、释放(发送信号)。

1.创建二值信号量

使用 xSemaphoreCreateBinary() 函数创建,返回信号量句柄(SemaphoreHandle_t)。

#include "FreeRTOS.h"
#include "semphr.h"// 定义二值信号量句柄(全局或任务间可见)
SemaphoreHandle_t xBinarySemaphore;void vInitSemaphores(void) {// 创建二值信号量(初始状态为0:无信号)xBinarySemaphore = xSemaphoreCreateBinary();// 检查创建是否成功(内存不足可能失败)if (xBinarySemaphore == NULL) {// 错误处理:如日志输出、系统复位等}
}

注意:二值信号量创建后默认状态为 “无信号”(值为 0),需通过xSemaphoreGive()手动设置为 “有信号”(值为 1)。

2.获取信号量(等待事件)

使用 xSemaphoreTake() 函数等待信号量变为 “有信号”(值 1),获取后信号量自动变为 “无信号”(值 0)。 函数原型:

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,  // 信号量句柄TickType_t xTicksToWait        // 等待超时时间(节拍数,0=不等待,portMAX_DELAY=无限等)
);

返回值:pdPASS(成功获取)、errQUEUE_EMPTY(超时未获取)。

示例:任务等待中断通知(如按键按下)

// 等待信号量的任务
void vWaitTask(void *pvParameters) {for (;;) {// 等待信号量(无限期等待,直到有信号)if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdPASS) {// 信号量获取成功:处理事件(如按键逻辑)printf("收到信号,执行操作...\n");}}
}

3.释放信号量(发送事件)

使用 xSemaphoreGive() 函数将信号量设置为 “有信号”(值 1),唤醒等待的任务。

  • 任务中释放:xSemaphoreGive()

  • 中断中释放:必须用 xSemaphoreGiveFromISR()(ISR 安全版本)

示例:中断中释放信号量(通知任务)

// 按键中断服务程序(ISR)
void EXTI0_IRQHandler(void) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;// 检查中断标志(硬件相关)if (EXTI_GetITStatus(EXTI_Line0) != RESET) {// 释放二值信号量(通知等待任务)xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);// 清理中断标志EXTI_ClearITPendingBit(EXTI_Line0);}// 若需要,触发任务切换(高优先级任务被唤醒时)portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

3.2互斥量的使用方法

互斥量核心用于保护共享资源(确保同一时间只有一个任务访问),使用流程也是 3 步:创建、获取(加锁)、释放(解锁)。

1.创建互斥量

使用 xSemaphoreCreateMutex() 函数创建,返回互斥量句柄(SemaphoreHandle_t)。

// 定义互斥量句柄(保护共享资源,如串口)
SemaphoreHandle_t xMutex;void vInitMutex(void) {// 创建互斥量(初始状态为“有信号”:可被获取)xMutex = xSemaphoreCreateMutex();// 检查创建是否成功if (xMutex == NULL) {// 错误处理}
}

注意:互斥量创建后默认状态为 “有信号”(值 1),表示 “资源可用”。

2.获取互斥量(加锁)

同样使用 xSemaphoreTake() 函数,获取后互斥量变为 “无信号”(值 0),表示 “资源被占用”。 示例:任务访问共享资源(如串口打印)前加锁

// 任务1:使用串口打印
void vTask1(void *pvParameters) {for (;;) {// 获取互斥量(最多等待100ms,超时则放弃)if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdPASS) {// 成功获取:安全访问共享资源(串口)printf("任务1:正在使用串口...\n");vTaskDelay(pdMS_TO_TICKS(50)); // 模拟使用资源// 用完后释放互斥量(解锁)xSemaphoreGive(xMutex);} else {// 获取失败(超时):处理错误(如重试或放弃)printf("任务1:获取互斥量超时!\n");}vTaskDelay(pdMS_TO_TICKS(100));}
}// 任务2:也需要使用串口(与任务1互斥)
void vTask2(void *pvParameters) {// 逻辑与任务1相同,省略...
}

3.释放互斥量(解锁)

使用 xSemaphoreGive() 函数,释放后互斥量变为 “有信号”(值 1),表示 “资源可用”。 核心规则:只有获取互斥量的任务才能释放它(所有权特性)。 如果任务 A 获取互斥量后,任务 B 试图释放,会导致未定义行为(可能系统崩溃)。

3.3使用时的核心注意事项

1.二值信号量的注意事项

  • 初始化状态:创建后默认 “无信号”(值 0),需首次调用xSemaphoreGive()才能让xSemaphoreTake()获取成功。

  • 中断中必须用FromISR版本:释放信号量时,ISR 中必须用xSemaphoreGiveFromISR(),不能用普通xSemaphoreGive()(否则可能导致调度错误)。

  • 不适合保护共享资源:二值信号量没有 “所有权” 和 “优先级继承”,若用于互斥,可能导致优先级反转(高优先级任务被低优先级任务阻塞)。

  • 避免长期持有:xSemaphoreTake()获取后,应尽快用xSemaphoreGive()释放,避免其他等待任务长期阻塞。

2.互斥量的注意事项

  • 严格的所有权:必须由获取互斥量的任务释放,其他任务释放会导致错误(FreeRTOS 不检查,可能引发系统异常)。

  • 禁止在 ISR 中使用:互斥量的xSemaphoreTake()可能阻塞(xTicksToWait非 0),而 ISR 不能阻塞;且互斥量的优先级继承机制在 ISR 中无效。

  • 避免嵌套死锁:同一任务多次获取同一个互斥量会导致死锁(第一次获取成功,第二次获取时因互斥量已被自己占用而阻塞,永远无法返回)。

// 错误示例:同一任务嵌套获取互斥量
void vBadTask(void *param) {xSemaphoreTake(xMutex, portMAX_DELAY); // 第一次获取成功xSemaphoreTake(xMutex, portMAX_DELAY); // 第二次获取:阻塞,死锁!
}
  • 持有时间要短:获取互斥量后,应仅执行 “访问共享资源” 的必要操作,尽快释放,避免其他任务长时间等待。

  • 优先级继承的限制:互斥量的优先级继承只能 “减轻” 优先级反转,不能完全消除。例如,若低优先级任务 L 持有互斥量,高优先级任务 H 等待,L 会被提升到 H 的优先级,但如果此时有中优先级任务 M,M 仍可能被 L(临时高优先级)阻塞。

3.两者共有的注意事项

  • 必须检查返回值:xSemaphoreTake()xSemaphoreGive()的返回值需判断(如超时、释放失败),否则可能因资源未获取 / 未释放导致逻辑错误。

  • 句柄有效性:使用前必须确保信号量 / 互斥量创建成功(句柄非 NULL),否则调用相关函数会导致崩溃。

  • 内存管理:信号量 / 互斥量依赖 FreeRTOS 堆内存,若堆内存不足,创建会失败(返回 NULL),需确保堆大小足够(FreeRTOSConfig.h中的configTOTAL_HEAP_SIZE)。

总结

  • 二值信号量:用于 “同步”(事件通知),无所有权,可在 ISR 中用FromISR版本,注意初始化和及时释放。

  • 互斥量:用于 “互斥”(保护共享资源),有所有权和优先级继承,禁止在 ISR 中使用,严格避免嵌套死锁。

四、怎么选?用场景判断

  • 用二值信号量:当你需要 “通知” 或 “同步” 时。 例 1:按键中断触发后,用 xSemaphoreGiveFromISR() 释放信号量,唤醒等待的任务处理按键事件。 例 2:任务 A 完成数据采集后,释放信号量,通知任务 B 开始处理数据。

  • 用互斥量:当你需要 “保护共享资源” 时。 例 1:多个任务需要向同一个串口打印日志,用互斥量确保同一时间只有一个任务能操作串口。 例 2:多个任务需要读写同一块内存中的配置参数,用互斥量避免数据错乱。

总结

  • 二值信号量:像 “门铃”,谁都可以按(释放),谁都可以听(获取),用于 “通知对方做事”。

  • 互斥量:像 “厕所钥匙”,谁拿到谁用,用完必须自己还,用于 “保证只有一个人用厕所”,还能避免 “急事的人(高优先级)被慢腾腾的人(低优先级)耽误”。

记住核心原则:同步用信号量互斥用互斥量,并始终检查返回值,就能避免大部分使用错误。


文章转载自:

http://YelRKM5I.gkjnz.cn
http://6DVxGoUJ.gkjnz.cn
http://G0XrtEH1.gkjnz.cn
http://em2DOu6u.gkjnz.cn
http://hfAJhUq1.gkjnz.cn
http://jK88FA5W.gkjnz.cn
http://c2q1vLcJ.gkjnz.cn
http://onq0diRh.gkjnz.cn
http://oPpga5oY.gkjnz.cn
http://SfnWL8E4.gkjnz.cn
http://JFFsKylt.gkjnz.cn
http://PMlQve2p.gkjnz.cn
http://ce5wGq3d.gkjnz.cn
http://e41r7Gwo.gkjnz.cn
http://y9MAR2Qi.gkjnz.cn
http://WfxtqvMV.gkjnz.cn
http://y7E0NXHl.gkjnz.cn
http://jwCYhWtC.gkjnz.cn
http://hvBJ49g8.gkjnz.cn
http://tWIWbLCc.gkjnz.cn
http://2kR84XSj.gkjnz.cn
http://QgR6aDWV.gkjnz.cn
http://3BfwEl2W.gkjnz.cn
http://WlGizVny.gkjnz.cn
http://KscOz3Wp.gkjnz.cn
http://WEhVEUch.gkjnz.cn
http://6ANeP8Bc.gkjnz.cn
http://wO9W2RBz.gkjnz.cn
http://AP56upwY.gkjnz.cn
http://0RVHiiuo.gkjnz.cn
http://www.dtcms.com/a/387519.html

相关文章:

  • 记一次golang结合前端的axios进行预签名分片上传遇到403签名错误踩坑
  • LeetCode 面试经典 150_哈希表_单词规律(41_290_C++_简单)
  • 微信小程序修改页面导航标题的方式
  • Torch-Rechub学习笔记-task1
  • LightTools照明光学系统设计
  • 从技术探索到社区共建:程宇翔的隐私计算开源之路
  • 【Redis】云原生时代Redis高可用新范式:多活架构+异地容灾 生成详细内容
  • JsonCpp: 一个好用且轻量级的JSON解析库
  • 【设计模式】桥接模式
  • ACP(五):优化提示词(Prompt),精细地控制大模型的输出
  • Egg.js 性能测试全解析:从压力测试到深度调优
  • 自制脚本,解决Ubuntu20.04 键盘会突然失灵、键盘延迟突然很大问题
  • 172.在vue3中使用openlayers:引用 hover 效果,展示各种鼠标 cursor 样式
  • SpringBoot Oracle
  • LLMs之IR:《MUVERA: Multi-Vector Retrieval via Fixed Dimensional Encodings》的翻译与解读
  • Redis与Java集成实战:从入门到高级应用
  • Chromium 138 编译指南 macOS篇:配置depot_tools(三)
  • qt QHXYModelMapper详解
  • 机器学习中的编码问题和标准化:类别编码、one-hot编码缺陷及改进
  • Qt QHorizontalStackedBarSeries详解
  • Python爬虫实战:研究Pandas,构建全运会数据采集和分析系统
  • 告别冗余 DOM:Vue Fragment 用法与性能优化解析
  • 快速排序:原理、实现与优化
  • JavaScript性能优化实战:深入剖析性能瓶颈与最佳实践
  • Lattice ECP5系列FPGA介绍
  • PySpark 窗口函数row_number、lag、lead的使用简述
  • 华为FreeBuds 7i不同设备要如何连接?
  • 使用LVS架设服务器集群系统实现负载均衡与高可用的知识点详解
  • 84-dify案例分享-使用Qwen-Image实现文生图、图生图
  • 留个档,Unity,Animation控制相机,出现抖动的问题记录