FreeRTOS互斥量

文章目录

  • 一、互斥量的使用场合
  • 二、互斥量函数
    • 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);	}
#endifxTaskCreate(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.rhkb.cn/news/389173.html

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

相关文章

无人机环保行业解决方案-应急环境污染处理

无人机环境应急处理 传统环境应急的典型挑战 发生环境应急事件时&#xff0c;最重要的是快速获取前方信息。然而&#xff0c;有毒气体 和易燃易爆品多&#xff0c;存在二次爆炸风险&#xff0c;严重威胁人身安全。无人机可快 速赶到事故现场&#xff0c;查看周边环境、污染物…

单火供电零线发生器 单火变零火线开关面板零火开关老房改造必备

创作 史新华 零线发生器套件与单火线供电套件&#xff0c;作为现代智能家居解决方案中的创新之作&#xff0c;它们犹如智能电气领域的魔术师&#xff0c;巧妙地解决了传统智能开关在单火线路环境中因无零线而难以应用的难题。这些套件&#xff0c;如同智能电气世界的桥梁&…

【守卫你的安全】基于高通QCS6490之AI智慧电子围篱展示方案

高通QCS6490是一款专为工业和商业物联网应用而设计的系统单芯片(SoC)&#xff0c;支援高阶物联网装置的Wi-Fi 6E连线&#xff0c;以及先进的摄像头、人工智能和计算功能&#xff0c;以实现低功耗下的强大性能。这款芯片结合高通Kryo™ 670 CPU和高通Hexagon处理器&#xff0c;具…

芋道源码yudao-cloud 二开日记(商品sku数据归类为规格属性)

商品的每一条规格和属性在数据库里都是单一的一条数据&#xff0c;从数据库里查出来后&#xff0c;该怎么归类为对应的规格和属性值&#xff1f;如下图&#xff1a; 在商城模块&#xff0c;商品的单规格、多规格、单属性、多属性功能可以说是非常完整&#xff0c;如下图&#x…

【前端逆向】最佳JS反编译利器,原来就是chrome!

有时候需要反编译别人的 min.js。 比如简单改库、看看别人的 min,js 干了什么&#xff0c;有没有重复加载&#xff1f;此时就需要去反编译Javascript。 Vscode 里面有一些反编译插件&#xff0c;某某Beautify等等。但这些插件看人品&#xff0c;运气不好搞的话&#xff0c;反…

Postman:API开发与测试的强大伴侣

在当今的数字化时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为不同软件系统之间通信的桥梁&#xff0c;它们如同数字世界的“翻译官”&#xff0c;使得数据和服务能够在不同的平台和应用程序之间无缝流动。然而&#xff0c;API的开发、测试和维护并非易事…

见证中国数据库的崛起:从追赶到引领的壮丽征程《三》

见证中国数据库的崛起&#xff1a;从追赶到引领的壮丽征程《三》 三、深度思考&#xff1a;中国数据库发展的经验与启示产学研用结合的创新模式应用驱动的创新路径人才培养的关键作用 【纪录片】中国数据库前世今生 在数字化潮流席卷全球的今天&#xff0c;数据库作为IT技术领域…

PowerDNS架构解析与安装部署指南

1、背景介绍 目前公司使用PowerDNS进行DNS管理&#xff0c;但由于采用的是单节点架构&#xff0c;存在不可用的风险。为提升系统的稳定性和可靠性&#xff0c;我们计划对现有架构进行重构。通过引入高可用性设计&#xff0c;我们将优化系统架构&#xff0c;使其能够在故障情况…

计算机毕业设计Python+Flask微博舆情分析 微博情感分析 微博爬虫 微博大数据 舆情监控系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI

基于Python/flask的微博舆情数据分析可视化系统 python爬虫数据分析可视化项目 编程语言&#xff1a;python 涉及技术&#xff1a;flask mysql echarts SnowNlP情感分析 文本分析 系统设计的功能&#xff1a; ①用户注册登录 ②微博数据描述性统计、热词统计、舆情统计 ③微博数…

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

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

