FreeRTOS 快速入门(六)之互斥量

news2024/9/26 3:25:49

目录

  • 一、互斥量
    • 1、基本概念
    • 2、运作机制
    • 3、死锁现象
    • 4、递归互斥量
  • 二、优先级反转和优先级继承问题
    • 1、优先级反转问题
    • 2、优先级继承问题
  • 三、互斥量函数
    • 1、互斥量
      • 1、创建
    • 2、获取互斥量
    • 3、释放互斥量
    • 4、删除互斥量


一、互斥量

1、基本概念

互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。 任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。

如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于任务与任务、任务与中断的同步,但是互斥量更多的是用于保护资源的互锁。

互斥锁可以被视为一个与正在共享的资源相关联的令牌,对于合法访问资源的任务,它必须首先成功 “获取” 令牌,成为资源的持有者,当持有者完成对资源的访问之后,其需要 ”归还” 令牌,只有 “归还” 令牌之后,该令牌才可以再次被其他任务所 “获取” ,这样保证了互斥的对共享资源的访问,上述机制如下图所示:

2、运作机制

用互斥量处理不同任务对临界资源的同步访问时,任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。

3、死锁现象

“死锁” 是使用互斥锁进行互斥的另一个潜在陷阱,当两个任务因为都在等待对方占用的资源而无法继续进行时,就会发生死锁,考虑如下所述的情况:

  • 任务 A 执行并成功获取互斥量 X
  • 任务 A 被任务 B 抢占
  • 任务 B 在尝试获取互斥量 X 之前成功获取互斥量 Y,但互斥量 X 由任务 A 持有,因此对任务 B 不可用,任务 B 选择进入阻塞状态等待互斥量 X 被释放
  • 任务 A 继续执行,它尝试获取互斥量 Y,但互斥量 Y 由任务 B 持有,所以对于任务 A 来说是不可用的,任务 A 选择进入阻塞状态等待待释放的互斥量 Y

经过上述的这样一个过程,读者可以发现任务 A 在等待任务 B 释放互斥量 Y ,而任务 B 在等待任务 A 释放互斥量 X ,两个任务都在阻塞状态无法执行,从而导致 ”死锁“ 现象的发生,与优先级翻转一样,避免 “死锁” 的最佳方法是在设计时考虑其潜在影响,并设计系统以确保不会发生死锁。

4、递归互斥量

任务也有可能与自身发生死锁,如果任务尝试多次获取相同的互斥体而不首先返回互斥体,就会发生这种情况,考虑以下设想:

  • 任务成功获取互斥锁
  • 在持有互斥体的同时,任务调用库函数
  • 库函数的实现尝试获取相同的互斥锁,并进入阻塞状态等待互斥锁变得可用

在此场景结束时,任务处于阻塞状态以等待互斥体返回,但任务已经是互斥体持有者。 由于任务处于阻塞状态等待自身,因此发生了死锁。

通过使用递归互斥体代替标准互斥体可以避免这种类型的死锁,同一任务可以多次 “获取” 递归互斥锁,并且只有在每次 “获取” 递归互斥锁之后都调用一次 “释放” 递归互斥锁,才会返回该互斥锁

因此递归互斥量可以视为特殊的互斥量,一个互斥量被一个任务获取之后就不能再次获取,其他任务想要获取该互斥量必须等待这个任务释放该互斥连,但是递归互斥量可以被一个任务重复获取多次,当然每次获取必须与一次释放配对使用。

注意不管是互斥量,还是递归互斥量均存在优先级继承机制,但是由于 ISR 并不是任务,因此互斥量和递归互斥量不能在中断中使用

二、优先级反转和优先级继承问题

1、优先级反转问题

在上一讲 FreeRTOS 快速入门(五)之信号量 中提到过使用二值信号量用于进程间同步时可能会出现优先级反转的问题,什么是“优先级反转”问题呢?考虑如下所述的任务运行过程:

  • 在 t1 时刻,低优先级的任务 TaskLP 切入运行状态,并且获取到了一个二值信号量
  • 在 t2 时刻,高优先级的任务 TaskHP 请求获取二值信号量,但是由于 TaskLP 还未释放该二值信号量,所以在 t3 时刻,任务 TaskHP 进入阻塞状态等待二值信号量被释放
  • 在 t4 时刻,中等优先级的任务 TaskMP 进入就绪状态,由于不需要获取二值信号量,因此抢占低优先级任务任务 TaskLP 切入运行状态
  • 在 t5 时刻,任务 TaskMP 运行结束,任务 TaskLP 再次切入运行状态
  • 在 t6 时刻,任务 TaskLP 运行结束,释放二值信号量,此时任务 TaskHP 从等待二值信号量的阻塞状态切入运行状态
  • 在 t7 时刻,任务 TaskHP 运行结束

