FreeRTOS学习——同步互斥

news2024/9/25 7:21:59

FreeRTOS学习——同步互斥

目录

  • FreeRTOS学习——同步互斥
    • 一、概念
      • 1.1 同步
      • 1.2 互斥
    • 二、示例——有缺陷的同步
    • 三、示例——优化有缺陷的同步
    • 四、示例——有缺陷的互斥
    • 五、总结

一、概念

1.1 同步

在FreeRTOS中,同步是指任务之间按照某种规则进行协调和按序执行的过程。其目的是保证任务或线程之间的有序交互,使它们能够按照预期的顺序完成各自的操作或实现特定的约束条件。常见的同步场景包括等待其他任务完成、等待某个条件满足、协调任务之间的依赖关系等。

FreeRTOS提供了多种同步机制,例如信号量、互斥量、消息队列等,用于实现任务之间的同步。这些机制可以帮助任务之间进行协作,以确保它们按照一定的顺序、时机和约束进行执行。

同步机制在FreeRTOS中非常重要,因为它们可以确保系统的正确性和稳定性。如果没有同步机制,任务之间可能会出现竞争条件,导致系统行为不可预测。通过使用同步机制,FreeRTOS可以确保任务之间的正确交互,从而提高系统的可靠性和性能。

1.2 互斥

FreeRTOS中,互斥是一种同步机制,用于保护共享资源,确保任务访问这些资源时的原子性,避免数据错误。具体来说,互斥是指在多任务环境中,运行特定代码段时确保数据的一致性和完整性,避免多个任务同时访问和修改共享资源导致错误的发生。它通过互斥量(又称互斥信号量)来实现,互斥量是一种特殊的二值信号量,用于实现对临界资源的独占式处理。任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性。

一句话理解同步与互斥:我等你用完厕所,我再用厕所。

什么叫同步?就是:哎哎哎,我正在用厕所,你等会。

什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。

同步与互斥经常放在一起讲,是因为它们之的关系很大, “互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?

二、示例——有缺陷的同步

实验目的:计算ul变量累加到1000000需要多长时间

具体实现:

创建2个Task,定义一个全局变量taskFlag,当taskFlag等于1时表示Task1正在运行,当taskFlag等于0时表示Task2正在运行

Task1:

  • Task1中定义一个累加变量ul
  • 第一次运行Task1(开始累加ul)时记录系统此刻tick时间vstartTime
  • 当ul>1000000(ul累加完毕)时记录系统此刻tick时间vendTime
  • 累加完毕后将累加结束标志endFlag置位,将vendTime-vstartTime时间赋值给全局变量vtotleTime

Task2:

  • 判断全局变量endFlag是否置位
  • 若endFlag置位,打印出vtotleTime

实验代码:

#define mainDELAY_LOOP_COUNT 1000000

volatile TickType_t vtotleTime;
volatile TickType_t vstartTime = 0, vendTime = 0;
volatile bool endFlag = FALSE;
volatile bool endLock = FALSE;
volatile uint8_t taskFlag = 0;

void vTask1( void *pvParameters )
{
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	vstartTime = xTaskGetTickCount();
	/* 打印任务1的信息 */
	printf( "Count start: %d\r\n",vstartTime );	

	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{	
		/* 表示Task1在运行 */
		taskFlag = 1;

		/* 延迟一会(比较简单粗暴) */
		if(endLock == FALSE)
		{
			ul++;

			if((ul > mainDELAY_LOOP_COUNT)  && (endFlag != TRUE))
			{
				endFlag = TRUE;
				vendTime = xTaskGetTickCount();
				vtotleTime = vendTime - vstartTime;
				vTaskDelay(10);
			}
		}		
	}
}

void vTask2( void *pvParameters )
{
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 表示Task2在运行 */
		taskFlag = 0;
		if(endFlag == TRUE)
		{
			endFlag = FALSE;
			printf( "Count end: %d\r\nTotle time: %d\r\n",vendTime, vtotleTime);
			endLock = TRUE;	
		}
	}
}

