这是第三弹,由于CSDN长度的限制,所以把FreeRTOS学习分为几部分来发,这是第三部分
主要包括信号量、互斥量使用
等
第一弹
:FreeRTOS学习笔记(1、FreeRTOS初识、任务的创建以及任务状态理论、调度算法等)
第二弹
: FreeRTOS学习笔记(2、同步与互斥通信、队列、队列集的使用)
队列可以传送数据,队列可以传送不同的数据
有的时候只需要传递状态,并不需要传递具体的信息
这就是信号量,不去传送数据,而是传送状态,这样至起到了通知的作用,更加节省内存
信号量,不能传输数据,只有一个计数值,来表示资源的数量
信号起通知作用
量,表示资源的数量
左边是生产者,生产好一个商品后让计数值+1
右边是消费者,取出一个商品后计数值-1
如何创建信号量
计数:事件产生give信号量,计数值加1,处理事件,take信号量,计数值减1
资源管理:想要访问资源时,首先需要take信号量,计数值减1,用完资源后,give信号量,计数值加1
计数型信号量,的取值范围为0-任意数
二进制信号量,的取值返回为0或者1 但是二进制信号量的初始值为0
除了取值不一样外,其他的操作都是完全一样的
信号量也相当于是一个队列
队列有一个结构体Queue,结构体中有一个指针,指向存放数据的一个buff
但是对于信号量,并不需要这个buffer,只需要这个结构体
对于信号量,核心是信号量的计数值
这个计数值保存在初始化信号量时传入的初始的计数值
创建完信号量后,就可以加减信号量的Value了
让信号量的计数值+1,并且取出东西
一开始Value为0,调用take函数,使信号量减1,没有数据,那么信号量就没办法-1,进入阻塞状态,还可以指定阻塞多长时间
在阻塞状态中,如果有另一个task往里面放数据,那么就会从阻塞状态中唤醒,进入Ready状态
对于计数型信号量,可以让这个值累加,但是不能超过创建时指定的最大值
对于二进制信号量,取值就只有0和1,如果值为1,再次调用give也不会成功
可以判断give函数的返回值,看累加是否成功
不管哪种信号量,只要没有超过创建时指定的最大值,都可以累加成功
如果信号量的值为0,就没办法take成功,不成功的话可以指定阻塞时间
小问题:
使用队列也可以实现同步,为什么还要使用信号量呢?
使用信号量时,先创建,然后去添加资源,获得资源,使用句柄来表示一个信号量
需要定义这两个宏
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /*信号量相关宏*/
#define configUSE_COUNTING_SEMAPHORES 1
对于动态创建的信号量,如果不使用,不再需要时,可以删除他们以回收内存
注意了二进制型信号量初始值为0
所以创建二进制信号量时需要,手动give一下,否则take时会一直卡在阻塞状态
task3和task4独占的使用串口
互斥量是一个特殊的二进制信号量
**任务A访问这些全局变量、函数代码时,独占它,就是上个锁。这些全局变
量、函数代码必须被独占地使用,它们被称为临界资源 **
互斥量,就是用来保护临界资源,大家互斥的使用这些资源
当出现一种情况
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抢占了
解决优先级反转的方法就是使用优先级继承
什么是优先级继承?
在C获得锁Take,因为锁被A上锁了,所以进入阻塞状态,进入阻塞状态的同时会进行优先级继承
此时A的优先级变成了C的优先级,A继承了C的优先级
此时A的优先级变为了3,所以C阻塞后,A开始运行
A对锁进行解锁,unlock,释放互斥量,A的优先级又变成了原来的优先级1
然后轮到C来执行
这个过程中C的优先级并没有被B来反转,优先级继承解决了上述优先级反转的问题
优先级继承的好处在于提升优先级,如果C的优先级比A的还低,就没有继承的必要
这是自我死锁
TaskA运行,上锁后,信号量计数值为0,打印数据
进入xxxlib函数,再次上锁,因为信号量计数值为0,无法继续上锁,所以进入阻塞状态
进入阻塞状态,没有办法解锁,造成了死锁
递归锁是互斥量的另外一种形式
在上锁了之后,还可以二次上锁,但是二次上锁后要解锁,否则会进入阻塞状态
一次上锁对应一次解锁
互斥量分为两种
二进制型信号量的初始值为0,所以创建时需要手动give释放一下,计数值+1,否则take时,计数值无法减1,将会发生阻塞
而互斥量的初始值为1,创建后不需要Give一次
/*互斥量相关宏*/
#define configUSE_MUTEXES 1
此时是二进制型信号量
互斥量初始值为1
二进制信号量初始值为0
Give/Take函数完全一样
互斥量具有优先级继承的功能
互斥量,本意是谁持有,谁释放
但是FreeRTOS没有实现这一点
A持有,B也可以释放
但是互斥量的递归锁实现了
谁持有,就有谁释放
递归上锁和解锁
FreeRTOS为了减小程序的体积,使用某些功能时,首先需要配置
和信号量不同的是Give/Take的函数发生改变
同时递归锁能够实现谁上锁,谁解锁的功能
递归锁可以让task互斥使用串口
递归锁实现了谁持有,就由谁来释放
递归锁内部会记录持有者,对于持有递归锁的task,可以循环的使用上锁,开锁