根据上述流程我们可以发现一个问题,即在 t4 时刻中等优先级的任务 TaskMP 先于高优先级的任务 TaskHP 抢占了处理器,这破坏了 FreeRTOS 基于优先级抢占式执行的原则,我们将这种情况称为优先级反转问题,上述描述的任务运行过程具体时刻流程图如下图所示:


导致这种优先级反转的问题的根本原因在于持有锁的低优先级任务因为优先级低,而得不到执行,得不到执行的话,就无法解锁,无法解锁就导致高优先级的任务获取锁会失败,从而导致高优先级任务一直在阻塞状态。

所以解决方式就是优先级继承,既然低优先级任务无法执行,那我就在高优先级任务进入阻塞之前将低优先级任务的优先级提升至与高优先级一致,这样等高优先级任务进入阻塞之后,低优先级任务就能继承高优先级任务的优先级,这样低优先级任务就能尽快执行(从而解锁,让高优先级能够获取锁)。

2、优先级继承问题

为了解决使用二值信号量可能会出现的优先级翻转问题,对二值信号量做了改进,增加了一种名为 “优先级继承” 的机制,改进后的实例称为了互斥量,注意虽然互斥量可以减缓优先级翻转问题的出现,但是并不能完全杜绝。

下面仍然考虑由上一小小节中提出的任务运行过程的例子,具体流程如下所述,我们可以细心理解其中的不同之处:

  • 在 t1 时刻,低优先级的任务 TaskLP 切入运行状态,并且获取到了一个互斥量
  • 在 t2 时刻,高优先级的任务 TaskHP 请求获取互斥量,但是由于 TaskLP 还未释放该互斥量,所以在 t3 时刻,任务 TaskHP 进入阻塞状态等待互斥量被释放,但是与二值信号量不同的是,此时 FreeRTOS 将任务 TaskLP 的优先级临时提高到与任务 TaskHP 一致的优先级,也即高优先级
  • 在 t4 时刻,中等优先级的任务 TaskMP 进入就绪状态发生任务调度,但是由于任务 TaskLP 此时优先级被提高到了高优先级,因此任务 TaskMP 仍然保持就绪状态等待优先级较高的任务执行完毕
  • 在 t5 时刻,任务 TaskLP 执行完毕释放互斥量,此时任务 TaskHP 抢占处理器切入运行状态,并恢复任务 TaskLP 原来的优先级
  • 在 t6 时刻,任务 TaskHP 执行完毕,此时轮到任务 TaskMP 执行
  • 在 t7 时刻,任务 TaskMP 运行结束

根据互斥量的上述任务流程我们可以发现与二值信号量的不同之处,上述描述的任务运行过程具体时刻流程图如下图所示:

三、互斥量函数

1、互斥量

1、创建

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

/**
  * @brief  动态分配内存创建互斥信号量函数
  * @retval 创建互斥信号量的句柄
  */
SemaphoreHandle_t xSemaphoreCreateMutex(void);
 
/**
  * @brief  静态分配内存创建互斥信号量函数
  * @param  pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态
  * @retval 返回成功创建后的互斥锁的句柄,如果返回NULL则表示内存不足创建失败
  */
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);
 
/**
  * @brief  动态分配内存创建递归互斥信号量函数
  * @retval 创建递归互斥信号量的句柄,如果返回NULL则表示内存不足创建失败
  */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
 
/**
  * @brief  动态分配内存创建二值信号量函数
  * @param  pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态
  */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(
								StaticSemaphore_t pxMutexBuffer);

2、获取互斥量

获取互斥量直接使用获取信号量的函数即可,但对于递归互斥量需要专门的获取函数,具体如下所述:

/**
  * @brief  获取信号量函数
  * @param  xSemaphore:正在获取的信号量的句柄
  * @param  xTicksToWait:等待信号量变为可用的时间
  * @retval 成功获取信号量则返回pdTRUE, xTicksToWait过期,信号量不可用,则返回pdFALSE
  */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
 
