FreeRTOS互斥量

news2024/9/23 0:35:13

文章目录

  • 一、互斥量的使用场合
  • 二、互斥量函数
    • 1、创建
    • 2、其他函数
  • 三、示例: 优先级继承
  • 四、递归锁
    • 1、死锁的概念
    • 2、自我死锁
    • 3、函数

怎么独享厕所?自己开门上锁,完事了自己开锁。

你当然可以进去后,让别人帮你把门:但是,命运就掌握在别人手上了。

使用队列、信号量,都可以实现互斥访问,以信号量为例:

  • 信号量初始值为1
  • 任务A想上厕所,"take"信号量成功,它进入厕所
  • 任务B也想上厕所,"take"信号量不成功,等待
  • 任务A用完厕所,"give"信号量;轮到任务B使用

这需要有2个前提:

  • 任务B很老实,不撬门(一开始不"give"信号量)
  • 没有坏人:别的任务不会"give"信号量

可以看到,使用信号量确实也可以实现互斥访问,但是不完美。

使用互斥量可以解决这个问题,互斥量的名字取得很好:

  • 量:值为0、1
  • 互斥:用来实现互斥访问

它的核心在于:谁上锁,就只能由谁开锁。

很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:

  • 即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。
  • 谁上锁、谁释放:只是约定。

本章涉及如下内容:

  • 为什么要实现互斥操作
  • 怎么使用互斥量
  • 互斥量导致的优先级反转、优先级继承

一、互斥量的使用场合

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。

比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信息混杂在一起。

这种现象很常见:

  • 访问外设:刚举的串口例子
  • 读、修改、写操作导致的问题

对于同一个变量,比如int a,如果有两个任务同时写它就有可能导致问题。
对于变量的修改,C代码只有一条语句,比如:a=a+8;,它的内部实现分为3步:读出原值、修改、写入。

image

我们想让任务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不符合。

  • 对变量的非原子化访问

    修改变量、设置结构体、在16位的机器上写32位的变量,这些操作都是非原子的。也就是它们的操作过程都可能被打断,如果被打断的过程有其他任务来操作这些变量,就可能导致冲突

  • 函数重入

    “可重入的函数"是指:多个任务同时调用它、任务和中断同时调用它,函数的运行也是安全的。可重入的函数也被称为"线程安全”(thread safe)。

    每个任务都维持自己的栈、自己的CPU寄存器,如果一个函数只使用局部变量,那么它就是线程安全的。

    函数中一旦使用了全局变量、静态变量、其他外设,它就不是"可重入的",如果该函数正在被调用,就必须阻止其他任务、中断再次调用它。

上述问题的解决方法是:任务A访问这些全局变量、函数代码时,独占它,就是上个锁。这些全局变量、函数代码必须被独占地使用,它们被称为临界资源

互斥量也被称为互斥锁,使用过程如下:

  • 互斥量初始值为1
  • 任务A想访问临界资源,先获得并占有互斥量,然后开始访问
  • 任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
  • 任务A使用完毕,释放互斥量;任务B被唤醒、得到并占有互斥量,然后开始访问临界资源
  • 任务B使用完毕,释放互斥量

正常来说:在任务A占有互斥量的过程中,任务B、任务C等等,都无法释放互斥量。

但是FreeRTOS未实现这点:任务A占有互斥量的情况下,任务B也可释放互斥量

二、互斥量函数

1、创建

互斥量是一种特殊的二进制信号量。

使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。

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

/* 创建一个互斥量,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

#define configUSE_MUTEXES 1

2、其他函数

要注意的是,互斥量不能在ISR中使用。

各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/*
 * 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
                   );

三、示例: 优先级继承

本节代码为:22_mutex_priority_inversion,主要看nwatch\game2.c。

12章12.5示例的问题在于,car1低优先级任务获得了锁,但是它优先级太低而无法运行。

如果能提升car1任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?

把car1任务的优先级提升到什么水平?car3也想获得同一个互斥锁,不成功而阻塞时,它会把car1的优先级提升得跟car3一样。

这就是优先级继承:

  • 假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁
  • 任务B说:你既然持有宝剑,又不给我,那就继承我的愿望吧
  • 于是任务A就继承了任务B的优先级
  • 这就叫:优先级继承
  • 等任务A释放互斥锁时,它就恢复为原来的优先级
  • 互斥锁内部就实现了优先级的提升、恢复

在22_mutex_priority_inversion里,创建的是互斥量,代码如下:

static void car1_task(void *params)
{
	struct car *car = params;
	struct ir_Data idata;
	
	/* 创建自己的队列 */
	QueueHandle_t xQueueIR = xQueueCreate(10,sizeof(struct ir_Data));
	
	/* 注册队列 */
	RegisterQueueHandle(xQueueIR);
	
	/* 初始化小车 */
	ShowCar(car);
	
	/* 获得信号量 */
	xSemaphoreTake(xSemTicks,portMAX_DELAY);//car1获得信号量开始运行
	
	while(1)
	{
//		/* 读取按键值  */
//		xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
		
//		/* 控制汽车往右移动 */
//		if(idata.data == car->control_key)
//		{
				if(car->x < g_xres - CAR_LENGTH)
				{
					/* 隐藏汽车 */
					HideCar(car);
					
					/* 调整位置 */
					car->x += 1	;//每次按下右移5个单位
					if(car->x > g_xres - CAR_LENGTH)//超过屏幕分辨率(128)
						car->x = g_xres - CAR_LENGTH;//到达最大位置处
					
					/* 重新显示汽车 */
					ShowCar(car);
					
					vTaskDelay(50);
					
					if(car->x == g_xres - CAR_LENGTH)
					{
						/* 汽车到达最右边 释放信号量 */
						xSemaphoreGive(xSemTicks);
						vTaskDelete(NULL);
					}
				}
//		}
	}
}