int main( void )
{
	prvSetupHardware();
	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

运行结果:

在这里插入图片描述

实验分析:从taskFlag分析,Task1在累加ul变量时,Task2仍然在运行,仍然耗费CPU资源,理论上分析如果在ul累加期间,使Task2任务挂起,ul从0累加到1000000耗时会减少一半。

三、示例——优化有缺陷的同步

基于“二”中的示例进行优化

优化思路:使用队列通信代替Task2对全局变量endFlag的判断,队列传输数据结构体:

typedef struct
{
	TickType_t startTime;
	TickType_t endTime;
	TickType_t stopFlag;
}TIME;
  • main中创建一个队列
  • Task2中接收队列,队列中没有数据时Task2阻塞,队列中有数据时打印出endTime-startTime,即ul累加到1000000耗时
  • Task1中累加ul,当ul累加到1000000时将数据结构体通过队列发送给Task2

实验代码:

#define mainDELAY_LOOP_COUNT 1000000

typedef struct
{
	TickType_t startTime;
	TickType_t endTime;
	TickType_t stopFlag;
}TIME;

void vTask1( void *pvParameters )
{
	TIME time1;
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	volatile TickType_t buf[10];

	time1.stopFlag = FALSE;
	T1 = xTaskGetTickCount();
	time1.startTime = xTaskGetTickCount();
	/* 打印任务1的信息 */
	printf( "Count start: %d\r\n",time1.startTime);	

	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{	
		/* 表示Task1在运行 */
		taskFlag = 1;

		/* 延迟一会(比较简单粗暴) */
			ul++;

			if((ul > mainDELAY_LOOP_COUNT) && time1.stopFlag != TRUE)
			{
				T2 = xTaskGetTickCount();
				time1.endTime = xTaskGetTickCount();
				time1.stopFlag = TRUE;
				xQueueSend(task1Handle, &time1, NULL);
			}		
	}
}

void vTask2( void *pvParameters )
{
	TIME time2;
	volatile uint32_t ul; /* volatile用来避免被优化掉 */

	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 表示Task2在运行 */
		taskFlag = 0;

		xQueueReceive(task1Handle, &time2, portMAX_DELAY);

		if(time2.stopFlag == TRUE)
		{
			printf( "Count end: %d\r\nTotle time: %d\r\n",time2.endTime, time2.endTime - time2.startTime);
			time2.stopFlag = FALSE;
		}
		

	}
}

int main( void )
{
	prvSetupHardware();
	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);

	task1Handle = xQueueCreate(1,sizeof(TIME));
	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

运行结果:

在这里插入图片描述

实验分析:使用队列时,Task2未接收到队列的数据时会进入挂起状态,不会再占用CPU资源,Task1往队列发送数据时,会同时将Task2从挂起状态改变为就绪或者运行状态。

四、示例——有缺陷的互斥

实验目的:不同任务访问相同临界资源(比如全局变量)时有缺陷的互斥

仍然使用“二”中的示例,进行简单的修改,在“二”的代码中Task1累加完ul变量得到累加耗时后使用了vTaskDelay函数使Task1挂起,如果不使用vTaskDelay函数就会出现有缺陷的互斥现象
实验代码:

#define mainDELAY_LOOP_COUNT 1000000

volatile TickType_t vtotleTime;
volatile TickType_t vstartTime = 0, vendTime = 0;
volatile bool endFlag = FALSE;
volatile bool endLock = FALSE;
volatile uint8_t taskFlag = 0;

void vTask1( void *pvParameters )
{
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	vstartTime = xTaskGetTickCount();
	/* 打印任务1的信息 */
	printf( "Count start: %d\r\n",vstartTime );	

	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{	
		/* 表示Task1在运行 */
		taskFlag = 1;

		/* 延迟一会(比较简单粗暴) */
		if(endLock == FALSE)
		{
			ul++;

			if((ul > mainDELAY_LOOP_COUNT)  && (endFlag != TRUE))
			{
				endFlag = TRUE;
				vendTime = xTaskGetTickCount();
				vtotleTime = vendTime - vstartTime;
				//vTaskDelay(10);
			}
		}		
	}
}

void vTask2( void *pvParameters )
{
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 表示Task2在运行 */
		taskFlag = 0;
		if(endFlag == TRUE)
		{
			endFlag = FALSE;
			printf( "Count end: %d\r\nTotle time: %d\r\n",vendTime, vtotleTime);
			endLock = TRUE;	
		}
	}
}