springboot的表现层/控制层controller开发

第一步&#xff1a;新建文件和注入业务层对象 需要使用的注解&#xff1a; 第一个声明是restful风格开发 第二个是需要设置网页访问路径 RestController RequestMapping("/fuels")//http://localhost/fuels注入服务层对象&#xff1a; Autowiredprivate FuelServ…

RabbitMQ知识总结(基本概念)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 基本概念 Producer&#xff1a; 消息的生产者&#xff0c;是一个向…

活动报道 | 盘古信息携IMS OS+小快轻准产品集亮相东莞市中小数转试点供需对接会

8月1日&#xff0c;由东莞市工业和信息化局主办&#xff0c;南城街道经济发展局承办&#xff0c;东莞市软件行业协会协办的东莞市中小企业数字化转型城市试点供需对接会&#xff08;城区和水乡新城片区&#xff09;隆重召开。市工业和信息化局副局长江小敏、市工业和信息化局信…

Mybatis超级方便操作数据方式(注解+封装mapper接口)!!!

Mybatis作为一个流行的持久层框架&#xff0c;其优化了Java程序与数据库的交互过程。它的核心在于使用Mapper接口与XML映射文件或注解绑定来实现对数据库的操作。这种方式不仅简化了数据库操作&#xff0c;还提升了开发效率&#xff0c;使得开发者可以从繁琐的JDBC代码中解放出…

MIT-离散数学笔记

离散数学 PropositionEx 1:Ex 2:Ex 3:Ex 4:Ex 5:Ex 6:Ex 7:Ex 8: Proposition In mathematics, we have a mathematical proof is a verification of a proposition by a chain of logical deductions from a set of axioms. 在数学中&#xff0c;数学证明是通过一组公理的一系…

Vmware ubuntu20.04 虚拟文件夹

目录 1.vmware 设置 2.ubuntu设置 1.vmware 设置 设置完成后我们开机 2.ubuntu设置 我们打开终端 输入命令 vmware-hgfsclient可以看到你当前的共享文件 然后我们输入以下命令&#xff0c;用于将共享文件夹挂载到虚拟机中 sudo vmhgfs-fuse .host:/ /mnt -o nonempty -o …

从零开始安装Jupyter Notebook和Jupyter Lab图文教程

前言 随着人工智能热浪&#xff08;机器学习、深度学习、卷积神经网络、强化学习、AGC以及大语言模型LLM, 真的是一浪又一浪&#xff09;的兴起&#xff0c;小伙伴们Python学习的热情达到了空前的高度。当我20年前接触Python的时候&#xff0c;做梦也没有想到Python会发展得怎么…

Blackcat V2.2付费会员制WordPress资源站主题

Blackcat-付费会员制WordPress资源站主题&#xff0c;该主题是基于简约实用的主题选项框架 Codestar Framework 进行开发的功能强大的付费会员制主题&#xff0c;该主题尤其适合用于搭建付费下载资源网站&#xff0c;比如素材站、软件站、视频教程站等付费资源下载网站。 集成…

(~_~)

一、用不同url头利用python访问一个网站&#xff0c;并把返回的东西保存为 requests库 主要用于http发送请求和处理响应 1.发送get和post请求 requests.get(目标网址) requests.post(url,data) post于get不同的是get一般用来请求获取数据&#xff0c;而post相当于带着数…

Yearning-MYSQL 审计平台部署

目录 一. 环境准备 二. 部署安装 三. 基础使用 1.用户管理 2. 创建SQL审计流程 3. 自定义审核规则 4. 导入数据源 5. 创建权限组 6. 登录用户申请工单 1. 创建一个DDL工单提交 2. SQL审核执行 3. SQL执行 4. 数据验证 Yearning 是一个开源的 MySQL SQL 审计平台…