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

【FreeRTOS】 二值信号量与互斥量(CMSIS-RTOS v2 版本)

目录

一、基本概念(CMSIS-RTOS v2 视角)

1.二值信号量(Binary Semaphore)

2.互斥量(Mutex)

二、核心区别(CMSIS-RTOS v2 特性)

关键区别详解

1.所有权(核心差异)

2.优先级反转处理(互斥量核心特性)

(1)用二值信号量保护资源(会出现优先级反转)

(2)用互斥量保护资源(避免优先级反转)

三、使用方法(适配 CubeMX + Keil 工程)

1.二值信号量(同步场景)

(1)创建二值信号量

(2)获取信号量(任务中等待事件)

(3)释放信号量(任务 / ISR 中发通知)

2.互斥量(互斥场景)

(1)创建互斥量

(2)加锁(任务中访问资源前)

(3)解锁(任务中访问资源后)

四、核心注意事项(CubeMX 工程适配)

1.二值信号量注意事项

2.互斥量注意事项

3.通用注意事项(CubeMX 工程特有)

五、场景选择指南(CubeMX 工程实战)

总结


对于使用 CubeMX 配置 FreeRTOS(CMSIS-RTOS v2 接口)的开发者,二值信号量和互斥量的底层逻辑与原生 FreeRTOS 一致,但函数接口、参数格式有标准化差异。本文从 “功能用途”“核心特性”“使用场景” 三个维度,结合 CMSIS-RTOS v2 函数(适配 CubeMX 生成的 Keil 工程),梳理两者的区别与使用方法。

一、基本概念(CMSIS-RTOS v2 视角)

CMSIS-RTOS v2 是 ARM 推出的标准化 RTOS 接口,CubeMX 生成的 FreeRTOS 工程默认采用这套接口,核心是通过统一函数名和参数格式,降低跨 RTOS 移植成本。

1.二值信号量(Binary Semaphore)

类比 “开关”,仅两种状态:“有信号”(可用) 和 “无信号”(不可用),核心用于 任务间 / 任务与 ISR 间的同步(如 “事件通知”“唤醒任务”)。

  • CMSIS-RTOS v2 中,二值信号量通过 osSemaphoreId_t 类型句柄管理,创建后默认状态为 “无信号”。

  • 核心操作:

    • 释放(发信号):osSemaphoreRelease()(任务中释放)、osSemaphoreReleaseFromISR()(中断中释放);

    • 获取(等信号):osSemaphoreAcquire()

2.互斥量(Mutex)

类比 “钥匙”,用于 保护共享资源(如串口、传感器、全局变量),确保同一时间只有一个任务能访问资源。

  • CMSIS-RTOS v2 中,互斥量通过 osMutexId_t 类型句柄管理,创建后默认状态为 “可用”(资源未被占用)。

  • 核心操作:

    • 加锁(拿钥匙):osMutexAcquire()

    • 解锁(还钥匙):osMutexRelease()

二、核心区别(CMSIS-RTOS v2 特性)

关键区别详解

1.所有权(核心差异)

  • 二值信号量:无归属关系。例如: 任务 A 调用 osSemaphoreRelease() 释放信号量(设为 “有信号”),任务 B 可调用 osSemaphoreAcquire() 获取,之后任务 C 还能再次释放 —— 谁都能 “发信号”,谁都能 “等信号”。

  • 互斥量:严格的所有权。例如: 任务 A 调用 osMutexAcquire() 加锁(拿到钥匙),则 只有任务 A 能调用 osMutexRelease() 解锁;若任务 B 试图释放任务 A 持有的互斥量,会返回 osErrorResource 错误,导致程序异常。

2.优先级反转处理(互斥量核心特性)

这是互斥量与二值信号量最关键的区别,结合 CMSIS-RTOS v2 行为举例:

假设有 3 个任务:高优先级任务 H、中优先级任务 M、低优先级任务 L,均需访问同一共享资源。

(1)用二值信号量保护资源(会出现优先级反转)
  • 步骤:

    • 低优先级任务 L 调用 osSemaphoreAcquire() 获取二值信号量,开始访问资源;

    • 高优先级任务 H 就绪,调用 osSemaphoreAcquire() 拿信号量失败,进入阻塞态;

    • 中优先级任务 M 就绪(优先级高于 L),抢占 L 的 CPU,导致 L 暂停访问资源;

    • M 运行期间,H 持续阻塞(因 L 无法释放信号量),最终 最高优先级的 H 被中优先级的 M 拖累—— 这就是优先级反转。

  • 原因:二值信号量无 “优先级继承” 特性,无法阻止低优先级任务被中优先级任务抢占。

