十六、互斥量

        互斥量的目的就是为了实现互斥访问。

1、概述

(1)举例说明:

        怎么独享厕所?自己开门上锁,完事了自己开锁。
        你当然可以进去后,让别人帮你把门:但是,命运就掌握在别人手上了。                           

(2)使用队列、信号量,都可以实现互斥访问,以信号量为例:

  • 信号量初始值为1。
  • 任务A想上厕所,"take"信号量成功,它进入厕所。
  • 任务B也想上厕所,"take"信号量不成功,等待。
  • 任务A用完厕所,"give"信号量;轮到任务B使用。

(3)使用信号量实现互斥访问,这需要有2个前提:

  • 任务B很老实,不撬门(一开始不"give"信号量)。
  • 没有坏人:别的任务不会"give"信号量。

(4)综上,可以看到,使用信号量确实也可以实现互斥访问,但是不完美。使用互斥量可以解决这个问题,互斥量的名字取得很好:

  • 量:值为0、1。
  • 互斥:用来实现互斥访问。

(5)互斥量的核心在于:谁上锁,就只能由谁开锁。很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:

  • 即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。
  • 谁上锁、谁释放:只是约定。

2、互斥量的使用场合

        在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信息混杂在一起。这种需要使用互斥访问的场合如下:

(1)访问外设:刚举的串口例子。

(2)读、修改、写操作导致的问题。                                                                                           

        对于同一个变量,  比如 int a ,如果有两个任务同时写它就有可能导致问题。对于变量的修改,C代码只有一条语句,比如: a=a+8; ,它的内部实现分为3步:读出原值、修改、写入。

        我们想让任务A、B都执行add_a函数,a的最终结果是 1+8+8=17 。假设任务A运行完代码①,在执行代码②之前被任务B抢占了:现在任务A的R0等于1。任务B执行完add_a函数,a等于9。任务A继续运行,在代码②处R0仍然是被抢占前的数值1,执行完②③的代码,a等于9,这跟预期的17不符合。

(3)对变量的非原子化访问

        修改变量、设置结构体、在16位的机器上写32位的变量,这些操作都是非原子的。也就是它们的操作过程都可能被打断,如果被打断的过程有其他任务来操作这些变量,就可能导致冲突。

(4)函数重入

        “可重入的函数”是指:多个任务同时调用它、任务和中断同时调用它,函数的运行也是安全的。可重入的函数也被称为"线程安全"(thread safe)。每个任务都维持自己的栈、自己的CPU寄存器,如果一个函数只使用局部变量,那么它就是线程安全的。函数中一旦使用了全局变量、静态变量、其他外设,它就不是"可重入的",如果该函数正在被调用,就必须阻止其他任务、中断再次调用它。

(5)上述问题的解决方法是:任务A访问这些全局变量、函数代码时,独占它,就是上个锁。这些全局变量、函数代码必须被独占地使用,它们被称为临界资源。

(6)互斥量也被称为互斥锁,使用过程如下:

  • 互斥量初始值为1
  • 任务A想访问临界资源,先获得并占有互斥量,然后开始访问
  • 任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
  • 任务A使用完毕,释放互斥量;任务B被唤醒、得到并占有互斥量,然后开始访问临界资源
  • 任务B使用完毕,释放互斥量

(7)正常来说:在任务A占有互斥量的过程中,任务B、任务C等等,都无法释放互斥量。但是FreeRTOS未实现这点:任务A占有互斥量的情况下,任务B也可释放互斥量。

3、互斥量函数

3.1、创建

(1)互斥量是一种特殊的二进制信号量。

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

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

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

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

#define configUSE_MUTEXES 1

3.2、其他

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

(2)函数原型

