互斥量 的初识

Q: 什么是互斥量?

A: 在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上二值型信号量用于同步, 而互斥型信号量用于资源保护。 互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象

优先级反转现象

以上图为例,系统中有3个不同优先级的任务 H/M/L,最高优先级任务H和最低优先级任务L通过普通二值信号量机制,共享资源。目前任务L占有资源,锁定了信号量,Task H运行后将被阻塞,直到Task L释放信号量后,Task H才能够退出阻塞状态继续运行。但是Task H在等待Task L释放信号量的过 程中,中等优先级任务M抢占了任务L(能够抢占的原因是Task H 和 L 通过信号量来共享资源,但是Task M没有参考信号量,所以可以直接打断优先级低的任务),从而延迟了信号量的释放时间,导致Task H阻塞了更长时间,Task M优先级明明比Task H低,但是却可以阻塞它,这种现象称为优先级倒置或反转

优先级继承

优先级继承是一种使用互斥信号量解决“优先级反转”问题的方法,但是不能完全解决,只能尽可能降低“优先级反转”带来的影响。

当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。

如果上图中的例子中,Tas H 和 Task L 共享的不是普通二值信号量而是互斥信号量,那么当Task L占用信号量的时候,高优先级的Task H依然会被阻塞,但是同时也会将低优先级的Task L的优先级提升至和自己一样的最高级所以中等优先级的Task M就无法再抢占Task L了,Task H也就不会阻塞很长时间了

互斥量相关 API 函数

注意!互斥信号量不能用于中断服务函数中!

  • 输入参数:无
  • 返回值: 成功,返回对应互斥量的句柄; 失败,返回 NULL 

注意,和之前普通二值信号量和计数信号量不同,之前的二值和计数信号量的原厂API函数在创建完信号量之后就结束了,是Cube再次对其封装,赋予了“创建完信号量就全部释放”的功能,但是互斥量的原厂API函数在创建后,就会自动释放一个互斥量,这个功能不需要Cube封装,是自带的

 

实操演示

需求: 1. 演示优先级反转  2. 使用互斥量优化优先级翻转问题

在 C:\mjm_CubeMX_proj 路径下,复制一份Cube的母版并重命名为 :mjm_freeRTOS_Mute:

优先级反转演示:

1. 打开相应的Cube文件,找到左侧的Middleware --> FREERTOS

1.1 然后在下方找到"Task and Queues",创建三个不同优先级的任务:

 

1.2 在下方找到"Timers and Semaphores",创建普通二值信号量:

2. 生成代码并打开Keil, 编写三个任务的内容:

注意!在写释放信号量的代码时,不能写成下面这种形式,因为在这个例程中,TASK_L和TASK_H都在不停的获取信号量,一旦将printf写在释放信号量函数之后,就会导致printf显示的位置错误,因为信号量一旦被释放,就会被另一个任务所获取了

if (xSemaphoreGive(myBinarySemHandle) == pdTRUE){ //释放信号量并判断返回值
    printf("Task_L has stopped\r\n");
}
#include "stdio.h"

void StartTask_H(void const * argument)
{
  for(;;)
  {
    if (xSemaphoreTake(myBinarySemHandle, portMAX_DELAY ) == pdTRUE){ //获取信号量并判断返回值,注意阻塞时间要设为最大,否则当TASK_L占用时,其他任务会直接获取失败并返回,不会等了
			printf("Task_H is running......\r\n");
			HAL_Delay(3000);
			printf("Task_H has stopped\r\n");
			xSemaphoreGive(myBinarySemHandle); //释放信号量
		}
		osDelay(1000); //注意,此处的Delay必不可少,因为如果没有Delay,信号量将不断被TASK_H 和 Task_L 所获取和释放,轮不到Task_M执行,无法复现优先级反转的效果
  }
}

void StartTask_M(void const * argument)
{
  for(;;)
  {
		printf("Task_M:HELLO MJM\r\n"); //Task_M不占用信号量,只是单纯占用CPU打印一句话
    osDelay(1000);
  }
}

void StartTask_L(void const * argument)
{
  for(;;)
  {
    if (xSemaphoreTake(myBinarySemHandle, portMAX_DELAY ) == pdTRUE){ //获取信号量并判断返回值,注意阻塞时间要设为最大,否则当TASK_L占用时,其他任务会直接获取失败并返回,不会等了
			printf("Task_L is running......\r\n");
			HAL_Delay(3000);
			printf("Task_L has stopped\r\n");
			xSemaphoreGive(myBinarySemHandle); //释放信号量
		}
		osDelay(1000); //注意,此处的Delay必不可少,因为如果没有Delay,信号量将不断被TASK_H 和 Task_L 所获取和释放,轮不到Task_M执行,无法复现优先级反转的效果
  }
}

 

实现效果1

打开串口助手:

 回顾刚刚提到的优先级反转的例子:

可见,被鼠标蓝色选中的区域就发生了优先级反转的现象

 

使用互斥量优化的演示:

1. 在上个演示的Cube文件中,找到左侧的Middleware --> FREERTOS

1.1 在下方找到"Timers and Semaphores",删除刚刚创建的普通二值信号量:

1.2 在下方找到"Mutexes",创建互斥量:

2. 生成代码并打开Keil, 重写三个任务的内容:

2.1 在freertos.c 中可以看到创建互斥量的代码,和二值信号量的创建非常类似 

2.2 重写代码,其实就是将二值信号量的句柄换成互斥量:

使用二值信号量的获取和释放函数可以直接适用于互斥量,从侧面印证了互斥量就是一种特殊的二值信号量

#include "stdio.h"

void StartTask_H(void const * argument)
{
  for(;;)
  {
    if (xSemaphoreTake(myMutexHandle, portMAX_DELAY ) == pdTRUE){ //获取信号量并判断返回值,注意阻塞时间要设为最大
			printf("Task_H is running......\r\n");
			HAL_Delay(3000);
			printf("Task_H has stopped\r\n");
			xSemaphoreGive(myMutexHandle); //释放信号量
		}
		osDelay(1000); //注意,此处的Delay必不可少,因为如果没有Delay,信号量将不断被TASK_H 和 Task_L 所获取和释放,轮不到Task_M执行,无法复现优先级反转的效果
  }
}


void StartTask_M(void const * argument)
{
  for(;;)
  {
		printf("Task_M:HELLO MJM\r\n"); //Task_M不占用信号量,只是单纯占用CPU打印一句话
    osDelay(1000);
  }
}


void StartTask_L(void const * argument)
{

  for(;;)
  {
    if (xSemaphoreTake(myMutexHandle, portMAX_DELAY ) == pdTRUE){ //获取信号量并判断返回值,注意阻塞时间要设为最大,否则当TASK_L占用时,其他任务会直接获取失败并返回,不会等了
			printf("Task_L is running......\r\n");
			HAL_Delay(3000);
			printf("Task_L has stopped\r\n");
			xSemaphoreGive(myMutexHandle); //释放信号量
		}
		osDelay(1000); //注意,此处的Delay必不可少,因为如果没有Delay,信号量将不断被TASK_H 和 Task_L 所获取和释放,轮不到Task_M执行,无法复现优先级反转的效果
  }
}

实现效果2

再次打开串口助手:

可见,在使用互斥量了之后,Task_M不再具备打断Task_L的能力了。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式理论:CAP理论 BASE理论

文章目录 1. CAP定理1.1 一致性1.2 可用性1.3 分区容错1.4 矛盾 2. BASE理论3. 解决分布式事务的思路4. 扩展 解决分布式事务问题,需要一些分布式系统的基础知识作为理论指导。 1. CAP定理 Consistency(一致性): 用户访问分布式系统中的任意节点,得到的…

Sentinel dashboard的使用;Nacos保存Sentinel限流规则

Sentinel dashboard的使用 往期文章 Nacos环境搭建Nacos注册中心的使用Nacos配置中心的使用Sentinel 容灾中心的使用 参考文档 Sentinel alibaba/spring-cloud-alibaba Wiki GitHub 限流结果 下载sentinel-dashboard github地址:Sentinel/sentinel-dashboar…

SpringBoot的三层架构以及IOCDI