int main( void )
{
	prvSetupHardware();
	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

运行结果:

在这里插入图片描述

实验分析:从运行结果来看,Task2打印了2次,理论上从代码分析,程序运行到Task2的打印时,应该是先将endFlag设置为了FALSE,但是打印了2次说明endFlag的值没有写入成功,单步调试分析一下:

将endFlag值在Watch1中显示,在Task2打印处设置一个断点,全速运行

在这里插入图片描述

从代码来看Task1也会修改endFlag的值,在Task1中计算累计时间处再打一个断点,全速运行

在这里插入图片描述

再次全速运行到Task2中打印处

在这里插入图片描述

再次全速运行再也不会听到断点处,这就是Task2中打印了2次的详细步骤。

缺陷原理:

1、当运行到Task2打印处是,endFlag被更改为FALSE

2、但是Task2还未来得及更改完endLock就切换到了Task1

3、Task1中又将endFlag更改为TRUE

4、切换到Task2时再次打印了一遍

5、这次在切换到Task1之前修改完了endLock的值

6、再切换到Task1时不会再更改endFlag的值了

修改代码逻辑可以避免Task2打印2次:

修改前:

void vTask2( void *pvParameters )
{
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 表示Task2在运行 */
		taskFlag = 0;
		if(endFlag == TRUE)
		{
			endFlag = FALSE;
			printf( "Count end: %d\r\nTotle time: %d\r\n",vendTime, vtotleTime);
			endLock = TRUE;	
		}
	}
}

修改后:

void vTask2( void *pvParameters )
{
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 表示Task2在运行 */
		taskFlag = 0;
		if(endFlag == TRUE)
		{
            endLock = TRUE;	
			endFlag = FALSE;
			printf( "Count end: %d\r\nTotle time: %d\r\n",vendTime, vtotleTime);
		}
	}
}

这样修改可以避免Task2打印2次,因为在打印时Task2已经完成了对endLock和endFlag的修改,但是同样存在缺陷,原因是:

C语言中1条给全局变量的赋值语句并不是程序的最小运行单位,C语言的本质是汇编,从Task2的汇编码可以看到,将endFlag的值赋值为0分为3个步骤:

在这里插入图片描述

假设在赋值过程中运行完汇编第二步后就切换了任务,其他任务对该临界资源也进行了修改,再切换到当前任务时该被修改的临界资源又被修改了。因此这种修改也并不是万无一失的。

补充一个办法:当前任务需要修改临界资源时,现将系统所有中断关闭,暂停任务调度和中断,修改完临界资源后再将中断恢复,恢复任务调度和中断。

但是这种方法关闭中断也对系统有一定的风险!

五、总结

正确使用互斥与同步,FreeRTOS提供的方法是安全可靠的,比如队列、信号量、互斥量、任务通知等等,就像“三、优化有缺陷的同步”一样,使用FreeRTOS提供的方法同样可以优化有缺陷的互斥。

在这里插入图片描述

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

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

相关文章

HarmonyOS:使用MindSpore Lite引擎进行模型推理

场景介绍 MindSpore Lite 是一款 AI 引擎,它提供了面向不同硬件设备 AI 模型推理的功能,目前已经在图像分类、目标识别、人脸识别、文字识别等应用中广泛使用。 本文介绍使用 MindSpore Lite 推理引擎进行模型推理的通用开发流程。 基本概念 在进行开…

PDI/Kettle-9.2.0.0-R(对应jdk1.8)源码编译问题记录及源码结构简介

目录 📚第一章 前言📗背景📗目的📗总体方向 📚第二章 代码结构初识基本结构📗代码模块详情 ⁉️问题记录❓问题一:代码分支哪些是发布版本❗答:后缀-R的版本 ❓问题二:50…

AI智能配音助手微信小程序前后端源码支持多种声音场景选择

大家好今天给大家带来一款配音小程序 ,这款小程序支持多种不同声音和场景的选择更人性化, 比如说支持各地区的方言,英文,童声呀等等、 另外也支持男声女声的选择,反正就是模板那些非常的多 当然啦音量,语调,语速那些都是可以DIY跳转的哟,所以说这一款小程…

使用PyTorch II的新特性加快LLM推理速度

Pytorch团队提出了一种纯粹通过PyTorch新特性在的自下而上的优化LLM方法,包括: Torch.compile: PyTorch模型的编译器 GPU量化:通过降低精度操作来加速模型 推测解码:使用一个小的“草稿”模型来加速llm来预测一个大的“目标”模型的输出 张量并行:通过在多个设备…

【专题】最小生成树(prim算法、kruscal算法)

目录 一、最小生成树二、Prim算法1. 算法思想2. 例题3. 性能分析 三、Kruscal算法1. 算法思想2. 例题3. 性能分析 一、最小生成树 生成树中边的权值(代价)之和最小的树。 二、Prim算法 1. 算法思想 设N(V,{E})是连通网,TE是N上最小生成树…

【IEEE】2区SCI,接收领域广,稳定检索47年!

重点 本期推荐 区块链是一种新兴技术,很多行业和领域都以创新方式采用了此技术,如能源、金融、媒体和娱乐以及零售等。此外,区块链作为一门新兴的交叉学科, 涉及密码学应用(加密,隐私等), 分布式…

