同步与互斥(二)

一、谁上锁就由谁解锁?

        互斥量、互斥锁,本来的概念确实是:谁上锁就得由谁解锁。
        但是FreeRTOS并没有实现这点,只是要求程序员按照这样的惯例写代码。
        main函数创建了2个任务:
        任务1:高优先级,一开始就获得互斥锁,永远不释放。
        任务2:任务1阻塞时它开始执行,它先尝试获得互斥量,失败的话就监守自盗(释放互斥量、开锁),然后再上锁

代码如下:

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

两个任务的代码和执行流程如下图所示:
A:任务1的优先级高,先运行,立刻上锁
B:任务1阻塞
C:任务2开始执行,尝试获得互斥量(上锁),超时时间设为0。根据返回值打印出:上锁失败

因为A流程那里没有解锁,所以会上锁失败,而且超时时间设置为0,不会等待,获取失败就直接返回BaseType_t,成功就返回的是pdTURE。
D:任务2监守自盗,开锁,成功!
E:任务2成功获得互斥量
F:任务2阻塞
        可见,任务1上的锁,被任务2解开了。所以,FreeRTOS并没有实现"谁上锁就得由谁开锁"的功能。

二、优先级反转

假设任务A、B都想使用串口,A优先级比较低:
        任务A获得了串口的互斥量
        任务B也想使用串口,它将会阻塞、等待A释放互斥量
高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)
如果涉及3个任务,可以让"优先级反转"的后果更加恶劣。
        互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二级制信号量的差别。
        程序使用二级制信号量来演示"优先级反转"的恶劣后果。
        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三个任务的代码和运行过程如下图所示:
A:HPTask优先级最高,它最先运行。在这里故意打印,这样才可以观察到flagHPTaskRun的脉
冲。
B:MPTask开始运行。在这里故意打印,这样才可以观察到flagMPTaskRun的脉冲。
C:LPTask开始运行,获得二进制信号量,然后故意打印很多字符
D:HP Delay时间到,HPTask恢复运行,它无法获得二进制信号量,一直阻塞等待

// #define portMAX_DELAY         ( TickType_t ) 0xffffffffUL

查看串口打印情况更加直观:


E:MP Delay时间到,MPTask恢复运行,它比LPTask优先级高,一直运行。导致LPTask无法运
行,自然无法释放二进制信号量,于是HPTask无法运行。
总结:
        LPTask先持有二进制信号量,但是MPTask抢占LPTask,使得LPTask一直无法运行也就无法释放信号量,导致HPTask任务无法运行,优先级最高的HPTask竟然一直无法运行!

static void vHPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask start\r\n");/* 让LPTask先运行 */	vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){	flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask wait for Lock\r\n");/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);}
}

static void vMPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );	flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;printf("MPTask start\r\n");/* 让LPTask、HPTask先运行 */	vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){	flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;}
}
static void vLPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	uint32_t i;char c = 'A';printf("LPTask start\r\n");/* 无限循环 */for( ;; ){	flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);/* 耗时很久 */printf("LPTask take the Lock for long time");for (i = 0; i < 26; i++) {flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;printf("%c", c + i);}printf("\r\n");/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);vTaskDelay(xTicksToWait);}
}

优先级继承

/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;int main( void )
{prvSetupHardware();/* 创建互斥量/二进制信号量 *///xLock = xSemaphoreCreateBinary( );xLock = xSemaphoreCreateMutex( );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;
}/*-----------------------------------------------------------*//*-----------------------------------------------------------*/
static void vLPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	uint32_t i;char c = 'A';printf("LPTask start\r\n");/* 无限循环 */for( ;; ){	flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);/* 耗时很久 */printf("LPTask take the Lock for long time");for (i = 0; i < 26; i++) {flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;printf("%c", c + i);}printf("\r\n");/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);printf("task_low\r\n");vTaskDelay(xTicksToWait);}
}static void vMPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );	flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;printf("MPTask start\r\n");/* 让LPTask、HPTask先运行 */	vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){	flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;}
}static void vHPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask start\r\n");/* 让LPTask先运行 */	vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){	flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask wait for Lock\r\n");/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);printf("task_high\r\n");flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);printf("task\r\n");}
}

 唯一的改变就是:

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

 

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

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

