news 2026/2/8 22:31:01

FreeRTOS学习(10)(互斥量+递归锁+事件组)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS学习(10)(互斥量+递归锁+事件组)

对于上一张讲过的优先级反转的的问题的时候我们引入了互斥量(也叫互斥锁)的概念,其实互斥量就是特俗的信号量。

那互斥量怎么去使用呢?

很简单,和信号量一样创建用xSemaphoreCreateMutex(),获取用xSemaphoreTake(),释放用xSemaphoreGive(),保持保护区域尽量短,FreeRTOS会自动处理优先级反转。

互斥量是一种特殊的二进制信号量但是特殊的点是二进制信号量和计数信号量获取者和释放者可以不是同一个任务,但是互斥信号量获取者和释放者必须是同一个人。

函数

创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

/* 创建一个互斥量,返回它的句柄。 * 此函数内部会分配互斥量结构体 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateMutex( void ); /* 创建一个互斥量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

#define configUSE_MUTEXES 1

要注意的是,互斥量不能在ISR中使用。

各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/* * xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量 */ void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); /* 释放 */ BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 获得 */ BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

但是在使用时也会遇到别的问题比如在嵌套或者递归函数中遇到了信号量冲突

比如任务A拿也互斥量M,但是任务A内部的嵌套函数需要信号量M但是拿不到,这样就会阻塞卡死

这是就需要用到递归锁

递归锁允许同一任务多次获取同一锁,通过内部计数机制避免自我死锁,必须使用TakeRecursive/GiveRecursive配套函数,获取N次就必须释放N次!

其他使用方式就和互斥锁一样,最重要的就是,获取N次就必须释放N次!获取N次就必须释放N次!获取N次就必须释放N次!

递归锁一般互斥量
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
获得xSemaphoreTakeRecursivexSemaphoreTake
释放xSemaphoreGiveRecursivexSemaphoreGive
/* 创建一个递归锁,返回它的句柄。* * 此函数内部会分配互斥量结构体* * 返回值: 返回句柄,非NULL表示成功* */ SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); */ 释放 */ BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore ); */ 获得 */ BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

我们每次建立一个任务只能触发一个任务函数,那有没有什么方法可以一个任务,触发多个函数话或者信号量呢。

有的兄弟有的

这个就是事件组

使用事件组必须要引用的头文件

#include "event_groups.h"

主要特点:

  • 每个事件由事件位(bit)表示,最多32个事件(32位)

  • 支持"或"(任意事件发生)和"与"(所有事件发生)两种等待模式

  • 事件标志被设置后不会自动清除,需要手动清除

  • 线程安全,可在任务和中断中使用

事件组的使用主要分为4个主要的部分就是:创建,设置,等待和是释放

首先是创建事件组

// 创建事件组 EventGroupHandle_t xEventGroupCreate(void); // 示例 EventGroupHandle_t xEventGroup = xEventGroupCreate(); if (xEventGroup == NULL) { // 创建失败处理 }

其次设置事件组

c // 在任务中设置事件位 EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet); // 在中断中设置事件位(带中断安全版本) BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken);

然后是等待事件组

EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, // pdTRUE: 等待成功后清除事件位 const BaseType_t xWaitForAllBits, // pdTRUE: 等待所有位; pdFALSE: 等待任意位 TickType_t xTicksToWait);

最后是释放事件组也叫清除事件组

// 清除指定事件位 EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear); // 在中断中清除事件位 BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear); // 获取当前事件位值(不修改) EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup); EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);

就这四部使用起来也比较简单给大家一个一般的使用格式

// 定义事件位(建议使用1左移方式) #define BIT_0 (1 << 0) #define BIT_1 (1 << 1) #define BIT_2 (1 << 2) // 创建事件组 EventGroupHandle_t xEventGroup; void vTask1(void *pvParameters) { EventBits_t uxBits; for (;;) { // 等待BIT_0或BIT_1任意一个事件发生 uxBits = xEventGroupWaitBits( xEventGroup, // 事件组句柄 BIT_0 | BIT_1, // 等待的事件位 pdTRUE, // 成功等待后清除这些位 pdFALSE, // 任意一个事件即可 portMAX_DELAY); // 无限等待 if ((uxBits & BIT_0) != 0) { // BIT_0被设置 } if ((uxBits & BIT_1) != 0) { // BIT_1被设置 } } } void vTask2(void *pvParameters) { for (;;) { // 执行某些操作后设置事件 xEventGroupSetBits(xEventGroup, BIT_0); vTaskDelay(pdMS_TO_TICKS(1000)); } }

第一步宏定义的操作可有可无但是这样会大大提高代码的可读性

对于这个串代码进行变种就能变成任务的与操作

