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

freeRTOS互斥量(mutex)

目录

前言

一、互斥量概述

二、互斥量函数

1.创建

2.其他函数

三、优先级反转示例

1.概念

2.代码示例

四、优先级继承 

1.概念

2.代码示例

五、递归锁

1.死锁的概念

2.自我死锁

3.函数

4.递归锁代码示例


前言

在之前的信号量中,我们想要实现互斥的效果,即独占的享用临界资源,假设厕所临界资源,怎么独占?自己开门上锁,完事了自己开锁。

但是freeRTOS的信号量并没有实现自己上的锁只能自己开这个功能,要想实现互斥,必须要有个前提条件:

        \bullet 没有坏人:别的任务不会"give"信号量(不撬门)

在我之前的文章关于Linux系统的互斥锁中,举得代码例子也是凭空拿了一把锁,这就说明要想通过信号量来实现互斥,需要程序员自己约定。


一、互斥量概述

可以看到,使用信号量确实也可以实现互斥访问,但是不完美。

使用互斥量可以解决这个问题,互斥量的名字取得很好:
        \bullet 量:值为0、1
        \bullet 互斥:用来实现互斥访问
它的核心在于:谁上锁,就只能由谁开锁。

很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:
        \bullet 即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。
        \bullet 谁上锁、谁释放:只是约定。

互斥锁解决的核心问题其实是优先级反转优先级继承

互斥量其实就是一种特殊的二进制信号量,只不过它能解决优先级反转和实现优先级继承。


二、互斥量函数

1.创建

使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。

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

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

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

#define configUSE_MUTEXES 1

注意:二级制信号量初始值是0,创建后需要Give一次;互斥量初始值是1,创建后不需要Give一次。 

2.其他函数

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

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

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

三、优先级反转示例

1.概念

假设任务A、B都想使用串口,A优先级比较低:
        \bullet 任务A获得了串口的互斥量
        \bullet 任务B也想使用串口,它将会阻塞、等待A释放互斥量
        \bullet 高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)

互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二进制信号量的差别。

2.代码示例

main函数创建了3个任务:LPTask/MPTask/HPTask(低/中/高优先级任务),代码如下:

/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
int main( void )
{prvSetupHardware();/* 创建二进制信号量!!!!!! */xLock = xSemaphoreCreateBinary();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;
}

注意:创建的是二进制信号量,用于演示优先级反转!!!

LPTask/MPTask/HPTask三个任务的代码和运行过程如下图所示:

\bullet A:HPTask优先级最高,它最先运行。在这里故意打印,这样才可以观察到flagHPTaskRun的脉          冲。
\bullet HP Delay:HPTask阻塞
\bullet B:MPTask开始运行。在这里故意打印,这样才可以观察到flagMPTaskRun的脉冲。
\bullet MP Delay:MPTask阻塞
\bullet C:LPTask开始运行,获得二进制信号量,然后故意打印很多字符
\bullet D:HP Delay时间到,HPTask恢复运行,它无法获得二进制信号量,一直阻塞等待
\bullet E:MP Delay时间到,MPTask恢复运行,它比LPTask优先级高,一直运行。导致LPTask无法             运行,自然无法释放二进制信号量,于是HPTask无法运行。

总结:
\bullet LPTask先持有二进制信号量
\bullet 但是MPTask抢占LPTask,LPTask一直无法运行也就无法释放信号量
\bullet 导致HPTask任务无法运行
\bullet 优先级最高的HPTask竟然一直无法运行!

程序运行的时序图如下:


四、优先级继承 

1.概念

上一个代码的问题在于,LPTask低优先级任务获得了锁,但是它优先级太低而无法运行。

如果能提升LPTask任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?

优先级继承:
        \bullet 假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁
        \bullet 任务B说:你既然持有宝剑,又不给我,那就继承我的愿望吧
        ​​​​​​​\bullet 于是任务A就继承了任务B的优先级
        ​​​​​​​\bullet 这就叫:优先级继承
        ​​​​​​​\bullet 等任务A释放互斥锁时,它就恢复为原来的优先级
        \bullet 互斥锁内部就实现了优先级的提升、恢复

2.代码示例

基于上一个代码,我们只需要做一个简单的修改