相关文章

先进制造身份治理现状洞察:从手动运维迈向自动化身份治理时代

在新一轮科技革命和产业变革的推动下&#xff0c;制造业正面临绿色化、智能化、服务化和定制化发展趋势。为顺应新技术革命及工业发展模式变化趋势&#xff0c;传统工业化理论需要进行修正和创新。其中&#xff0c;对工业化水平的判断标准从以三次产业比重标准为主回归到工业技…

Kubernetes 容器编排(7)

离线业务编排详解 在线业务和离线业务 在线业务 Deployment、StatefulSet以及 DaemonSet 这三个编排概念的共同之处是&#xff1a;它们主要编排的对象&#xff0c;都是"在线业务"&#xff0c;即&#xff1a;Long Running Task&#xff08;长作业&#xff09;。比如…

尚硅谷 java 2023(基础语法)笔记

一、变量与运算符 1、HelloWorld的编写和执行 class HelloChina{public static void main(String[] args){System.out.println("hello,world!!你好&#xff0c;中国&#xff01;");} } 总结&#xff1a; 1. Java程序编写和执行的过程&#xff1a; 步骤1&#xff1…

HackTheBox - Medium - Linux - Sandworm (我的创作纪念日

Sandworm Sandworm 是一台中等难度的 Linux 机器&#xff0c;它托管了一个具有“PGP”验证服务的 Web 应用程序&#xff0c;该服务容易受到服务器端模板注入 &#xff08;SSTI&#xff09; 的攻击&#xff0c;导致“Firejail”监狱内的远程代码执行 &#xff08;RCE&#xff0…

12、Qt:用QProcess类启动外部程序:简单使用

一、说明 简单使用&#xff1a;在一个函数中&#xff0c;使用QProcess类的临时对象调用可执行文件exe&#xff0c;只有这个exe执行完了&#xff0c;这个函数才往下执行&#xff0c;一次性打印出exe所有输出信息&#xff1b;复杂使用&#xff1a;创建QProcess类的全局对象&…

STM32F4的DHT11初始化与实例分析

STM32—— DHT11 本文主要涉及STM32F4 的DHT11的使用以及相关时序的介绍&#xff0c;最后有工程下载地址。 文章目录 STM32—— DHT11一、 DHT11的介绍1.1 DHT11的经典电路 二、DHT11的通信2.1 DHT11的传输数据格式2.2 DHT11 通信分步解析 三、 DHT11 代码3.1 引脚图3.2 电路图…

阿里云林立翔:基于阿里云 GPU 的 AIGC 小规模训练优化方案

云布道师 本篇文章围绕生成式 AI 技术栈、生成式 AI 微调训练和性能分析、ECS GPU 实例为生成式 AI 提供算力保障、应用场景案例等相关话题展开。 生成式 AI 技术栈介绍 1、生成式 AI 爆发的历程 在 2022 年的下半年&#xff0c;业界迎来了生成式 AI 的全面爆发&#xff0c…

【接口测试】HTTP接口详细验证清单

概述 当我们在构建、测试、发布一套新的HTTP API时&#xff0c;包括我在内的大多数人都不知道他们所构建的每一个组件的复杂性和细微差别。 即使你对每一个组件都有深刻的理解&#xff0c;也可能会有太多的信息在你的脑海中出现。 以至于我们不可能一下把所有的信息进行梳理…

pycharm下执行conda命令提示无法识别解决方案

1 问题描述 win10环境命令行执行conda命令&#xff0c;报命令无法识别&#xff0c;错误信息如下&#xff1a; PS D:\code\cv> conda activate pt conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&a…

python基础语法总结

基础是深入必不可少的&#xff0c;如果没有基础的支持&#xff0c;后面都是浮云&#xff0c;就像情侣之间&#xff0c;没有感情基础是不会有好结果的&#xff0c;物资基础是靠两个人打拼的&#xff0c;所以学习就像谈恋爱&#xff0c;两个人要学会沟通&#xff0c;才能修成正果…

循环神经网络中的梯度消失或梯度爆炸问题产生原因分析(二)

上一篇中讨论了一般性的原则&#xff0c;这里我们具体讨论通过时间反向传播&#xff08;backpropagation through time&#xff0c;BPTT&#xff09;的细节。我们将展示目标函数对于所有模型参数的梯度计算方法。 出于简单的目的&#xff0c;我们以一个没有偏置参数的循环神经…

在Linux环境下模拟实现命令解释器用c语言实现mypwd「粉丝答疑」

粉丝提问 Solution 要在 Linux 环境下用 C 语言模拟实现一个命令解释器&#xff0c;包含mypwd, mymkdir, myrmdir, mycd, mylist, mycp, mydate, mycreate, mydelete, exit等基本命令&#xff0c;需要按照以下步骤进行&#xff1a; 理解每个命令的功能&#xff1a; mypwd: 显示…

SQL---Zeppeline前驱记录与后驱记录查询

内容导航 类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统…

RTP/RTCP/RTSP/SIP/SDP/RTMP对比

RTP&#xff08;Real-time Transport Protocol&#xff09;是一种用于实时传输音频和视频数据的协议。它位于传输层和应用层之间&#xff0c;主要负责对媒体数据进行分包、传输和定时。 RTCP&#xff08;Real-Time Control Protocol&#xff09;是 RTP 的控制协议&#xff0c;…

TrustZone之可信操作系统

有许多可信内核&#xff0c;包括商业和开源的。一个例子是OP-TEE&#xff0c;最初由ST-Ericsson开发&#xff0c;但现在是由Linaro托管的开源项目。OP-TEE提供了一个功能齐全的可信执行环境&#xff0c;您可以在OP-TEE项目网站上找到详细的描述。 OP-TEE的结构如下图所示&…

Windows 系统彻底卸载 SQL Server 通用方法

Windows 系统彻底卸载 SQL Server 通用方法 无论什么时候&#xff0c;SQL Server 的安装和卸载都是一件让我们头疼的事情。因为不管是 SQL Server 还是 MySQL 的数据库&#xff0c;当我们在使用数据库时因为未知原因出现问题&#xff0c;想要卸载重装时&#xff0c;如果数据库…

vue element plus 管理系统路由菜单简要设计(后端获取菜单)

1 需求 管理系统“菜单”由后端接口返回&#xff0c;前端需要根据后端返回的“菜单”数组&#xff0c;构造路由&#xff0c;渲染侧栏菜单有些菜单是子菜单&#xff0c;有对应的路由&#xff0c;但是不在侧栏显示&#xff08;比如一些详情页面&#xff09; 注&#xff1a;这里的…

Asp.Net Core 项目中常见中间件调用顺序

常用的 AspNetCore 项目中间件有这些&#xff0c;调用顺序如下图所示&#xff1a; 最后的 Endpoint 就是最终生成响应的中间件。 Configure调用如下&#xff1a; public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseD…

LIGA-Stereo:为基于立体 3D 检测器的学习 LiDAR 几何感知表示

论文地址&#xff1a;https://openaccess.thecvf.com/content/ICCV2021/papers/Guo_LIGA-Stereo_Learning_LiDAR_Geometry_Aware_Representations_for_Stereo-Based_3D_Detector_ICCV_2021_paper.pdf 论文代码&#xff1a;https://github.com/xy-guo/LIGA-Stereo 摘要 基于立…

CloudCanal x Debezium 打造实时数据流动新范式

简述 Debezium 是一个开源的数据订阅工具&#xff0c;主要功能为捕获数据库变更事件发送到 Kafka。 CloudCanal 近期实现了从 Kafka 消费 Debezium 格式数据&#xff0c;将其 同步到 StarRocks、Doris、Elasticsearch、MongoDB、ClickHouse 等 12 种数据库和数仓&#xff0c;…