stm32与Freertos入门(二)移植FreeRTOS到STM32中

简介 注意:FreeRTOS并不是实时操作系统,而是分时复用的,只不过切换频率很快,感觉上是同时在工作。本次使用的单片机型号为STM32F103C8T6,通过CubeMX快速移植。 一、CubeMX快速移植 1、选择芯片 打开CubeMX软件,进行…

Diva配置——Communication Tests

关联文章:CANoe.Diva生成测试用例 Diva目录 一、CANoe.Diva简介二、Communication Tests配置一、CANoe.Diva简介 CANoe.DiVa 是一种 CANoe 选项,用于对 ECU 中的诊断软件实施进行自动化测试。 可以通过CANdelaStudio制作的CDD或ODX文件,经过Diva配置自动生成测试用例和测试脚…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于时空注意力卷积模型的超短期风电功率预测》

这个标题描述了一种用于超短期风电功率预测的模型,该模型基于时空注意力卷积模型。下面我会逐步解读这个标题的关键词和背景: 超短期风电功率预测:风电功率预测是指根据历史风速和其他相关数据,通过建立数学模型来预测未来特定时间…

Vue学习计划-Vue2--VueCLi(四)组件传值和自定义事件

1. 组件传值 组件化编码流程: 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用: 一个组件在用&#xff0c…

云演ctf 你能看到我吗?

1、看题目稍微尝试不好用,看到提示的文件访问也不好用。读取文件又好像拦截了啥。 读取hint.php文件payload ctfphp://filter/convert.base64-encode/resourcehihintnt.php2、解密后又看到一个文件 有需要绕过 ctfphp://filter/convert.base64-encode/resource..…

轻量封装WebGPU渲染系统示例<45>- 材质组装流水线(MaterialPipeline)灯光、阴影、雾(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/MaterialPipelineFog.ts 当前示例运行效果: 此示例基于此渲染系统实现,当前示例TypeScript源码如下: export class MaterialPipelineFog {pr…

C语言数组刷题-----数组填充

输入一个整数 V ,输出一个长度为 10 的数组 N ,数组中的第一个元素为 V ,每个后续元素的值都为上一个元素的值的 2 倍。 例如,如果输入整数为 1 ,则数组为:1,2,4,8… 输入格式 输入一个整数 V 。输出格…

cmake多模块架构, DLL依赖编译

整体结构: 最外层的cmakelist CMakeLists.txt project(cmakeMulPackage) cmake_minimum_required(VERSION 3.17) set(CMAKE_CXX_STANDARD 11)#设置环境相关 message("set env.cmake") message("CMAKE_BUILD_TYPE is ${CMAKE_BUILD_TYPE}") …

TG-5510cb: txo高稳定性+105℃高温

TG-5510CB是一款高稳定性TCXO,可提供CMOS或限幅正弦输出,5G基站和边缘计算的额定温度为85C,需要室外安装、小型化和无风扇运行。与其他TCXO相比,实验室提供了许多改进,如低温度斜率和相位噪声。符合GR-1244-CORE地层3和…

详细教程 - 从零开发 Vue 鸿蒙harmonyOS应用 第一节

关于使用Vue开发鸿蒙应用的教程,我这篇之前的博客还不够完整和详细。那么这次我会尝试写一个更加完整和逐步的指南,从环境准备,到目录结构,再到关键代码讲解,以及调试和发布等,希望可以让大家详实地掌握这个过程。 一、准备工作 下载安装 DevEco Studio 下载地址:…

AD20-Excel创建IC类元件库

目录 准备模板AD操作 准备模板 AD操作 结果生成如下: over!!!

【教学类-06-18】20231216 (按“列”正序题)X-Y之间“加法题+题”(1页最多0-13。不考虑空格补全)

作品展示:按列排序,从小到大正序(有空格) 背景需求: 55格模板,很多幼儿都是按照“列”的方式,从上到下,从左向右完成题目的。 视觉上,两列之间间距大(给孩子…

labelme标注json文件检查标注标签(修改imageWidth,imagePath,imageHeight)

# !/usr/bin/env python # -*- encoding: utf-8 -*- #---wzhimport os import json# 这里写你自己的存放照片和json文件的路径 json_dir =rC:\Users\Lenovo\Desktop\json3 json_files = os.listdir(json_dir

广西开放大学形成性考核 平时作业 统一资料 参考

试卷代号:1293 心理学 参考试题 一、选择题(每个3分,共30分,含单选和多选,请将正确答案的字母序号填入括号中) 1.人们通常把下列图形知觉为一个连续、完整的形状,说明图形知觉遵循( )。…