int main( void )
{prvSetupHardware();/* 创建互斥量 *///xLock = xSemaphoreCreateBinary( );xLock = xSemaphoreCreateMutex();

创建一个互斥量即可,运行时序图如下图所示:

​​​​​​​\bullet A:HPTask执行 xSemaphoreTake(xLock, portMAX_DELAY); ,它的优先级被LPTask继承
​​​​​​​\bullet B:LPTask抢占MPTask,运行
​​​​​​​\bullet C:LPTask执行 xSemaphoreGive(xLock); ,它的优先级恢复为原来值
​​​​​​​\bullet D:HPTask得到互斥锁,开始运行
​​​​​​​\bullet 互斥锁的"优先级继承",可以减小"优先级反转"的影响 


五、递归锁

1.死锁的概念

在我之前的博客讲互斥锁的时候也讲过什么情况下会造成死锁,例如,线程 A 持有锁 L1 并请求锁 L2,而线程 B 持有锁 L2 并请求锁 L1,这种情况可能导致死锁。所以我们编写程序时要注意,不要写成死锁的情况,等以后写一些大型的代码,需要同时操控几个互斥量时,可能会犯这种错误。

假设有2个互斥量M1、M2,2个任务A、B:
        ​​​​​​​​​​​​​​\bullet A获得了互斥量M1
        ​​​​​​​​​​​​​​\bullet B获得了互斥量M2
        ​​​​​​​​​​​​​​\bullet A还要获得互斥量M2才能运行,结果A阻塞
        ​​​​​​​​​​​​​​\bullet B还要获得互斥量M1才能运行,结果B阻塞
        ​​​​​​​​​​​​​​\bullet A、B都阻塞,再无法释放它们持有的互斥量
        ​​​​​​​\bullet 死锁发生!

2.自我死锁

假设这样的场景:
        ​​​​​​​\bullet 任务A获得了互斥锁M
        ​​​​​​​​​​​​​​\bullet 它调用一个库函数
        ​​​​​​​​​​​​​​\bullet 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
        ​​​​​​​​​​​​​​\bullet 死锁发生!
一个任务申请锁后调用其他函数时申请同一个锁,导致要释放锁的任务阻塞,从而无法释放锁。

3.函数

怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:
        ​​​​​​​​​​​​​​\bullet 任务A获得递归锁M后,它还可以多次去获得这个锁
        ​​​​​​​​​​​​​​\bullet "take"了N次,要"give"N次,这个锁才会被释放

递归锁的函数一般互斥量的函数名不一样,参数类型一样,列表如下:

函数原型如下:

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

注意:要使用递归锁,需要定义配置项configUSE_RECURSIVE_MUTEXES!!!

 4.递归锁代码示例

递归锁实现了:谁上锁就由谁解锁。

main函数里创建了2个任务
        ​​​​​​​​​​​​​​\bullet 任务1:高优先级,一开始就获得递归锁,然后故意等待很长时间,让任务2运行
        ​​​​​​​​​​​​​​\bullet 任务2:低优先级,看看能否操作别人持有的锁
main函数代码如下:

/* 递归锁句柄 */
SemaphoreHandle_t xMutex;
int main( void )
{prvSetupHardware();/* 创建递归锁 */xMutex = xSemaphoreCreateRecursiveMutex( );if( xMutex != NULL ){/* 创建2个任务: 一个上锁, 另一个自己监守自盗(看看能否开别人的锁自己用)*/xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );/* 启动调度器 */vTaskStartScheduler();}else{/* 无法创建递归锁 */}/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}

两个任务经过精细设计,代码和运行流程如下图所示:
        ​​​​​​​​​​​​​​\bullet A:任务1优先级最高,先运行,获得递归锁
        ​​​​​​​​​​​​​​\bullet B:任务1阻塞,让任务2得以运行
        ​​​​​​​​​​​​​​\bullet C:任务2运行,看看能否获得别人持有的递归锁:不能
        ​​​​​​​​​​​​​​\bullet D:任务2故意执行"give"操作,看看能否释放别人持有的递归锁:不能
        ​​​​​​​​​​​​​​\bullet E:任务2等待递归锁
        ​​​​​​​​​​​​​​\bullet F:任务1阻塞时间到后继续运行,使用循环多次获得、释放递归锁
        ​​​​​​​​​​​​​​\bullet 递归锁在代码上实现了:谁持有递归锁,必须由谁释放

程序运行结果如下图所示:

相关文章:

freeRTOS互斥量(mutex)

目录 前言 一、互斥量概述 二、互斥量函数 1.创建 2.其他函数 三、优先级反转示例 1.概念 2.代码示例 四、优先级继承 1.概念 2.代码示例 五、递归锁 1.死锁的概念 2.自我死锁 3.函数 4.递归锁代码示例 前言 在之前的信号量中,我们想要实现互斥的…...

基于GeoTools使用JavaFx进行矢量数据可视化实战

目录 前言 一、JavaFx展示原理说明 二、GeoTools的Maven依赖问题 三、引入Geotools相关的资源包 四、创建JavaFx的Canvas实例 五、JavaFx的Scene和Node的绑定 六、总结 前言 众所周知,JavaFx是Java继Swing之后的又一款用于桌面应用的开发利器。当然&#xff0…...

zabbix的setup无法进入第二步

