FreeRTOS学习笔记(3、信号量、互斥量的使用)

前言

这是第三弹,由于CSDN长度的限制,所以把FreeRTOS学习分为几部分来发,这是第三部分


主要包括信号量、互斥量使用

往期学习笔记链接

第一弹FreeRTOS学习笔记(1、FreeRTOS初识、任务的创建以及任务状态理论、调度算法等)
第二弹: FreeRTOS学习笔记(2、同步与互斥通信、队列、队列集的使用)
第三弹: FreeRTOS学习笔记(3、信号量、互斥量的使用)
第四弹: FreeRTOS学习笔记(4、事件组、任务通知)
第五弹: FreeRTOS学习笔记(5、定时器、中断管理、调试与优化)

学习工程

所有学习工程
oufen / FreeRTOS学习
都在我的Gitee工程当中,大家可以参考学习

信号量 semaphore

队列可以传送数据,队列可以传送不同的数据

有的时候只需要传递状态,并不需要传递具体的信息

这就是信号量,不去传送数据,而是传送状态,这样至起到了通知的作用,更加节省内存

信号量,不能传输数据,只有一个计数值,来表示资源的数量


信号起通知作用

量,表示资源的数量

左边是生产者,生产好一个商品后让计数值+1
右边是消费者,取出一个商品后计数值-1

如何创建信号量

  • 创建信号量
  • 生产者生产好后让计数值+1 give
  • 消费者取出后让计数值-1 take

计数:事件产生give信号量,计数值加1,处理事件,take信号量,计数值减1
资源管理:想要访问资源时,首先需要take信号量,计数值减1,用完资源后,give信号量,计数值加1

image.png

两种信号量的对比

  • 计数型信号量
  • 二进制信号量

计数型信号量,的取值范围为0-任意数

二进制信号量,的取值返回为0或者1 但是二进制信号量的初始值为0

除了取值不一样外,其他的操作都是完全一样的

信号量也相当于是一个队列

队列有一个结构体Queue,结构体中有一个指针,指向存放数据的一个buff

但是对于信号量,并不需要这个buffer,只需要这个结构体

对于信号量,核心是信号量的计数值
这个计数值保存在初始化信号量时传入的初始的计数值

image.png

创建完信号量后,就可以加减信号量的Value了
让信号量的计数值+1,并且取出东西

一开始Value为0,调用take函数,使信号量减1,没有数据,那么信号量就没办法-1,进入阻塞状态,还可以指定阻塞多长时间
在阻塞状态中,如果有另一个task往里面放数据,那么就会从阻塞状态中唤醒,进入Ready状态

  1. 对于give,信号量加1 解锁

对于计数型信号量,可以让这个值累加,但是不能超过创建时指定的最大值

对于二进制信号量,取值就只有0和1,如果值为1,再次调用give也不会成功
可以判断give函数的返回值,看累加是否成功

不管哪种信号量,只要没有超过创建时指定的最大值,都可以累加成功
image.png

  1. 对于take,信号量-1 上锁

如果信号量的值为0,就没办法take成功,不成功的话可以指定阻塞时间

  • 0 take不成功,返回err
  • portMax_Delay 一直阻塞,直到成功
  1. 有多个task执行take,当其他task执行give时,唤醒哪个任务?
  • 优先级最高的task 优先执行take
  • **优先级相同时,等待最久的task ** 优先执行take

image.png
pdTRUE,表示Take成功

小问题:
使用队列也可以实现同步,为什么还要使用信号量呢?

  • 使用队列可以传递数据,数据的保存需要空间
  • 使用信号量时不需要传递数据,更加节省空间
  • 使用信号量时不需要复制数据,效率更高

image.png

信号量的使用

使用信号量时,先创建,然后去添加资源,获得资源,使用句柄来表示一个信号量

需要定义这两个宏

#define configSUPPORT_DYNAMIC_ALLOCATION 1 /*信号量相关宏*/
#define configUSE_COUNTING_SEMAPHORES 1

image.png
image.png

1、创建信号量

初始值为0 信号量计数值 最大值为10
image.png

2、give

image.png
image.png

3、take

image.png
image.png

4、删除信号量

对于动态创建的信号量,如果不使用,不再需要时,可以删除他们以回收内存
image.png

使用计数型信号量实现同步功能

虽然实现了同步功能,但是对于数据的完整性,需要我们自己来做
image.png

使用二进制型信号量实现互斥功能

注意了二进制型信号量初始值为0
所以创建二进制信号量时需要,手动give一下,否则take时会一直卡在阻塞状态

task3和task4独占的使用串口

创建二进制信号量来实现互斥
image.png

image.png

task3和task4实现了串口的独占使用,即实现互斥功能
image.png

互斥量 mutex

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

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

互斥量,就是用来保护临界资源,大家互斥的使用这些资源

image.png

二进制信号量也能实现互斥
image.png

当出现一种情况