// 必需的头文件 #include "FreeRTOS.h" #include "task.h" #include "event_groups.h" // 可选:用于调试输出 #include <stdio.h> // 定义事件位 #define BIT_0 (1 << 0) #define BIT_1 (1 << 1) #define BIT_2 (1 << 2) // 事件组句柄 EventGroupHandle_t xEventGroup; void vSenderTask(void *pvParameters) { for (;;) { // 设置事件位 xEventGroupSetBits(xEventGroup, BIT_0); // 任务延迟 vTaskDelay(pdMS_TO_TICKS(1000)); } } void vReceiverTask(void *pvParameters) { EventBits_t uxBits; for (;;) { // 等待事件位 uxBits = xEventGroupWaitBits( xEventGroup, BIT_0 | BIT_1, pdTRUE, // 清除事件位 pdFALSE, // 等待任意位 portMAX_DELAY // 无限等待 ); if (uxBits & BIT_0) { printf("BIT_0 received\n"); } } } int main(void) { // 创建事件组 xEventGroup = xEventGroupCreate(); // 创建任务 xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, 1, NULL); xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 启动调度器 vTaskStartScheduler(); return 0; }

然后也可以改成在中断中使用的样子

// 必需的头文件 #include "FreeRTOS.h" #include "task.h" #include "event_groups.h" // 可选:硬件相关头文件 #include "stm32f4xx.h" // 示例:STM32 MCU EventGroupHandle_t xEventGroup; // 中断服务程序 void TIM2_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 在中断中设置事件位 xEventGroupSetBitsFromISR( xEventGroup, BIT_0, &xHigherPriorityTaskWoken ); // 如果需要,执行上下文切换 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }

我在配置的过程中遇到了一些错误,在这分享给大家

七、常见错误排查

错误1:未找到头文件

错误信息:fatal error: event_groups.h: No such file or directory 解决方案:确保include路径正确 在MX或IDE中正确设置include路径 例如:-I../FreeRTOS/Source/include

错误2:未启用事件组功能

//确保在FreeRTOSConfig.h中启用 #define configUSE_EVENT_GROUPS 1 // 必须为1

错误3:链接错误

错误:undefined reference to `xEventGroupCreate'

解决方案:确保链接了FreeRTOS库文件

在Makefile中添加:freertos.c 或相应的库文件

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

DLSS Swapper完整教程:免费解锁游戏性能的终极方案

DLSS Swapper完整教程&#xff1a;免费解锁游戏性能的终极方案 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper DLSS Swapper是一款革命性的免费工具&#xff0c;专门为NVIDIA显卡用户设计&#xff0c;能够让你轻松管理…

作者头像 李华
网站建设 2026/2/8 17:58:55

支持109种语言的文档解析SOTA模型|PaddleOCR-VL-WEB快速上手指南

支持109种语言的文档解析SOTA模型&#xff5c;PaddleOCR-VL-WEB快速上手指南 1. 简介&#xff1a;面向多语言文档解析的高效视觉-语言模型 在企业级信息处理场景中&#xff0c;如何从扫描件、PDF、图像等非结构化文档中高效提取结构化内容&#xff0c;一直是自动化流程中的关…

作者头像 李华
网站建设 2026/2/7 17:21:48

DLSS Swapper终极教程:轻松管理游戏DLSS版本,性能优化一键搞定

DLSS Swapper终极教程&#xff1a;轻松管理游戏DLSS版本&#xff0c;性能优化一键搞定 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 想要在游戏中获得最佳性能表现&#xff1f;DLSS Swapper正是你需要的革命性工具。…

作者头像 李华
网站建设 2026/2/5 20:19:51

DLSS Swapper:游戏画质升级的革命性解决方案

DLSS Swapper&#xff1a;游戏画质升级的革命性解决方案 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 还在为游戏画面不够清晰而烦恼&#xff1f;当别人在4K分辨率下享受极致细节时&#xff0c;你是否还在忍受模糊的…

作者头像 李华
网站建设 2026/2/5 12:45:30

DLSS版本管理终极指南:如何用DLSS Swapper彻底优化游戏体验?

DLSS版本管理终极指南&#xff1a;如何用DLSS Swapper彻底优化游戏体验&#xff1f; 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 还在为游戏画面卡顿而烦恼吗&#xff1f;想要轻松切换不同版本的DLSS文件却无从下手…

作者头像 李华
网站建设 2026/2/6 6:43:26

10分钟精通ZeroOmega:智能代理切换完全指南

10分钟精通ZeroOmega&#xff1a;智能代理切换完全指南 【免费下载链接】ZeroOmega Manage and switch between multiple proxies quickly & easily. 项目地址: https://gitcode.com/gh_mirrors/ze/ZeroOmega 在复杂的网络开发环境中&#xff0c;高效管理多个代理配…

作者头像 李华