/**
  * @brief  获取递归互斥量
  * @param  xMutex:正在获得的互斥锁的句柄
  * @param  xTicksToWait:等待信号量变为可用的时间
  * @retval 成功获取信号量则返回pdTRUE, xTicksToWait过期,信号量不可用,则返回pdFALSE
  */
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex,
								   TickType_t xTicksToWait);

3、释放互斥量

释放互斥量直接使用释放信号量的函数即可,但对于递归互斥量需要专门的释放函数,具体如下所述:

/**
  * @brief  释放信号量函数
  * @param  xSemaphore:要释放的信号量的句柄
  * @retval 成功释放信号量则返回pdTRUE, 若发生错误,则返回pdFALSE
  */
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
 
/**
  * @brief  释放递归互斥量
  * @param  xMutex:正在释放或“给出”的互斥锁的句柄
  * @retval 成功释放递归互斥量后返回pdTRUE
  */
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);

4、删除互斥量

直接使用信号量的删除函数即可,具体如下所述:

/**
  * @brief  获取信号量函数
  * @param  xSemaphore:要删除的信号量的句柄
  * @retval None
  */
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);

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

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

相关文章

Vue.js学习笔记(七)使用sortablejs或el-table-draggable拖拽ElementUI的el-table表格组件

文章目录 前言一、el-table-draggable是什么?二、使用步骤1.安装使用2.sortablejs 总结 前言 记录 el-table-draggable 插件使用方法。 一、el-table-draggable是什么? el-table-draggable的存在就是为了让vue-draggable支持element-ui中的el-table组件…

Lesson 81+82 Roast beef and potatoes

Lesson 8182 Roast beef and potatoes 词汇 bath n. 洗澡,浴缸 搭配:have a bath 泡澡 相关:take a shower 淋浴,冲个澡    shower:花洒,喷头 例句:Bobby总是在傍晚洗澡。    Bobby alw…

基于预训练模型,进行氨基酸序列编码,用于深度学习模型构建

本团队提供生物医学领域专业的AI(机器学习、深度学习)技术支持服务。如果您有需求,请扫描文末二维码关注我们。 在对氨基酸序列数据进行深度学习模型构建时,首先需要将字符形式的序列数据进行编码操作。最简单的当然是One-hot编码…

【Java】/* 双向链式队列 和 循环队列 - 底层实现 */