static void car2_task(void *params)
{
	struct car *car = params;
	struct ir_Data idata;
	
	vTaskDelay(1000);//car2等待1s后开始运行 不用等待car1释放信号量
	
	/* 创建自己的队列 */
	QueueHandle_t xQueueIR = xQueueCreate(10,sizeof(struct ir_Data));
	
	/* 注册队列 */
	RegisterQueueHandle(xQueueIR);
	
	/* 初始化小车 */
	ShowCar(car);
	
	/* 获得信号量 */
	//xSemaphoreTake(xSemTicks,portMAX_DELAY);
	
	while(1)
	{
//		/* 读取按键值  */
//		xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
		
//		/* 控制汽车往右移动 */
//		if(idata.data == car->control_key)
//		{
				if(car->x < g_xres - CAR_LENGTH)
				{
					/* 隐藏汽车 */
					HideCar(car);
					
					/* 调整位置 */
					car->x += 1	;//每次按下右移5个单位
					if(car->x > g_xres - CAR_LENGTH)//超过屏幕分辨率(128)
						car->x = g_xres - CAR_LENGTH;//到达最大位置处
					
					/* 重新显示汽车 */
					ShowCar(car);
					
					//vTaskDelay(50);
					mdelay(50 );
					
					if(car->x == g_xres - CAR_LENGTH)
					{
						/* 汽车到达最右边 释放信号量 */
						//xSemaphoreGive(xSemTicks);//car2没有获取信号量 所以当其运行完毕自杀后car1继续运行
						vTaskDelete(NULL);
					}
				}
//		}
	}
}

static void car3_task(void *params)
{
	struct car *car = params;
	struct ir_Data idata;
	
	/* 创建自己的队列 */
	QueueHandle_t xQueueIR = xQueueCreate(10,sizeof(struct ir_Data));
	
	/* 注册队列 */
	RegisterQueueHandle(xQueueIR);
	
	/* 初始化小车 */
	ShowCar(car);
	
	vTaskDelay(2000);//car3延时2s后等待获得信号量开始运行
	
	/* 获得信号量 */
	xSemaphoreTake(xSemTicks,portMAX_DELAY);
	
	while(1)
	{
//		/* 读取按键值  */
//		xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
		
//		/* 控制汽车往右移动 */
//		if(idata.data == car->control_key)
//		{
				if(car->x < g_xres - CAR_LENGTH)
				{
					/* 隐藏汽车 */
					HideCar(car);
					
					/* 调整位置 */
					car->x += 1	;//每次按下右移5个单位
					if(car->x > g_xres - CAR_LENGTH)//超过屏幕分辨率(128)
						car->x = g_xres - CAR_LENGTH;//到达最大位置处
					
					/* 重新显示汽车 */
					ShowCar(car);
					
					//vTaskDelay(50);
					mdelay(50 );
					
					if(car->x == g_xres - CAR_LENGTH)
					{
						/* 汽车到达最右边 释放信号量 */
						xSemaphoreGive(xSemTicks);
						vTaskDelete(NULL);
					}
				}
//		}
	}
}

void car_game(void)
{
	int i,j;
	int x;
	
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
	draw_init();
	draw_end();
	
	//xSemTicks = xSemaphoreCreateBinary();//创建一个二值信号量(这里有个现象:由于二值信号量的初始值为0 所以下面三个任务无法获得信号量 都将原地不动 解决方法:释放信号量)

	xSemTicks = xSemaphoreCreateMutex();//创建互斥量
	
	/* 绘制路标	*/
	for(i=0;i<3;i++)
	{
		for(j=0;j<8;j++)
		{
			draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
			draw_flushArea(16*j, 16+17*i, 8, 1);		
		}
	}

#if 0
	/* 显示三辆小车 */
	for(i=0;i<3;i++)
	{
		draw_bitmap(cars[i].x, cars[i].y, carImg, 15, 16, NOINVERT, 0);
		draw_flushArea(cars[i].x, cars[i].y, 15, 16);	
	}
#endif

	xTaskCreate(car1_task, "car1task", 128, &cars[0], osPriorityNormal, NULL);	
	xTaskCreate(car2_task, "car2task", 128, &cars[1], osPriorityNormal+2, NULL);	
	xTaskCreate(car3_task, "car3task", 128, &cars[2], osPriorityNormal+3, NULL);	
}