注意-部署时,报错要看的日志不止一个,php日志的报错也要看的,nginx接收到请求后是转发到php-fpm的 [rootweb01-84-41 ~]# chmod -R 777 /var/lib/php/session chmod: 无法访问"/var/lib/php/session": 没有那个文件或目录 [rootweb…...

代码随想录算法训练营第四十六天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

一、115. 不同的子序列 题目链接:115. 不同的子序列 - 力扣(LeetCode) 文章讲解:代码随想录 (programmercarl.com)——115. 不同的子序列 视频讲解:动态规划之子序列,为了编辑距离做铺垫 | LeetCode&#x…...

宝塔安装nginx失败报错“检测到系统组件wget不存在,无法继续安装”

宝塔安装nginx失败报错“检测到系统组件wget不存在,无法继续安装” 问题描述解决方案 问题描述 在宝塔中安装lnmp环境时,安装nginx失败报错:检测到系统组件wget不存在,无法继续安装 如下图所示 通过检查发现系统是已经安装了wge…...

C++之运算符重载系列深入学习:从入门到精通!

为什么需要对运算符进行重载 C预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其…...

国赛分析。。。。

山东 6散落2两元素 浙江 8散落两元素 安徽不公布 4散落2元素 120s 华南 8散落两元素 西部 8散落两元素 华北 8 2.。。。 华东 东北 路边6张两元素...

无缝融入,即刻智能[一]:Dify-LLM大模型平台,零编码集成嵌入第三方系统,42K+星标见证专属智能方案

无缝融入,即刻智能[一]:Dify-LLM大模型平台,零编码集成嵌入第三方系统,42K+星标见证专属智能方案 1.Dify 简介 1.1 功能情况 Dify,一款引领未来的开源大语言模型(LLM)应用开发平台,革新性地融合了后端即服务(Backend as a Service,BaaS)与LLMOps的精髓,为开发者铺…...

PLSQL导入导出ORACLE数据提示失败问题修改PLSQL配置

oracle中plsql导入提示无法导入问题 1.首先看下是否环境变量已经配置(具体配置看下面环境变量配置) 2.plsql数据导入中tools-->Preferences中配置如下框中的内容 3.设置 tnsnames.ora文件中看下是否设置有问题 4.PLSQL乱码问题 NLS_LANG SIMPLIFIED CHINESE_CHINA.ZHS16…...

从Shift+F6到雪花算法:IDEA开发中的那些坑与解法

在日常开发中,提升代码质量和开发效率是每个开发者追求的目标。作为 Java 开发者常用的集成开发环境,IntelliJ IDEA 提供了丰富的功能与快捷键,帮助我们更快速、更高效地完成日常工作。然而,即便是如此强大的工具,也会…...

Linux知识点总结

学习目标 常见的快捷键 Linux 是一个开源的类 Unix 操作系统,广泛应用于服务器、桌面和嵌入式系统。以下是一些重要的 Linux 知识点总结: 基础概念 文件系统:Linux 使用层次化的文件系统结构,根目录为 /,其他目录如 …...

Gradio 快速开发网页应用

Gradio 是一个开源的 Python 框架,可以快速开发页面,Gradio 主要用于 AI 模型 Demo 的开发,通过几行代码可以快速生成一个 Web Demo,由于 AI 算法工程师使用的都是 Python 语言,使用 Python 开发 Demo 会相对简单&…...

spring使用validation参数及全局异常检测

1.validation参数验证工具 1.1.validation-api技术链 validation-api是一个Java的数据校验规范,它定义了一套用于校验Java Bean的API。它是JSR 303规范的一部分,也被称为Bean Validation。validation-api提供了一系列的注解,用于在Java类的…...

学习笔记 韩顺平 零基础30天学会Java(2024.8.8)

P492 第三代日期使用 P493 第三代日期方法 P495 String翻转 作业代码见chapter12homework 对于需要异常处理的情况,可以通过这种想法得到: P495 注册处理题 P496 字符串统计 P497 String内存布局测试题 P498 常见类阶段梳理 P499 集合介绍 集合的使用并不…...

45.跳跃游戏