一、链式队列 1. 使用双向链表实现队列,可以采用尾入,头出 也可以采用 头入、尾出 (LinkedList采用尾入、头出) 2. 下面代码实现的是尾入、头出: package bageight;/*** Created with IntelliJ IDEA.* Description:* User: tangyuxiu* Date: …

[kaggle竞赛] 毒蘑菇的二元预测

毒蘑菇的二元预测 您提供了很多关于不同二元分类任务的资源和链接,看起来这些都是Kaggle竞赛中的参考资料和高分解决方案。为了帮助您更好地利用这些资源,这里是一些关键点的总结: Playground Season 4 Episode 8 主要关注的竞赛: 使用银行…

2024/8/22 英语每日一段

Belgian triathlete Claire Michel ultimately said it was a virus and not bacteria from the water that made her sick after a swim. But Belgium’s Olympic committee said in a statement that it hoped “lessons will be learned” for future Olympics. “We are th…

鸿蒙(API 12 Beta3版)【使用ImageEffect编辑图片】图片开发指导

场景介绍 ImageEffect提供了一系列接口用于图像的编辑。开发者可以通过ImageEffect接口处理不同图像输入类型Pixelmap、NativeWindow、NativeBuffer或Uri,获得滤镜处理效果。 针对ImageEffect,常见的开发场景如下: 通过ImageEffect提供的N…

iOS 18 Beta 7测试版本体验,无新功能,修复已知bug

近日苹果公司发布了iOS 18 beta7版本,版本号22A5346a。那iOS 18beta7版本是否比其他的测试版要更好用呢?以下测试结果仅供果粉参考,一机一况,以个人实际体验为准。 一、日常使用体验 1、App响应非常快,动画过渡时间稍…

【高等代数笔记】线性方程组的解法(三、四、五)

1. 线性方程组的解法 由于这个视频课的分p十分抽象,我还是把一节完整的课学完再发表笔记吧,要不然太零碎了。 接上一篇文章 阶梯形方程组为 { x 1 − x 2 2 x 3 − 1 0 0 \left\{\begin{array}{l} x_{1}-x_{2}2 \\ x_{3}-1 \\ 00 \end{array}\righ…

dll错误修复工具:一键解决系统DLL错误产生的程序问题(新手入门)

dll错误修复工具,主要解决导致Windows系统上程序出错的各种dll相关问题。金舟DirectXDLL一键修复提供了全面的且快速的扫描功能,能够检测出导致程序故障的任何dll错误,并且一键进行修复。 一、什么是dll文件 dll是系统的动态链接库文件&…

6年赚了300亿孙悟空才是真财神!带火文旅、引发装机热潮、搅动A股....这波热度你蹭到了吗?

《黑神话:悟空》上线后一博主连续32小时直播,观看人数超3000万,涨粉近46万!该主播个人收益或高达85万元!游戏里的36个取景地中,山西独占27个。8月20日,小西天景区出售门票比去年同期增长300%。 …

前端面试题-vue框架

1. 聊聊为什么会出现 React、vue 这样的框架,他们的出现解决了什么问题 用户界面越来越复杂,框架采用声明式的写法,将界面的构建和数据的管理分离出来,大大提升开发效率和维护效率。 (1)原生JS实现不太方…

如何在Visio中画精准的圆弧,角度标记,弧度标记(已解决)

1、导入 “绘图工具形状” 打开 Visio--》 然后: 点击 绘制工具形状 之后, 在界面会出现 绘制工具形状的选项 如下 本人 使用圆弧 ,弧线功能,然后两头增加箭头,实现角度标注,如下

【Qt开发】建立自己的Qt基本类、函数库封装 包括图表、多线程、串口等

【Qt开发】建立自己的Qt基本类、函数库 包括图表、多线程、串口等 文章目录 前言QtCharts绘图继承QObject的QThread多线程QSerialPort串口配置、发送、接收回调函数附录:C语言到C的入门知识点(主要适用于C语言精通到Qt的C开发入门)C语言与C的…

2024年企业记账最主流的8大财务软件大对比

企业记账的8大主流财务软件:1.合思;2.用友好会计财务软件;3.浪潮云会计;4.金蝶精斗云财务软件;5.Zoho Books;6.管家婆;7.QuickBooks;8.云账房。 对小企业主来说,采用高效…

Linux | 探究C语言文件接口与Linux系统文件接口的区别与联系 | fopen和open的区别与联系

什么是尘土?从大地之肺发出的一声叹息。 - 《阿多尼斯诗集》(阿多尼斯) 2024.8.23 目录 1、C语言IO接口 示例代码:使用 fopen 和 fclose 读写文件 示例1:通过write写文件 示例2:通过read写文件 C语言的标准流:std…

集合及数据结构第十节(上)————优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列

系列文章目录 集合及数据结构第十节(上)————优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列 优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列 优先级队列的概念堆的概念堆的存储方式堆的创建变量的作…

谷粒商城实战笔记-250-商城业务-消息队列-RabbitMQ安装-Docker

一,docker安装RabbitMq RabbitMQ 是一个开源的消息代理软件,广泛用于实现异步通信和应用程序解耦。 使用 Docker 容器化技术可以简化 RabbitMQ 的安装和部署过程。 以下是使用 Docker 安装 RabbitMQ 的详细步骤。 步骤 1: 安装 Docker 如果您的系统…

Linux 软件编程 网络 tcp

1.TCP粘包问题: TCP发送数据是连续的,两次发送的数据可能粘连成一包被接收到 1.解决粘包问题方法: 1.接收指定长度:(不稳定) 发送5个字节 接收5个字节 2.睡眠&#x…

【数据库】Mysql 批量变更所有字段类型为varchar的字符集

生成变更语句 SELECT CONCAT(ALTER TABLE , TABLE_NAME, MODIFY , COLUMN_NAME, , COLUMN_TYPE, , CHARACTER SET utf8 COLLATE utf8_general_ci , CASE WHEN IS_NULLABLE YES THEN NULL DEFAULT NULL WHEN IS_NULLABLE NO AND ISNULL(COLUMN_DEFAULT) THEN NOT NULL EL…