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

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

    • 前言
    • 信号量 semaphore
      • 两种信号量的对比
      • 信号量的使用
        • 1、创建信号量
        • 2、give
        • 3、take
        • 4、删除信号量
        • 使用计数型信号量实现同步功能
        • 使用二进制型信号量实现互斥功能
    • 互斥量 mutex
      • 问题:优先级反转
      • 解决方法:优先级继承
      • 问题:递归上锁造成死锁
      • 解决方法:递归锁
      • 互斥量的基本使用
        • 1、创建互斥量
        • 2、获得互斥量 Take
        • 3、释放互斥量 Give
      • 使用优先级继承来实现优先级反转
        • 二进制信号量优先级反转的过程分析
        • 互斥量使用优先级继承来解决优先级反转
      • 互斥量和二进制信号量的区别和共同点
      • 互斥量的递归锁
        • 递归锁实现
          • 1、创建递归锁
          • 2、Give/Take

前言

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


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

第一弹:FreeRTOS学习笔记(1、FreeRTOS初识、任务的创建以及任务状态理论、调度算法等)
第二弹: FreeRTOS学习笔记(2、同步与互斥通信、队列、队列集的使用)

信号量 semaphore

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

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

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

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


信号起通知作用

量,表示资源的数量

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

如何创建信号量

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第1张图片

两种信号量的对比

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

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

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

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

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

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

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第2张图片

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

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

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

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

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

不管哪种信号量,只要没有超过创建时指定的最大值,都可以累加成功
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第3张图片

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

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第4张图片
pdTRUE,表示Take成功

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第5张图片

信号量的使用

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

需要定义这两个宏

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

image.png
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第6张图片

1、创建信号量

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

2、give

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第7张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第8张图片

3、take

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第9张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第10张图片

4、删除信号量

对于动态创建的信号量,如果不使用,不再需要时,可以删除他们以回收内存
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第11张图片

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

虽然实现了同步功能,但是对于数据的完整性,需要我们自己来做
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第12张图片

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

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

task3和task4独占的使用串口

创建二进制信号量来实现互斥
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第13张图片

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第14张图片

task3和task4实现了串口的独占使用,即实现互斥功能
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第15张图片

互斥量 mutex

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

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第16张图片

二进制信号量也能实现互斥
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第17张图片

当出现一种情况

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抢占了

优先级高的程序反而不能执行,这就是优先级反转
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第18张图片

解决方法:优先级继承

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

什么是优先级继承?

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

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

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

问题:递归上锁造成死锁

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第19张图片

这是自我死锁

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第20张图片

解决方法:递归锁

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

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

此时B来上锁,就会进入阻塞状态
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第21张图片

互斥量分为两种

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

互斥量的基本使用

1、创建互斥量

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

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

image.png
创建互斥量时还需要配置宏
image.png
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第22张图片

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

2、获得互斥量 Take

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第23张图片

3、释放互斥量 Give

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第24张图片

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第25张图片

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

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

此时是二进制型信号量

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第26张图片
下图就是优先级反转的例子
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第27张图片

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第28张图片

根据波形图对
优先级反转详细说明
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第29张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第30张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第31张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第32张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第33张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第34张图片

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第35张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第36张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第37张图片

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

  1. 互斥量初始值为1

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

  3. Give/Take函数完全一样

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

互斥量的递归锁

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

但是FreeRTOS没有实现这一点

A持有,B也可以释放

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

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

一般的互斥量,并没有实现,谁持有,就由谁释放
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第38张图片

递归锁实现

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第39张图片

1、创建递归锁

image.png

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

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

2、Give/Take

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第40张图片
FreeRTOS学习笔记(3、信号量、互斥量的使用)_第41张图片

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

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

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

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第42张图片

FreeRTOS学习笔记(3、信号量、互斥量的使用)_第43张图片

你可能感兴趣的:(FreeRTOS,学习,笔记,FreeRTOS,stm32,c语言)