06 FreeRTOS 互斥量(mutex)

2024-05-30 01:52
文章标签 互斥 06 freertos mutex

本文主要是介绍06 FreeRTOS 互斥量(mutex),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、互斥量的使用场景

        用于保护临界资源,在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。 比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是AB的信 息混杂在一起。

        在使用二进制信号量达到互斥的效果的时候,不能保证谁上锁谁解锁的问题。假如A在上锁然后去上厕所,这时B来解锁了,然后C来发现没有锁也来上厕所,那么这时A就曝光了。

        虽然互斥量也不能在代码上解决谁上锁谁解锁的问题,但是它可以解决优先级反转、递归上锁/解锁的问题。

        优先级反转:假设任务A、B、C的优先级分别为1、2、3,因为A先运行去获得了锁,后面C去抢占获得锁时,因为没有锁了所以被阻塞了,这时B运行,加入B运行过程中一直没有放弃CPU资源,这时候A没法执行,不能去放锁,而C此时也一直被阻塞。这就是高优先级的反而不能执行,这种现象就叫优先级反转。

        解决这种可以通过优先级继承,A开始执行,执行过程中去获得这个锁,然后轮到B执行,因为B的优先级比较高,所以B可以抢占A,然后轮到C来执行,C的优先级更高,可以抢占B,C来调用lock的时候,因为锁已经被A拿了,于是C会进入阻塞状态,在C调用lock的时候还会做个优先级继承,将自身的优先级继承给A,A的优先级变成了3,这时候A就可以执行去放锁,放锁之后又轮到C来执行。

        递归上锁:假设在任务A、B中都有上锁和解锁,在任务A执行过程中,去调用任务B,但是在B执行时,锁已经在之前被A获得并且没有释放,这就会造成递归上锁,这时任务A、B会进入阻塞状态,造成死锁。

        解决这种可以通过递归锁,即任务A在持有这个锁的过程中还可以继续去上锁。

2、互斥量函数

2.1 创建

        互斥量是一种特殊的二进制信号量。 使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。

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

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

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

#define configUSE_MUTEXES 1

 2.2 其它函数

        要注意的是,互斥量不能在ISR中使用。 各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

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

2.3 递归锁函数

        递归锁的函数跟一般互斥量的函数名不一样,参数类型一样。

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

3、示例代码

3.1 互斥

        要配置一下#define configUSE_MUTEXES 1

static SemaphoreHandle_t xSemUART;void TaskGenericFunction( void * param)
{while(1){		xSemaphoreTake(xSemUART, portMAX_DELAY);	//在使用串口前先take这个信号量printf("%s\r\n", (char *)param);xSemaphoreGive(xSemUART);	//用完释放掉vTaskDelay(1);}	
}//main函数中
xSemUART = xSemaphoreCreateMutex();	//创建互斥量,互斥量初始值是1xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);

3.2 优先级反转/继承

static volatile uint8_t flagLPTaskRun = 0;
static volatile uint8_t flagMPTaskRun = 0;
static volatile uint8_t flagHPTaskRun = 0;/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;static void vLPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	uint32_t i;char c = 'A';printf("LPTask start\r\n");/* 无限循环 */for( ;; ){	flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);/* 耗时很久 */printf("LPTask take the Lock for long time");for (i = 0; i < 500; i++) {flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;printf("%c", c + i);}printf("\r\n");/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);vTaskDelay(xTicksToWait);}
}static void vMPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );	flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;printf("MPTask start\r\n");/* 让LPTask、HPTask先运行 */	vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){	flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;}
}static void vHPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask start\r\n");/* 让LPTask先运行 */	vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){	flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask wait for Lock\r\n");/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);}
}int main( void )
{prvSetupHardware();/* 创建互斥量/二进制信号量 */xLock = xSemaphoreCreateBinary( );xSemaphoreGive(xLock);if( xLock != NULL ){/* 创建3个任务: LP,MP,HP(低/中/高优先级任务)*/xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );/* 启动调度器 */vTaskStartScheduler();}else{/* 无法创建互斥量/二进制信号量 */}/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}

        该程序实现了优先级反转的现象 

        只要将上述程序中的代码做出以下修改即可解决 