(2)用互斥量保护资源(避免优先级反转)
  • 步骤:

    • 低优先级任务 L 调用 osMutexAcquire() 加锁,开始访问资源;

    • 高优先级任务 H 就绪,调用 osMutexAcquire() 加锁失败,进入阻塞态;

    • CMSIS-RTOS v2 检测到 “高优先级任务 H 等待 L 持有的互斥量”,自动将 L 的优先级临时提升至与 H 相同;

    • 中优先级任务 M 就绪后,因优先级低于 “临时提权的 L”,无法抢占 L,L 可快速完成资源访问并解锁;

    • 互斥量释放后,H 立即加锁访问资源,避免优先级反转。

  • 原因:互斥量的 “优先级继承” 特性,确保持有资源的低优先级任务不被中优先级任务打断。

三、使用方法(适配 CubeMX + Keil 工程)

CubeMX 配置 FreeRTOS 后,会自动生成 CMSIS-RTOS v2 相关头文件(cmsis_os2.h)和初始化框架,以下代码可直接嵌入工程使用。

1.二值信号量(同步场景)

核心用于 “事件通知”(如中断唤醒任务、任务间同步),使用流程分 “创建 → 获取 → 释放” 三步。

(1)创建二值信号量

通过 osSemaphoreNew() 创建,参数需指定 “信号量计数”(二值信号量计数固定为 1)、“初始值”(0 表示默认无信号)和 “属性配置”(可选,如名称)。

#include "cmsis_os2.h"// 定义二值信号量句柄(全局可见,供任务/ISR 访问)
osSemaphoreId_t xBinarySem;// 信号量属性配置(可选,用于设置名称,调试用)
const osSemaphoreAttr_t BinarySem_Attr = {.name = "BinarySem_Key"  // 信号量名称,可在调试工具中识别
};// 初始化二值信号量(通常在 main 函数或 RTOS 初始化钩子中调用)
void Sem_Init(void) {// 创建二值信号量:计数=1(二值),初始值=0(无信号),属性=BinarySem_AttrxBinarySem = osSemaphoreNew(1, 0, &BinarySem_Attr);// 检查创建是否成功(CubeMX 生成的工程中,堆不足会返回 NULL)if (xBinarySem == NULL) {// 错误处理:如通过 SEGGER_RTT 打印日志SEGGER_RTT_printf(0, "二值信号量创建失败!\r\n");}
}

(2)获取信号量(任务中等待事件)

通过 osSemaphoreAcquire() 等待信号量,参数为 “信号量句柄” 和 “超时时间(ms)”,超时后返回错误。

// 等待信号量的任务(例如:按键事件处理任务)
void Task_WaitSem(void *argument) {osStatus_t status;  // 存储函数返回状态for (;;) {// 等待信号量,无限期阻塞(osWaitForever),直到有信号status = osSemaphoreAcquire(xBinarySem, osWaitForever);if (status == osOK) {// 信号量获取成功:处理事件(如按键逻辑、数据解析)SEGGER_RTT_printf(0, "收到信号,执行事件处理...\r\n");} else {// 异常处理(如超时、信号量无效)SEGGER_RTT_printf(0, "获取信号量失败,状态:%d\r\n", status);}}
}

(3)释放信号量(任务 / ISR 中发通知)

  • 任务中释放:直接调用 osSemaphoreRelease()

  • 中断中释放:必须调用 osSemaphoreReleaseFromISR()(ISR 安全版本),避免调度错误。