把二值信号量创建打开,互斥量创建注释掉就会有优先级反转的问题。

把二值信号量创建注销掉,互斥量创建打开,就解决了优先级反转的问题。

22_mutex_priority_inversion的实验现象为:car1先运行一会;然后car2运行一会;接着car3任务启动,但是它无法获得互斥量而阻塞,并且提升了car1的优先级;于是:car1、car2交替运行(虽然car2的优先级高于car1,但是car2会使用vTaskDelay阻塞,car1就有机会运行了);当car1运行到终点,释放了互斥量,car3就可以运行了。

四、递归锁

1、死锁的概念

日常生活的死锁:我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!

假设有2个互斥量M1、M2,2个任务A、B:

  • A获得了互斥量M1
  • B获得了互斥量M2
  • A还要获得互斥量M2才能运行,结果A阻塞
  • B还要获得互斥量M1才能运行,结果B阻塞
  • A、B都阻塞,再无法释放它们持有的互斥量
  • 死锁发生!

2、自我死锁

假设这样的场景:

  • 任务A获得了互斥锁M
  • 它调用一个库函数
  • 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
  • 死锁发生!

3、函数

怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:

  • 任务A获得递归锁M后,它还可以多次去获得这个锁
  • "take"了N次,要"give"N次,这个锁才会被释放

递归锁的函数根一般互斥量的函数名不一样,参数类型一样,列表如下:

递归锁一般互斥量
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
获得xSemaphoreTakeRecursivexSemaphoreTake
释放xSemaphoreGiveRecursivexSemaphoreGive

函数原型如下:

/* 创建一个递归锁,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );


/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );

/* 获得 */
BaseType_t xSemaphoreTakeRecursive(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

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

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

相关文章

Linux驱动入门实验班——步进电机模块驱动(附百问网视频链接)

目录 一、工作原理 二、接口图 三、真值表 四、编写思路 1.构造file_operations结构体 2.编写入口函数 3.编写出口函数 4.编写write函数 五、bug记录 六、源码 课程链接 一、工作原理 步进电机由定子和转子两部分组成。定子上有多组线圈&#xff0c;通常称为相&…

8.16 mysql读写分离架构+MyCAT实现读写分离

1、读写分离的目的 数据库负载均衡&#xff1a; 当数据库请求增多时&#xff0c;单例数据库不能够满足业务 需求。需要进行数据库实例的扩容。多台数据库同时相 应请求。也就是说需要对数据库的请求&#xff0c;进行负载均衡 但是由于数据库服务特殊原因&#xff0c;数据库扩容…

《SPSS零基础入门教程》学习笔记——05.模型入门

文章目录 5.1 回归模型5.2 分类模型5.3 降维和刻度模型5.4 生存分析和时间序列模型 5.1 回归模型 线性回归&#xff08;分析 -> 回归 -> 线性&#xff09;曲线估计&#xff08;分析 -> 回归 -> 曲线估计&#xff09; 二元Logistic回归&#xff08;分析 -> 回归…

Centos7主机带宽限速

需求&#xff1a;最近有两个主机经常把带宽打满。咨询了阿里云无法对内网网卡做限制。这边想使用linux默认的TC工具。 限速之前测试带宽。这时带宽有 168.4MB/s。 ]# scp filebeat-8.8.2-x86_64.rpm 172.116.47.54:/root/100% 26MB 168.4MB/s 00:00 1. 限制出站&#xff0…

数据结构入门——08排序

1.排序 1.1什么是排序 排序是一种操作&#xff0c;通过比较记录中的关键字&#xff0c;将一组数据按照特定顺序&#xff08;递增或递减&#xff09;排列起来。排序在计算机科学中非常重要&#xff0c;因为它不仅有助于数据的快速检索&#xff0c;还能提高其他算法的性能。 1…

PostgreSQL-03-入门篇-过滤数据

文章目录 1. WHEREWHERE 子句概述WHERE 子句示例1) 使用 WHERE 子句和等于运算符 () 示例2) 使用 WHERE 子句和 AND 运算符示例3) 使用 WHERE 子句和 OR 运算符示例4) 使用 WHERE 子句和 IN 运算符示例5) 使用 WHERE 子句和 LIKE 运算符示例6) 将 WHERE 子句与 BETWEEN 运算符一…