/** 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
);

4、互斥量与二进制信号量相比

(1)互斥量与二进制信号量相比,能解决优先级反转的问题和递归上锁/解锁的问题。

(2)互斥量、二进制信号量的异同:

  • 互斥量有优先级继承功能
  • Give/Take函数完全一样
  • 二进制信号量的初始值是0
  • 互斥量的初始值是1

4.1、优先级反转

(1)假设存在A、B、C三个任务,一个二进制信号量;A的优先级为1,B的优先级为2、C的优先级为3;A和C任务的执行都需要信号量。

(2)一开始,B和C任务处在阻塞状态,A任务先执行并获得信号量,这时B任务被唤醒,抢占CPU资源。

(3)B任务开始执行,等到C任务被唤醒。

(4)C任务执行,获取信号量失败,进入阻塞态,B任务继续执行。

(5)这时由于等不到A释放信号量,高优先级的任务C反而得不到执行,低优先级的任务B在执行。这便是优先级反转。

(6)解决方法法是通过互斥量的优先级继承。把二进制信号量改为互斥量,任务C在获取互斥量失败时,把已获得互斥量的任务A的优先级提高为3。任务A执行完释放互斥量后,在恢复优先级为1。

(7)互斥锁内部就实现了优先级的提升、恢复,互斥锁的优先级继承,可以减少“优先级反转”影响。

4.2、递归上锁/解锁

(1)假设存在任务A,一个互斥量,一个函数Func;任务A和函数Func都需要使用互斥量。

(2)任务A执行并获得互斥量,然后调用函数Func,这是函数Func又去获取互斥量,便会失败。

(3)解决方法是采用递归互斥量

5、递归锁

5.1、死锁的概念

(1)日常生活的死锁:我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!

(2)假设有2个互斥量M1、M2,2个任务A、B:

  • A获得了互斥量M1
  • B获得了互斥量M2
  • A还要获得互斥量M2才能运行,结果A阻塞
  • B还要获得互斥量M1才能运行,结果B阻塞
  • A、B都阻塞,再无法释放它们持有的互斥量
  • 死锁发生!

5.2、自我死锁

(1)假设这样的场景:

  • 任务A获得了互斥锁M
  • 它调用一个库函数
  • 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
  • 死锁发生!

5.3、函数

(1)怎么解决死锁这类问题呢?可以使用递归锁(Recursive Mutexes),它的特性如下:

  • 任务A获得递归锁M后,它还可以多次去获得这个锁
  • "take"了N次,要"give"N次,这个锁才会被释放

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

递归锁一般互斥量
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
获得xSemaphoreTakeRecursivexSemaphoreTake
释放xSemaphoreGiveRecursivexSemaphoreGive

(3)函数原型如下:

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

5.4、注意点

(1)递归锁在代码上实现了:

  • 谁持有递归锁,必须由谁释放。
  • 递归上锁/解锁。

(2)使用递归锁,需要定义配置项:

        FreeRTOS为了减少程序的体积,使用某些功能,必须先配置。

#define    configUSE_RECURSIVE_MUTEXES    1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/169580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3.3.2 深度理解BITS_TO_LONGS函数

文章目录 定义物理意义内核应用应用一:直接使用应用二 间接使用测试驱动层示例应用层示例参考定义 BITS_TO_LONGS定义在include/linux/bitops.h中,具体如下 #define BITS_PER_BYTE 8 #define

Task.Run为什么会存在内存泄漏的风险?

由于值类型是拷贝方式的赋值,捕获的本地变量和类成员是指向各自的值,对本地变量的捕获不会影响到整个类。但如果把类中的值类型改为引用类型,那这两者最终指向的是同一个对象值,这是否意味着使用本地变量还是无法避免内存泄漏&…

【java】-D参数使用

在开发过程中我们使用开源工具经常会用到在启动命令时候加入一个 -Dxxx 类型的参数。到底-Dxxx是干什么用的了。 官方文档 地址:文档地址 java命令使用 下面是来源于官方文档: java [options] classname [args] java [options] -jar filename [args…

FFmpeg零基础学习(二)——视频文件信息获取

目录 前言正文一、获取宽高信息1、核心代码2、AVFormatContext3、avformat_alloc_context4、avformat_open_input5、avformat_find_stream_info6、av_dump_format7、av_find_best_stream End、遇到的问题1、Qt Debug模式avformat_alloc_context 无法分配对象,而Rele…

2023年汉字小达人市级比赛在线模拟题的使用顺序、建议和常见问题

今天是2023年11月25日,星期六,上午举办了2023年第八届上海小学生古诗文大会的复选活动(复赛),结束了复选活动,很多学霸孩子们马上就开始投入到第十届汉字小达人的市级活动(市级比赛)…

PCL 计算两点云之间的最小距离

目录 一、 算法原理二、 代码实现三、 结果展示四、 相关链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、 算法原理 pcl::registration::CorrespondenceEstimation是确定目标和查询点集(或特征)之间对应关…

【Web】CmsEasy 漏洞复现

访问主页 到处点一点没啥发现 扫目录 访问/admin 账号密码都是admin admin,不知道为什么,这里就先当作是默认吧 (其实都是信息检索,能在网上搜到就行hhh) 登录成功 看到左边列表有模板,心里大概有数了哈 进行一波历…

Android虚拟化

一、开源项目 开源的项目有一些,比如完全虚拟化的: twoyi 两仪由两部分组成:两仪 App,它实际上是一个 UI 渲染引擎,两仪内部运行的 ROM。 但是看telegram和github,这个app没有完整开源,并且最近…

二维列表如何利用set()进行去重

一维列表去重 输入&#xff1a; a[1,2,3,4,5,6,6] print(a) print(type(a)) bset(a) print(b) 输出&#xff1a; [1, 2, 3, 4, 5, 6, 6] <class list> {1, 2, 3, 4, 5, 6} 代码显示一维列表去重是没有问题的 二维列表去重 输入&#xff1a; a[[1,2,3,],[4,5,6],[…

StarRocks Evolution:One Data,All Analytics

在 11 月 17 日举行的 StarRocks Summit 2023上&#xff0c;StarRocks TSC Member、镜舟科技 CTO 张友东详细介绍了 StarRocks 社区的发展情况&#xff0c;并全面解析了 StarRocks 的核心技术与未来规划&#xff1b;我们特意将他的精彩演讲整理出来&#xff0c;以帮助大家更深入…

Typescript的数据类型

Typescript Typescript 是 Javascript 的一个超集。 Typescript 在原有 js 的基础之上又添加了编译期的类型检查的功能。意味着如 果在ts 的环境下开发时&#xff0c;会对变量的数据类型进行较为严格的验证&#xff0c;防止程序员写出可能出问题的代码&#xff0c;规范编程习惯…

docker环境安装

环境 主机环境 1. 宿主机环境 ubuntu-22.04.3-live-server-amd64 &#xff0c;下载地址&#xff1a; https://mirrors.aliyun.com/ubuntu-releases/22.04.3/ubuntu-22.04.3-live-server-amd64.iso 2. apt 包管理器&#xff0c;镜像源修改 : 将 http://cn.archive.ubunt…

间接法加窗分析信号的功率谱

本篇文章是博主在通信等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对通信等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在 通信领域笔记&#xff…

【算法萌新闯力扣】:卡牌分组

力扣热题&#xff1a;卡牌分组 一、开篇 今天是备战蓝桥杯的第22天。这道题触及到我好几个知识盲区&#xff0c;以前欠下的债这道题一并补齐&#xff0c;哈希表的遍历、最大公约数与最小公倍数&#xff0c;如果你还没掌握&#xff0c;这道题练起来&#xff01; 二、题目链接:…

Redis key 过期监听实现

1.技术背景&#xff0c;想知道 redis 设置了TTL时间的key 过期&#xff0c;且有后续的业务处理的场景可以使用。 bug点&#xff1a; 使用redis 缓存失效监听会有一定的延迟&#xff0c; 过期事件是在redis服务器删除键的时候生成的&#xff0c;而不是在理论上生存时间到达0值得…

python每日一题——6三数之和

题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 …

【数据结构】树与二叉树(廿三):树和森林的遍历——层次遍历(LevelOrder)

文章目录 5.3.1 树的存储结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法5.3.3 树和森林的遍历1. 先根遍历&#xff08;递归、非递归&#xff09;2. 后根遍历&#xff08;递归、非递归&#xff09;3. 森林的遍历4. 层次遍历a. 算法LevelOrderb. 算法解读c. 时间复杂度d.代码…

STM32 启动文件分析

STM32 启动文件分析 基于STM32F103VET6芯片的 startup_stm32f10x_hd.s 启动文件分析 设置栈&#xff0c;将栈的大小Stack_Size设置为0x00004900&#xff08;18688/102418KB&#xff09;&#xff0c;即局部变量不能大于18KB。&#xff08;EQU等值指令&#xff0c;将0x0000490…

Arduio开发STM32所面临的风险

据说micro_ros用到了arduino,然后用arduino搞stm32需要用到这个Arduino STM32的东西&#xff0c;然后这里申明了&#xff1a;这些代码没有经过严格测试&#xff0c;如果是向心脏起搏器&#xff0c;自动驾驶这样要求严格的的情况下&#xff0c;这个东西不能保证100%不发生问题&a…

尺度为什么是sigma?

我们先看中值滤波和均值滤波。 以前&#xff0c;我认为是一样的&#xff0c;没有区分过。 他们说&#xff0c;均值滤波有使图像模糊的效果。 中值滤波有使图像去椒盐的效果。为什么不同呢&#xff1f;试了一下&#xff0c;果然不同&#xff0c;然后追踪了一下定义。 12345&…