TaskA获得信号量,计数值-1,此时二进制信号量为0
打印数据
此时TaskC运行另一个函数,give信号量,计数值+1,此时二进制信号量为1
此时TaskB从阻塞状态,进入Ready态
也能打印数据

此时串口被两个Task使用,就不是独占关系,不是互斥,对临界资源进行使用

本来应该是TaskA上锁(获得信号量,使信号量的计数值为0)
打印完数据后,应该由TaskA自己解锁
可是其他任务帮TaskA解锁,造成串口不是独占使用

要解决这样的问题,就应该是谁上锁,谁来解锁
二进制信号量并不能保证,谁上锁,谁解锁
虽然互斥量也不能保证

但是互斥量可以解决

  • 优先级反转
  • 解决递归上锁/解锁的问题

如何实现谁上锁,谁解锁

  • 上锁、解锁代码成对出现
  • 在临界代码中(想要某种资源被独占使用),不要解锁

问题:优先级反转

什么是优先级反转?
A/B/C的优先级分别是 1 2 3
A先运行,获得了锁,此时进入阻塞状态
B优先级比A高,抢占进入Running状态
由于A已经使用了锁,所以进入阻塞状态
C的优先级比B高,此时C运行,也想获得锁,因为A已经使用了锁,所以C进入阻塞状态
此时优先级高的Task优先执行,轮到B运行
在B运行过程中,一直没有放弃CPU资源,此时A不能执行

在这种情况下,C的优先级最高,A的优先级最低,结果优先级最高的C被B抢占了

优先级高的程序反而不能执行,这就是优先级反转
image.png

解决方法:优先级继承

解决优先级反转的方法就是使用优先级继承

什么是优先级继承?

在C获得锁Take,因为锁被A上锁了,所以进入阻塞状态,进入阻塞状态的同时会进行优先级继承
此时A的优先级变成了C的优先级,A继承了C的优先级
此时A的优先级变为了3,所以C阻塞后,A开始运行
A对锁进行解锁,unlock,释放互斥量,A的优先级又变成了原来的优先级1
然后轮到C来执行

这个过程中C的优先级并没有被B来反转,优先级继承解决了上述优先级反转的问题

优先级继承的好处在于提升优先级,如果C的优先级比A的还低,就没有继承的必要

问题:递归上锁造成死锁

image.png

这是自我死锁

TaskA运行,上锁后,信号量计数值为0,打印数据
进入xxxlib函数,再次上锁,因为信号量计数值为0,无法继续上锁,所以进入阻塞状态
进入阻塞状态,没有办法解锁,造成了死锁

image.png

解决方法:递归锁

递归锁是互斥量的另外一种形式

在上锁了之后,还可以二次上锁,但是二次上锁后要解锁,否则会进入阻塞状态
一次上锁对应一次解锁

此时B来上锁,就会进入阻塞状态
image.png

互斥量分为两种

  • 普通的互斥量
    • 具有优先级继承的功能
  • 递归锁
    • 除了具有优先级继承的功能外
    • 递归的功能

互斥量的基本使用

1、创建互斥量

二进制型信号量的初始值为0,所以创建时需要手动give释放一下,计数值+1,否则take时,计数值无法减1,将会发生阻塞

而互斥量的初始值为1,创建后不需要Give一次

image.png
创建互斥量时还需要配置宏
image.png
image.png

/*互斥量相关宏*/
#define configUSE_MUTEXES 1

2、获得互斥量 Take

image.png

3、释放互斥量 Give

image.png

image.png

使用优先级继承来实现优先级反转

二进制信号量优先级反转的过程分析

此时是二进制型信号量

image.png
下图就是优先级反转的例子
image.png

image.png

根据波形图对
优先级反转详细说明
image.png
image.png
image.png
image.png
image.png
image.png

互斥量使用优先级继承来解决优先级反转

image.png
image.png
image.png

互斥量和二进制信号量的区别和共同点

  1. 互斥量初始值为1

  2. 二进制信号量初始值为0

  3. Give/Take函数完全一样

  4. 互斥量具有优先级继承的功能

互斥量的递归锁

互斥量,本意是谁持有,谁释放

但是FreeRTOS没有实现这一点

A持有,B也可以释放

但是互斥量的递归锁实现了

谁持有,就有谁释放
递归上锁和解锁

一般的互斥量,并没有实现,谁持有,就由谁释放
image.png

递归锁实现

image.png

1、创建递归锁

image.png

递归锁实现,要首先配置相关宏
image.png

FreeRTOS为了减小程序的体积,使用某些功能时,首先需要配置

2、Give/Take

和信号量不同的是Give/Take的函数发生改变

同时递归锁能够实现谁上锁,谁解锁的功能

image.png
image.png

递归锁可以让task互斥使用串口

递归锁实现了谁持有,就由谁来释放

递归锁内部会记录持有者,对于持有递归锁的task,可以循环的使用上锁,开锁

image.png

image.png

猜你喜欢

转载自blog.csdn.net/cyaya6/article/details/132507946