// 示例1:任务中释放信号量(如数据采集完成后通知处理任务)
void Task_SendSem(void *argument) {for (;;) {// 模拟数据采集(如读取传感器)SEGGER_RTT_printf(0, "数据采集完成,发送信号...\r\n");// 释放信号量(设为“有信号”)if (osSemaphoreRelease(xBinarySem) != osOK) {SEGGER_RTT_printf(0, "任务中释放信号量失败!\r\n");}osDelay(1000);  // 每隔1秒发送一次信号}
}// 示例2:中断中释放信号量(如按键中断唤醒任务)
void EXTI0_IRQHandler(void) {// 检查中断标志(硬件相关,CubeMX 生成的 HAL 函数)if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {osStatus_t status;// 中断中释放信号量,需传入“是否需要任务切换”的标志status = osSemaphoreReleaseFromISR(xBinarySem, NULL);if (status != osOK) {// 中断中无法打印(可选通过全局变量标记错误)}// 清除中断标志__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);}
}

2.互斥量(互斥场景)

核心用于 “保护共享资源”,使用流程分 “创建 → 加锁 → 解锁” 三步,需严格遵守 “谁加锁谁解锁” 的规则。

(1)创建互斥量

通过 osMutexNew() 创建,参数为 “属性配置”(可选,如名称、是否支持递归锁),默认支持优先级继承。

#include "cmsis_os2.h"// 定义互斥量句柄(全局可见,保护共享资源如串口)
osMutexId_t xMutex;// 互斥量属性配置(可选)
const osMutexAttr_t Mutex_Attr = {.name = "Mutex_UART",  // 互斥量名称,调试用.attr_bits = osMutexRecursive  // 可选:设置为递归互斥量(允许同一任务多次加锁)
};// 初始化互斥量(同信号量,在 RTOS 初始化时调用)
void Mutex_Init(void) {// 创建互斥量,传入属性配置(NULL 表示默认配置)xMutex = osMutexNew(&Mutex_Attr);if (xMutex == NULL) {SEGGER_RTT_printf(0, "互斥量创建失败!\r\n");}
}

(2)加锁(任务中访问资源前)

通过 osMutexAcquire() 加锁,参数为 “互斥量句柄” 和 “超时时间(ms)”,超时后无法访问资源。

// 任务1:使用串口打印(共享资源)
void Task_UART1(void *argument) {osStatus_t status;for (;;) {// 加锁:最多等待 100ms,超时则放弃访问status = osMutexAcquire(xMutex, 100);if (status == osOK) {// 加锁成功:安全访问共享资源(如串口打印)SEGGER_RTT_printf(0, "任务1:正在使用串口...\r\n");osDelay(50);  // 模拟串口操作耗时// 解锁:必须在同一任务中释放osMutexRelease(xMutex);} else {// 加锁失败(超时或互斥量无效)SEGGER_RTT_printf(0, "任务1:获取串口锁失败,状态:%d\r\n", status);}osDelay(200);  // 任务周期}
}// 任务2:同样需要使用串口(与任务1互斥)
void Task_UART2(void *argument) {osStatus_t status;for (;;) {status = osMutexAcquire(xMutex, 100);if (status == osOK) {SEGGER_RTT_printf(0, "任务2:正在使用串口...\r\n");osDelay(50);osMutexRelease(xMutex);} else {SEGGER_RTT_printf(0, "任务2:获取串口锁失败,状态:%d\r\n", status);}osDelay(300);}
}

(3)解锁(任务中访问资源后)

通过 osMutexRelease() 解锁,必须由加锁的任务调用,否则返回 osErrorResource 错误,甚至导致系统异常。

// 错误示例:任务A加锁,任务B解锁
void Task_Error(void *argument) {// 任务A加锁osMutexAcquire(xMutex, osWaitForever);SEGGER_RTT_printf(0, "任务A加锁成功\r\n");// 任务B尝试解锁(错误!)osMutexRelease(xMutex);  // 若在任务B中调用,返回 osErrorResource
}

四、核心注意事项(CubeMX 工程适配)

1.二值信号量注意事项

  • 初始化状态:osSemaphoreNew(1, 0, ...) 中 “初始值 = 0” 表示默认无信号,需先调用 osSemaphoreRelease() 才能让 osSemaphoreAcquire() 获取成功。

  • 中断中必须用 FromISR 版本:中断中释放信号量时,必须使用 osSemaphoreReleaseFromISR(),不能用普通 osSemaphoreRelease()(会破坏调度器状态)。

  • 不适合保护资源:若用二值信号量替代互斥量保护资源,会因无优先级继承导致优先级反转,需避免。

  • 超时时间单位:CMSIS-RTOS v2 中所有超时参数单位为 毫秒(ms)(原生 FreeRTOS 为节拍数),无需转换(CubeMX 已配置 osKernelGetTickFreq() 自动适配)。

2.互斥量注意事项

  • 严格所有权:仅加锁任务可解锁,跨任务解锁会导致错误(CubeMX 生成的工程中,错误状态可通过 osStatus_t 返回值捕获)。

  • 禁止在 ISR 中使用:osMutexAcquire() 可能阻塞(超时时间非 0),而 ISR 必须快速执行,不允许阻塞;且互斥量的优先级继承在 ISR 中无效。

  • 递归锁慎用:通过 osMutexRecursive 属性创建的递归互斥量,允许同一任务多次加锁,但需确保 “加锁次数 = 解锁次数”,否则会导致死锁。

// 递归锁正确用法
osMutexAcquire(xMutex, osWaitForever);  // 第1次加锁
osMutexAcquire(xMutex, osWaitForever);  // 第2次加锁(仅递归锁支持)
// 业务逻辑...
osMutexRelease(xMutex);  // 第1次解锁
osMutexRelease(xMutex);  // 第2次解锁(必须配对)
  • 持有时间要短:加锁后仅执行 “访问共享资源” 的必要操作(如读写变量、调用外设函数),尽快解锁,避免其他任务长期等待。

3.通用注意事项(CubeMX 工程特有)

  • 句柄有效性检查:CubeMX 生成的工程中,信号量 / 互斥量创建失败(返回 NULL)通常是因为 FreeRTOS 堆内存不足,需在 CubeMX → Middleware → FreeRTOS → Heap Size 中增大堆大小(建议至少 2KB 以上)。

  • 任务栈大小配置:调用信号量 / 互斥量函数会占用任务栈空间,CubeMX 中需为任务配置足够栈大小(CubeMX → Tasks and Queues → 选择任务 → Stack Size,建议至少 512 字节)。

  • 中断优先级配置:若在 ISR 中使用信号量,需确保中断优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY(CubeMX 中在 FreeRTOS → Configuration → Kernel Settings 配置),否则无法调用 FromISR 版本函数。

五、场景选择指南(CubeMX 工程实战)

总结

CMSIS-RTOS v2 接口下,二值信号量和互斥量的核心逻辑与原生 FreeRTOS 一致,区别仅在于函数名和参数格式的标准化。记住以下核心原则,即可在 CubeMX 工程中正确使用:

  • 同步用二值信号量:像 “门铃”,谁都能按(释放)、谁都能听(获取),用于 “通知事件”;

  • 互斥用互斥量:像 “厕所钥匙”,谁拿谁用、用完自还,用于 “保护资源”,并能避免优先级反转。

http://www.dtcms.com/a/388819.html

相关文章:

  • Qt C++ :Qt全局定义<QtGlobal>
  • 【STL源码剖析】从源码看 list:从迭代器到算法
  • MySQL 专题(三):事务与锁机制深度解析
  • 使用BLIP训练自己的数据集(图文描述)
  • Geoserver修行记--在geoserver中如何复制某个图层组内容
  • DBG数据库透明加密网关:SQLServer应用免改造的安全防护方案,不限制开发语言的加密网关
  • 不同上位开发语言、PLC下位平台、工业协议与操作系统平台下的数据类型通用性与差异性详解
  • 【入门篇|第二篇】从零实现选择、冒泡、插入排序(含对数器)
  • javaweb Servlet基本介绍及开发流程
  • MySQL MHA高可用
  • 整体设计 逻辑拆解之2 实现骨架:一元谓词+ CNN的谓词系统
  • SpEL(Spring Expression Language)学习笔记
  • Java 字节码进阶3:面向对象多态在字节码层面的原理?
  • Tensor :核心概念、常用函数与避坑指南
  • 机器学习实战·第四章 训练模型(1)
  • 一次因表单默认提交导致的白屏排查记录
  • Linux:io_uring
  • 《第九课——C语言判断:从Java的“文明裁决“到C的“原始决斗“——if/else的生死擂台与switch的轮盘赌局》
  • 学习日报|Spring 全局异常与自定义异常拦截器执行顺序问题及解决
  • Spring Boot 参数处理
  • Debian系统基本介绍:新手入门指南
  • Spring Security 框架
  • Qt QPercentBarSeries详解
  • RTT操作系统(3)
  • DNS服务管理
  • IDA Pro配置与笔记
  • 虚函数表在单继承与多继承中的实现机制
  • 矿石生成(1)
  • Linux 线程的概念
  • Unity学习之资源管理(Resources、AssetDatabase、AssetBundle、Addressable)