自动化运维---ansible

ansible是一种由Python开发的自动化运维工具&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 特点: 部署简单 默认使用ssh进行管理&#xff0c;基于py…

【LLM大模型论文日更】| 大型语言模型用于模拟搜索用户行为

论文&#xff1a;https://arxiv.org/pdf/2403.09142代码&#xff1a;未开源机构&#xff1a;中国人民大学高瓴人工智能研究院领域&#xff1a;信息检索发表&#xff1a;SIGIR2024 短文 Abstract 由于成本效益和可再现性方面的优势&#xff0c;用户模拟已成为信息检索系统面向用…

Python酷库之旅-第三方库Pandas(084)

目录 一、用法精讲 351、pandas.Series.str.isdigit方法 351-1、语法 351-2、参数 351-3、功能 351-4、返回值 351-5、说明 351-6、用法 351-6-1、数据准备 351-6-2、代码示例 351-6-3、结果输出 352、pandas.Series.str.isspace方法 352-1、语法 352-2、参数 3…

【 亿邦动力网-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

钢铁百科:美标A633GrC材质分析、A633GrC正火状态、A633GrC材料性能

A633GrC钢板是一种美国ASTM标准的高强度低合金结构钢板&#xff0c;字母"A"代表钢&#xff08;Steel&#xff09;&#xff0c;数字"633"表示该材料的牌号&#xff0c;而"GrC"表示该材料为高强度低合金钢&#xff08;High Strength Low Alloy&…

wtv-next 宝塔更新教程

数据库升级办法 输入用户名密码&#xff0c;点执行 程序升级办法 文件后缀看清楚&#xff0c;是amd64、arm64。etc 目录下是配置文件&#xff0c;按照格式往下加就行。 回到刚才那个页面 先点停止&#xff0c;再点启动

【大模型部署及其应用 】RAG检索技术和生成模型的应用程序架构:RAG 使用 Meta AI 的 Llama 3

目录 RAG检索技术和生成模型的应用程序架构1. **基本概念**2. **工作原理**3. **RAG的优势**4. **常见应用场景**5. **RAG的挑战**6. **技术实现**参考RAG 使用 Meta AI 的 Llama 3亲自尝试运行主笔记本与文档应用聊天关键架构组件1. 自定义知识库2. 分块3. 嵌入模型4. 矢量数据…

GAMES104:08游戏引擎的动画技术基础-学习笔记

文章目录 一&#xff0c;动画技术简介动画技术的挑战 二&#xff0c;2D游戏动画技术2.1 精灵动画&#xff08;sprite animation&#xff09;2.2 Live2D 三&#xff0c;3D游戏动画技术3.1 基于层次的刚体动画3.2 顶点动画3.3 Morph Target Animation3.4 蒙皮动画3.4.1 2D蒙皮动画…

房产系统源码开发

一、开发需求&#xff1a; 信息录入与管理&#xff1a;系统需要支持各种数据的录入&#xff0c;这包括但不限于客户信息、房源信息、员工信息、合同信息和财务信息等。这些数据是房产系统的基础&#xff0c;对于后续的信息查询、分析和共享至关重要。信息查询与检索&#xff1…

Ports Number of field profile samples 参数设置

Ports Number of field profile samples 参数设置 正文正文 在进行 Port 仿真时,通常我们会对 port 端口中的光束模式进行设定,很多时候我们会遇到一个名为 Number of field profile samples 的参数,如下图所示: 如果去官方论坛查看解释,往往会被搞得云里雾里。这个参数…

什么是AR、VR、MR、XR?

时代背景 近年来随着计算机图形学、显示技术等的发展&#xff0c;视觉虚拟化技术得到了广泛的发展&#xff0c;并且越来越普及化&#xff0c;慢慢的也走入人们的视野。目前市场上视觉虚拟化技术的主流分为这几种 VR、AR、MR、XR。这几项技术并不是最近才出现的&#xff0c;VR的…

RabbitMQ 的工作原理

下面是rabbitmq 的工作原理图解 1、客户端连接到消息队列服务器&#xff0c;打开一个channel。 2、客户端声明一个exchange&#xff0c;并设置相关属性。 3、客户端声明一个queue&#xff0c;并设置相关属性。 4、客户端使用routing key&#xff0c; 在exchange和queue 之间…

FreeBSD 针对OpenSSH 高危漏洞发布紧急补丁

近日&#xff0c;FreeBSD 项目的维护者针对OpenSSH 高危漏洞发布了紧急补丁。该漏洞被追踪为 CVE-2024-7589&#xff0c;CVSS 得分为 7.4&#xff08;最高分为 10.0&#xff09;。通过利用该漏洞&#xff0c;黑客能够在权限提升的情况下远程执行任意代码。 根据上周发布的一份…