:双层for。复杂度n*n n class Solution {public int jump(int[] nums) {// 找到所有的条约方法,返回其中的最小次数// 从后向前,依次记录到最后的次数int n nums.length;if(n 1) return 0;// int[] temp new int[n];// temp[n-1] 0;fo…...

Golang | Leetcode Golang题解之第328题奇偶链表

题目: 题解: func oddEvenList(head *ListNode) *ListNode {if head nil {return head}evenHead : head.Nextodd : headeven : evenHeadfor even ! nil && even.Next ! nil {odd.Next even.Nextodd odd.Nexteven.Next odd.Nexteven even.N…...

【ARM】CMSIS 软件标准接口

目录 CMSIS:Cortex Microcontroller Software Interface Standard1. 概述2. CMSIS-Core2.1 概述2.2 关键组件2.3 示例代码2.4 详细解释 3. CMSIS-DSP3.1 概述3.2 关键组件3.3 示例代码3.4 详细解释 4. CMSIS-RTOS4.1 概述4.2 关键组件4.3 示例代码4.4 详细解释 5. C…...

Qt 小功能:加载等待动画——转圈圈

加载等待动画实现——转圈圈 效果图:(看封面最好) 关键要点 流畅的动画: 使用 QTimer 每 50 毫秒更新一次动画,确保动画流畅。 视觉效果: 使用 QPainter 的平滑像素转换和抗锯齿选项,提高动画…...

【Linux进程篇】进程终章:POSIX信号量线程池线程安全的单例模式自旋锁读者写者问题

W...Y的主页 😊 代码仓库分享 💕 前言:在之前的进程间通信时我们就讲到过信号量,他的本质就是一个计数器,用来描述临界资源的一个计数器。我们当时使用电影院的例子来说明信号量。电影院的座位被我们称为临界资源&a…...

MathType7.5破解版下载安装激活图文详细教程(附激活秘钥)

🌟 引言:揭秘MathType,数学编辑的瑞士军刀! 嘿,各位小伙伴,今天我要给你们安利一个我超级喜欢的数学神器——MathType!如果你跟我一样,在处理数学公式时常常感到头疼,那你…...

2-62 基于MATLAB gui 编制短波通信系统

基于MATLAB gui 编制短波通信系统,录制一段语音信号,分别通过AM SSB DSB 等调制信号,加入噪声,然后解调出来,可比较各种调制解调方式的优劣。程序已调通,可直接运行。 2-62 matlab gui - 小红书 (xiaohongs…...

windows C++-C++/WinRT 中创建组件和事件(下)

跨 ABI 的简单信号 如果无需连同事件传递任何形参或实参,则可以定义自己的简单 Windows 运行时委托类型。 以下示例展示 Thermometer 运行时类的更简易版本。 它声明名为 SignalDelegate 的委托类型,然后使用该类型来引发信号类型事件,而不是…...

C++初学者指南-5.标准库(第二部分)--二叉堆操作

C初学者指南-5.标准库(第二部分)–二叉堆操作 文章目录 C初学者指南-5.标准库(第二部分)--二叉堆操作背景什么是“堆”二叉最大堆二叉树的表示 堆操作C标准库中的堆初始化堆收缩堆增长堆 辅助操作sort_heap (Heap → Sorted Array)is_heapis_heap_until 相关内容 不熟悉 C 的标…...

在Ubuntu 16.04上安装Git的方法

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 简介 在现代软件开发中,一个不可或缺的工具是某种版本控制系统。版本控制系统允许您在源代码级别跟踪软件。您可以跟踪更改…...

redis内存淘汰策略-------Reservoir Sampling(水库采样)

文章目录 过期删除策略和内存淘汰策略内存淘汰策略evictionPoolEntryevictionPoolPopulate Reservoir SamplingdictGetRandomKeydictGetSomeKeysReservoir Samplingchatgpt对Reservoir Sampling的介绍 过期删除策略和内存淘汰策略 详细介绍请参考博客“redis过期删除策略和内存…...

C++《类和对象》(上)

在之前的C入门基础知识中我们了解了C的发展过程已经重要性,还初步了解了C中一些相比C语言特有的知识点,例如命名空间、缺少参数、函数重载、引用等,接下来在本篇中我们将开始C整个体系中非常重要的一个知识章节——类和对象,类和对…...

LLM大语言模型算法特训

百度 LLM(Large Language Model)大语言模型算法特训是一个深度学习领域的高级培训项目,专门设计用于训练和优化大规模语言模型的开发者和研究人员。本文将详细探讨LLM算法的基本原理、训练技术、应用领域以及参与者可以预期的学习收获和挑战。…...

Docker相关笔记

Docker笔记 1. Dockerfile编译构建docker Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有指令。 Dockerfile 常用的有如下关键字: FROM:指定基础镜像,后续定制操作都是基于这个基础镜像,比如: …...

前端技术day01-HTML入门

一、前端介绍 技术描述HTML用于构建网站的基础结构的CSS用于美化页面的,作用和化妆或者整容作用一样JS实现网页和用户的交互Vue主要用于将数据填充到html页面上的Element主要提供了一些非常美观的组件 二、工具软件 VsCode 在前端领域,有一个公认好用…...

Multisim 用LM358 运放模拟线性稳压器 - 运放输出饱和 - 前馈电容

就是拿运放搭一个可调的LDO 稳压器,类似下面这个功能框图里的感觉。本来应该非常简单,没什么好说的,没想到遇到了两个问题。 原理 - 理想运放 我用PNP 三极管Q2 作为输出,运放输出电压升高时,流过PNP 三极管BE 的电流变…...