int main( void )
{/* 创建互斥量/二进制信号量 *///xLock = xSemaphoreCreateBinary( );//xSemaphoreGive(xLock);xLock = xSemaphoreCreateMutex();    //在这里修改}

3.3 递归锁

        互斥量的本意是:谁持有,就由谁释放。

        但是FreeRTOS并没有实现这一点,Linux也没有,A持有,B也可以释放。

        通过递归锁实现了:谁持有,就由谁释放;递归上锁/解锁。

        设置一个程序,任务四和任务五交替使用串口打印,之后让任务五来偷偷解锁。

void TaskGenericFunction( void * param)
{while(1){		xSemaphoreTake(xSemUART, portMAX_DELAY);	//在使用串口前先take这个信号量printf("%s\r\n", (char *)param);xSemaphoreGive(xSemUART);	//用完释放掉vTaskDelay(1);}	
}void Task5Function( void * param)
{vTaskDelay(10);while(1){while(1){if(xSemaphoreTake(xSemUART, 0) != pdTRUE){xSemaphoreGive(xSemUART);	//放锁}else{break;}}printf("%s\r\n", (char *)param);xSemaphoreGive(xSemUART);	//放锁vTaskDelay(1);}	
}//main函数中
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
xTaskCreate(Task5Function, "Task5", 100, "Task 5 is running", 1, NULL);

        通过结果发现后面串口打印出来的数据乱套了,证明一般的互斥量没有实现谁上锁就由谁来解锁。

        修改上述代码通过递归锁来查看。

        使用递归锁要先定义#define configUSE_RECURSIVE_MUTEXES 1

void TaskGenericFunction( void * param)
{while(1){		xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);	//在使用串口前先take这个信号量printf("%s\r\n", (char *)param);xSemaphoreGiveRecursive(xSemUART);	//用完释放掉vTaskDelay(1);}	
}void Task5Function( void * param)
{vTaskDelay(10);while(1){while(1){if(xSemaphoreTakeRecursive(xSemUART, 0) != pdTRUE){xSemaphoreGiveRecursive(xSemUART);	//放锁}else{break;}}printf("%s\r\n", (char *)param);xSemaphoreGiveRecursive(xSemUART);	//放锁vTaskDelay(1);}	
}//main函数中
xSemUART = xSemaphoreCreateRecursiveMutex();	//创建递归锁

        通过结果可以看出几个任务都正常的使用串口了,完成了谁上锁就由谁来解锁的问题。

这篇关于06 FreeRTOS 互斥量(mutex)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1015251

相关文章

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

FreeRTOS-基本介绍和移植STM32

FreeRTOS-基本介绍和STM32移植 一、裸机开发和操作系统开发介绍二、任务调度和任务状态介绍2.1 任务调度2.1.1 抢占式调度2.1.2 时间片调度 2.2 任务状态 三、FreeRTOS源码和移植STM323.1 FreeRTOS源码3.2 FreeRTOS移植STM323.2.1 代码移植3.2.2 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系

FreeRTOS内部机制学习03(事件组内部机制)

文章目录 事件组使用的场景事件组的核心以及Set事件API做的事情事件组的特殊之处事件组为什么不关闭中断xEventGroupSetBitsFromISR内部是怎么做的? 事件组使用的场景 学校组织秋游,组长在等待: 张三:我到了 李四:我到了 王五:我到了 组长说:好,大家都到齐了,出发! 秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的

FreeRTOS学习笔记(六)队列

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、队列的基本内容1.1 队列的引入1.2 FreeRTOS 队列的功能与作用1.3 队列的结构体1.4 队列的使用流程 二、相关API详解2.1 xQueueCreate2.2 xQueueSend2.3 xQueueReceive2.4 xQueueSendFromISR2.5 xQueueRecei

FreeRTOS学习笔记(二)任务基础篇

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、 任务的基本内容1.1 任务的基本特点1.2 任务的状态1.3 任务控制块——任务的“身份证” 二、 任务的实现2.1 定义任务函数2.2 创建任务2.3 启动任务调度器2.4 任务的运行与切换2.4.1 利用延时函数2.4.2 利用中断 2.5 任务的通信与同步2.6 任务的删除2.7 任务的通知2

前端-06-eslint9大变样后,如何生成旧版本的.eslintrc.cjs配置文件

目录 问题解决办法 问题 最近在写一个vue3+ts的项目,看了尚硅谷的视频,到了配置eslintrc.cjs的时候我犯了难,因为eslint从9.0之后重大更新,跟以前完全不一样,但是我还是想用和老师一样的eslintrc.cjs文件,该怎么做呢? 视频链接:尚硅谷Vue项目实战硅谷甄选,vue3项目+TypeScript前端项目一套通关 解决办法 首先 eslint 要

FreeRTOS学习笔记(四)Freertos的中断管理及临界保护

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、Cortex-M 中断管理1.1 中断优先级分组1.2 相关寄存器1.3 相关宏定义1.4 FreeRTOS 开关中断 二、临界段及其保护2.1 taskENTER_CRITICAL( ) 和 taskEXIT_CRITICAL( )2.2 taskENTER_CRITICAL_FROM_ISR( )

C++入门(06)安装QT并快速测试体验一个简单的C++GUI项目

文章目录 1. 清华镜像源下载2. 安装3. 开始菜单上的 QT 工具4. 打开 Qt Creator5. 简单的 GUI C++ 项目5.1 打开 Qt Creator 并创建新项目5.2 设计界面5.3 添加按钮的点击事件5.4 编译并运行项目 6. 信号和槽(Signals and Slots) 这里用到了C++类与对象的很多概念 1. 清华镜像源下载 https://

F12抓包06-4:导出metersphere脚本

metersphere是一站式的开源持续测试平台,我们可以将浏览器请求导出为HAR文件,导入到metersphere,生成接口测试。 metersphere有2种导入入口(方式),导入结果不同:         1.导入到“接口定义”:自动生成接口API和单接口case(接口自动去重;每个请求生成不同case,重复的请求生成重复的case,名称自动加数字后缀,自动与接口关联)。