目录 一、IOC&DI入门 二、三层架构 数据库访问层 业务逻辑层 控制层 一、IOC&DI入门 在软件开发中,IOC(Inversion of Control)和DI(Dependency Injection)是密切相关的概念。 IOC(控制反转&a…

【腾讯云 Cloud Studio 实战训练营】全新的开发方式,让你实现一站式开发

一、前言 关于 Cloud Studio 全在线云端开发 用户只需要浏览器就可以访问和使用Cloud Studio,无需在本地配置开发环境。Cloud Studio将开发环境部署在云服务器上,用户可以随时随地进行开发。多语言支持 Cloud Studio支持常见的开发语言,如Node.js、Python、Java、PHP等。用户…

Arcgis画等高线

目录 数据准备绘制等高线3D等高线今天我们将学习如何在ArcGIS中绘制等高线地图。等高线地图是地理信息系统中常见的数据表现形式,它通过等高线将地形起伏展现得一目了然,不仅美观,还能提供重要的地形信息。 数据准备 在开始之前,确保已经准备好了高程数据,它通常以栅格数…

11、PHP面向对象1

1、PHP的面向对象与其他语言类似,但也有不同。 PHP访问成员变量时,需要用“->”,而不能用“.”,访问成员函数时,需要用“->”,而不能用“.”。操作符“::”可以在没有任何声明实例的情况下访问类中的…

使用LangChain构建问答聊天机器人案例实战(二)

使用LangChain构建问答聊天机器人案例实战 逐行解读和验证全生命周期Prompting 现在我们使用GPT-4作为语言模型的驱动力,这个模型将成为整个应用程序的引擎,驱动整个应用程序运行,同时,应用程序也是基于Cpython去实现的,如图14-8所示,Pyodide是CPython到WebAssembly/Emsc…

jmeter实现webservice接口测试

其实可以用jmeter两种sampler进行webservice的测试: 1、SOAP/XML-RPC Request(但是在jmeter3.2以后版本中已经取消了这个取样器) 2、HTTP请求 下面分别介绍两种方式 一、首先需要使用soupUI工具抓取webservice接口的部分需要的信息。 1、新建项目 2、新建成功的…

htmlCSS-----定位

目录 前言 定位 分类和取值 定位的取值 1.相对定位 2.绝对位置 元素居中操作 3.固定定位 前言 今天我们来学习html&CSS中的元素的定位,通过元素的定位我们可以去更好的将盒子放到我们想要的位置,下面就一起来看看吧! 定位 定位posi…

pytorch(续周报(1))

文章目录 2.1 张量2.1.1 简介2.1.2 创建tensor2.1.3 张量的操作2.1.4 广播机制 2.2 自动求导Autograd简介2.2.1 梯度 2.3 并行计算简介2.3.1 为什么要做并行计算2.3.2 为什么需要CUDA2.3.3 常见的并行的方法:网络结构分布到不同的设备中(Network partitioning)同一层…

【Linux多线程】详解线程控制、线程分离

线程互斥与同步 👸 理解线程🤴pthead_t🥷关于线程🦸‍♀️线程控制POSIX线程库线程ID及进程地址空间布局 🦸线程分离__thread关键字🦸‍♂️pthread_detach函数🦹‍♀️pthread_exit函数&#x…

RNN架构解析——传统RNN模型

目录 传统RNN的内部结构图使用RNN优点和缺点 传统RNN的内部结构图 使用RNN rnnnn.RNN(5,6,1) #第一个参数是输入张量x的维度,第二个是隐藏层维度,第三层是隐藏层的层数 input1torch.randn(1,3,5) #第一个是输入序列的长度,第二个是批次的样本…

网络层IP协议的基本原理 数据链路层ARP协议 域名解析以及一些重要技术

目录 1 网络层IP协议协议头格式网段划分DHCPCIDR:基于子网掩码的划分方式特殊的IP号IP地址的数量限制私有IP地址和公网IP地址路由路由表 2 数据链路层 — 局域网的转发问题以太网认识以太网以太网帧格式局域网通信原理 MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对…

自动化测试——APP测试

一、环境配置 1、安装jdk 配置环境变量 2、Android SDK 环境安装 3、Appium Server安装 4、模拟器安装 5、安装appium-python-client Python第三方库 二、APP自动化测试原理 三、Desired Capabilites——APPium自动化配置项 1、设置参数 2、操作系统 3、选择版本 4、设备名称…

CAN转EtherNet/IP网关can协议破解服务

JM-EIP-CAN 是自主研发的一款 ETHERNET/IP 从站功能的通讯网关。该产品主要功能是将各种 CAN 总线和 ETHERNET/IP 网络连接起来。 本网关连接到 ETHERNET/IP 总线中做为从站使用,连接到 CAN 总线中根据节点号进行读写。 技术参数 ETHERNET/IP 技术参数 网关做为 …

选择器jQuery

诚信是你价格不菲的鞋子,踏遍千山万水,质量也应永恒不变。 jQuery选择器大全总结: jQuery选择器是一种用于在HTML文档中选择元素的强大工具。下面是一些常用的jQuery选择器的总结: 基本选择器: 元素选择器&#xff1a…

HarmonyOS/OpenHarmony元服务开发-卡片使用动效能力

ArkTS卡片开放了使用动画效果的能力,支持显式动画、属性动画、组件内转场能力。需要注意的是,ArkTS卡片使用动画效果时具有以下限制: 以下示例代码实现了按钮旋转的动画效果: Entry Component struct AttrAnimationExample { St…

生命在于学习——APP渗透学习笔记

一、app渗透篇 1、Android 简介 自从 Android 被谷歌收购(2005 年),谷歌已经完成了整个开发,在过去的 9 年里,尤其是在安全方面,有很多变化。 现在,它是世界上最广泛使用的智能手机平台&#…

PHP使用Redis实战实录4:单例模式和面向过程操作redis的语法

PHP使用Redis实战实录系列 PHP使用Redis实战实录1:宝塔环境搭建、6379端口配置、Redis服务启动失败解决方案PHP使用Redis实战实录2:Redis扩展方法和PHP连接Redis的多种方案PHP使用Redis实战实录3:数据类型比较、大小限制和性能扩展PHP使用Re…

IT技术面试必备:如何做好IT类技术面试?

博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客👦🏻 《java 面试题大全》 🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭 《MYSQL从入门到精通》数据库是开发者必会基础之…