12、FreeRTOS信号量(semaphore)

一、信号量的特性

信号量分为以下几种

  • 二值信号量
  • 计数信号量
  • 互斥信号量
  • 递归互斥信号量

1.1 使用场景

前面介绍的队列(queue)可以用于传输数据:在任务之间、任务和中断之间。 有时候我们只需要传递状态,并不需要传递具体的信息,比如:

  • 我的事做完了,通知一下你
  • 卖包子了、卖包子了,做好了1个包子!做好了2个包子!做好了3个包子!
  • 这个停车位我占了,你们只能等着

在这种情况下我们可以使用 信号量(semaphore),它更节省内存。

1.2 什么是信号量

信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代码段不被并发调用。

信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还可以用来表示资源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有两个状态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。

信号量也是队列的一种

1.3 信号量和队列的区别

队列信号量
可以容纳多个数据, 创建队列时有2部分内存: 队列结构体、存储数 据的空间只有计数值,无法容纳其他数据。 创建信号量时,只需要分配信号量结构体
生产者:没有空间存入数据时可以阻塞生产者:用于不阻塞,计数值已经达到最大时 返回失败
消费者:没有数据时可以阻塞消费者:没有资源时可以阻塞

1.4 两种信号量的对比

信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最 大值不是1,它就是计数型信号量。 差别列表如下:

二值信号量计数信号量
被创建时初始值为0被创建时初始值可以设定
其他操作是一样的其他操作是一样的

在这里插入图片描述

二、二值信号量/计数信号量

2.1 什么是二值信号量

二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我们用它来进行互斥访问或任务同步

互斥访问:比如门钥匙,只有获取到钥匙才可以开门。

任务同步:比如我录完视频你才可以看视频。

在这里插入图片描述

2.2 什么是计数信号量

计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。

在这里插入图片描述

2.2 (二值信号量/计数信号量) 相关API

  • 创建

    使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:

    创建方式二值信号量计数信号量
    动态创建xSemaphoreCreateBinaryxSemaphoreCreateCounting
    静态创建xSemaphoreCreateBinaryStaticxSemaphoreCreateBinaryStatic

    创建二进制信号量的函数原型如下:

    /* 创建一个二进制信号量,返回它的句柄。
    * 此函数内部会分配信号量结构体
    * 返回值: 返回句柄,非NULL表示成功
    */
    SemaphoreHandle_t xSemaphoreCreateBinary( void );
    /* 创建一个二进制信号量,返回它的句柄。
    * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
    * 返回值: 返回句柄,非NULL表示成功
    */
    SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t
    *pxSemaphoreBuffer );
    
    

    创建计数型信号量的函数原型如下:

    /* 创建一个计数型信号量,返回它的句柄。
    * 此函数内部会分配信号量结构体
    * uxMaxCount: 最大计数值
    * uxInitialCount: 初始计数值
    * 返回值: 返回句柄,非NULL表示成功
    */
    SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t
    uxInitialCount);
    /* 创建一个计数型信号量,返回它的句柄。
    * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
    * uxMaxCount: 最大计数值
    * uxInitialCount: 初始计数值
    * pxSemaphoreBuffer: StaticSemaphore_t结构体指针
    * 返回值: 返回句柄,非NULL表示成功
    */
    SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
    UBaseType_t uxInitialCount,
    StaticSemaphore_t
    *pxSemaphoreBuffer );
    
  • 删除

    对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。 vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:

    /*
    * xSemaphore: 信号量句柄,你要删除哪个信号量
    */
    void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
    
  • 获取/释放**( give/take)**

    操作方式任务中使用在任务中使用中断中使用在中断中使用
    释放信号量(give)xSemaphoreGivexSemaphoreGiveFromISR
    获取信号量(take)xSemaphoreTakexSemaphoreTakeFromISR

    xSemaphoreGive 的函数原型如下:

    BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
    

    xSemaphoreGive 函数的参数与返回值列表如下:

    参数说明
    xSemaphore信号量句柄,释放哪个信号量
    返回值pdTRUE表示成功,
    如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
    如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败

    pxHigherPriorityTaskWoken的函数原型如下:

    BaseType_t xSemaphoreGiveFromISR(  SemaphoreHandle_t xSemaphore,
      								  BaseType_t *pxHigherPriorityTaskWoken);
    

    xSemaphoreGiveFromISR 函数的参数与返回值列表如下:

    参数说明
    xSemaphore信号量句柄,释放哪个信号量
    pxHigherPriorityTaskWoken如果释放信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE
    返回值pdTRUE表示成功,
    如果二进制信号量的计数值已经是1,再次调用此函数则返回失 败;
    如果计数型信号量的计数值已经是最大值,再次调用此函数则返 回失败

    xSemaphoreTake 的函数原型如下:

    BaseType_t xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,
    TickType_t xTicksToWait
    );
    
    

    xSemaphoreTake函数的参数与返回值列表如下:

    参数说明
    xSemaphore信号量句柄,获取哪个信号量
    xTicksToWait如果无法马上获得信号量,阻塞一会:
    0:不阻塞,马上返回
    portMAX_DELAY: 一直阻塞直到成功
    其他值: 阻塞的Tick个数,
    可以使用 pdMS_TO_TICKS() 来指定阻塞时间为若干 ms
    返回值pdTRUE表示成功

    xSemaphoreTakeFromISR的函数原型如下:

    BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore,
      								 BaseType_t *pxHigherPriorityTaskWoken  );
    

    xSemaphoreTakeFromISR函数的参数与返回值列表如下:

    参数说明
    xSemaphore信号量句柄,获取哪个信号量
    pxHigherPriorityTaskWoken如果获取信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE
    返回值pdTRUE表示成功

三、互斥量(mutex)

## 3.1 什么是互斥量?

在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上 二值型信号量用于同步,而 互斥型信号量用于资源保护

互斥型信号量和二值型信号量还有一个 最大的区别,互斥型信号量可以有效解决优先级反转现象。

3.2 什么是优先级翻转

实质:低优先级任务将二值信号量获取,导致高优先级无法获取二值信号量阻塞,而不用获取信号量的中优先级任务能够打断低优先级任务,等到中优先级任务执行完后,得到信号量的低优先级任务继续执行,等到执行完后归还二值信号量,高优先级任务获取信号量后才能执行,这种现象称之为优先级翻转。

在这里插入图片描述
​ 以上图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L通过信号量机 制,共享资源。目前任务L占有资源,锁定了信号量,Task H运行后将被阻塞,直到Task L释放信号量后, Task H才能够退出阻塞状态继续运行。但是Task H在等待Task L释放信号量的过程中,中等优先级任务M抢 占了任务L,从而延迟了信号量的释放时间,导致Task H阻塞了更长时间,这种现象称为优先级倒置或反转。

优先级继承: 当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。

3.3 互斥量的使用场合

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。 比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信 息混杂在一起。

3.4 互斥量API

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

#define configUSE_MUTEXES 1
  • 创建

    互斥量是一种特殊的二进制信号量。 使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。 创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

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

    要注意的是,互斥量不能在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);
    
    

四、递归锁

4.1 死锁的概念

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

假设有2个互斥量M1M2,2个任务AB

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

4.2 自我死锁

假设这样的场景:

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

4.3 API函数

怎么解决这类问题?可以使用递归锁(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);

五、注意事项

使用互斥量的两个任务是相同优先级时的注意事项


文章是自己总结而记录,有些知识点没说明白的,请各位看官多多提意见,多多交流,欢迎大家留言
如果技术交流可以加以下群,方便沟通

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

^Lek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值