十六、互斥量

news2024/12/23 5:10:38

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

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.coloradmin.cn/o/1252221.html

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

相关文章

分享一篇很久以前的文档-VMware Vsphere菜鸟篇

PS:由于内容是很久以前做的记录,在整理过程中发现了一些问题,简单修改后分享给大家。首先ESXI节点和win7均运行在VMware Workstation上面,属于是最底层,而新创建的CentOS则是嵌套后创建的操作系统,这点希望…

得物面试:MySQL为何需要4M来双写?为什么redo不双写?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格,遇到很多很重要的面试题: MySQL为何需要4M来双写?为什么redo不双写&…

count=0语句的位置

简洁一点的代码: 像count0这种语句要注意放好位置,尤其是在循环里。

2017年3月24日 Go生态洞察:HTTP/2服务器推送技术深度解析

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

操作无法完成错误0x0000709的解决办法,解决0x0000709错误

操作无法完成错误0x0000709是一种常见的Windows错误。这篇文章将带大家了解错误的原因,并提供一些解决该问题的方法,希望能够帮助大家解决0x0000709错误问题。 操作系统错误是我们在使用电脑时经常遇到的问题之一。其中之一就是操作无法完成错误0x000070…

ethernet II 的故事

以太帧有很多种类型。不同类型的帧具有不同的格式和MTU值。但在同种物理媒体上都可同时存在。 以太网第二版或者称之为Ethernet II 帧,DIX帧,是最常见的帧类型。并通常直接被IP协议使用。 格式 当数据帧到达网卡时,网卡要先去掉前导码&#…

安卓系统修图软件(一)

平时我们会不时在朋友圈发自己的自拍照,或者是风景图等,许多小伙伴们此时会对照片进行一定的修理,比如添加滤镜等操作。在电脑上用ps修图比较繁琐,日常中大可不必用这把宰牛刀;而手机自带的编辑器,或者是QQ…

位图及有关海量数据处理

bitset 1.给40亿个不重复的无符号整数,没排过序,给一个无符号整数,如何快速判断一个数是否在这40亿个中 ①.如果用排序加二分查找,40亿个数需要16g内存,内存开不出这么大连续空间 ②.每个值映射一个比特位,…

【Linux】进程间通信——system V共享内存、共享内存的概念、共享内存函数、system V消息队列、信号量

文章目录 进程间通信1.system V共享内存1.1共享内存原理1.2共享内存数据结构1.3共享内存函数 2.system V消息队列2.1消息队列原理 3.system V信号量3.1信号量原理3.2进程互斥 4.共享内存的使用示例 进程间通信 1.system V共享内存 1.1共享内存原理 共享内存区是最快的IPC形式…

图形编辑器开发:缩放和旋转控制点

大家好,我是前端西瓜哥。好久没写图形编辑器开发的文章了。 今天来讲讲控制点。它是图形编辑器的不可缺少的基础功能。 控制点是吸附在图形上的一些小矩形和圆形点击区域,在控制点上拖拽鼠标,能够实时对被选中进行属性的更新。 比如使用旋…

python_接口自动化测试框架

📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…

基于springBoot+Vue的停车管理系统

开发环境 IDEA JDK1.8 MySQL8.0Node 系统简介 本项目为前后端分离项目,前端使用vue,后端使用SpringBoot开发,主要的功能有用户管理,停车场管理,充值收费,用户可以注册登录系统,自主充值和预…

论文阅读——Prophet(cvpr2023)

一、Framework 这个模型分为两阶段:一是答案启发生成阶段(answer heuristics generation stage),即在一个基于知识的VQA数据集上训练一个普通的VQA模型,产生两种类型的答案启发,答案候选列表和答案例子&am…

python安装redis库

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

关于电路的一些杂项内容补充总结

过载和过流 什么是过载?什么是过流?专业电力知识快来与网上国网交流~ - 知乎 磁珠 最全讲解磁珠_磁珠的用法_大话硬件的博客-CSDN博客 重点 磁珠主要是用来抑制信号线、电源线上的高频的噪声和尖峰干扰。 谐振 什么是谐振?什么是LC谐振电路&a…

2024北京理工大学计算机考研分析

24计算机考研|上岸指南 北京理工大学 计算机学院始建于1958年,是全国最早设立计算机专业的高校之一。2018年4月,计算机学院、软件学院、网络科学与技术研究院合并成立新的计算机学院。学院累计为国家培养各类人才15000余名。计算机科学学科ESI排名进入全…

LeetCode.203移除链表元素(原链表操作、虚拟头结点)

LeetCode.203移除链表元素 1.问题描述2.解题思路3.代码 1.问题描述 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head [1,2,6,3,4,5,6], val …

WGCLOUD 中文繁体版本 下载

wgcloud 繁体版下载 下載繁體版安裝包 - WGCLOUD

AJAX技术-04-- 跨域说明

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1 同源策略同源策略介绍规定要求 请求协议://域名:端口号 关于同源策略练习关于同源策略总结 2.JSONPJSONP原理说明关于JSONP优化 3.CORS介绍介绍不允许跨域说明跨域…