12、FreeRTOS信号量(semaphore)

文章目录

  • 一、信号量的特性
    • 1.1 使用场景
    • 1.2 什么是信号量
    • 1.3 信号量和队列的区别
    • 1.4 两种信号量的对比
  • 二、二值信号量/计数信号量
    • 2.1 什么是二值信号量
    • 2.2 什么是计数信号量
    • 2.2 (二值信号量/计数信号量) 相关API
  • 三、互斥量(mutex)
    • 3.2 什么是优先级翻转
    • 3.3 互斥量的使用场合
    • 3.4 互斥量API
  • 四、递归锁
    • 4.1 死锁的概念
    • 4.2 自我死锁
    • 4.3 API函数
  • 五、注意事项

一、信号量的特性

信号量分为以下几种

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

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);

五、注意事项

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


文章是自己总结而记录,有些知识点没说明白的,请各位看官多多提意见,多多交流,欢迎大家留言
如果技术交流可以加以下群,方便沟通
QQ群:370278903
点击链接加入群聊【蜡笔小芯的嵌入式交流群】
![])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/690314.html

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

相关文章

互斥锁概述

1. 同步互斥概述 在多任务操作系统中,同时运行的多个任务可能都需要访问/使用同一种资源多个任务之间有依赖关系,某个任务的运行依赖于另一个任务同步和互斥就是用于解决这两个问题的。 互斥:一个公共资源同一时刻只能被一个进程或线程使用…

谷粒商城实战(023 业务-订单模块-页面和幂等性等校验)

Java项目《谷粒商城》架构师级Java项目实战,对标阿里P6-P7,全网最强 总时长 104:45:00 共408P 此文章包含第270p-第p283的内容 简介 远程调用的服务已经注册到了注册中心 校验 幂等性校验 拿令牌 读令牌 删令牌 要实现 原子性 在一个事务中 添加令牌…

使用注解的方式进行配置RabbitMQ

引入依赖&#xff1a; <dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><scope>test</scope></dependency> 配置application.yml server:port: 8082 spring:rabbitmq…

堆排序 之实现最小的K个数

目录 1、方式一&#xff1a;通过自定义实现建堆和堆化操作 2、方式二&#xff1a;借助模块heapq实现 2.1、模块heapq的基本使用 2.2、使用heapq实现最小的k个数 3、堆在实际项目的应用 实现语言&#xff1a;Python 3.9 题目来源&#xff1a;牛客 分析&#xff1a; 要找…

Tiff文件解析和PackBits解压缩

实现了Tiff图片文件格式的解析&#xff0c;对Tiff文件中的PackBits压缩格式进行解压缩&#xff0c;对Tiff文件中每一个Frame转换成BufferedImage显示。 Java语言实现&#xff0c;Eclipse下开发&#xff0c;AWT显示图片。 public static TIFF Parse(final byte[] bytes) throw…

es6语法总结

【1】语法 &#xff08;1&#xff09;声明变量(let-var-const) 变量提升&#xff1a; 是JavaScript引擎在代码执行前将变量的声明部分提升到作用域顶部的行为。尽管变量的声明被提升了&#xff0c;变量的赋值&#xff08;即初始化&#xff09;仍然保留在原来的位置。因此&…

原型模式类图与代码

现要求实现一个能够自动生成求职简历的程序&#xff0c;简历的基本内容包括求职者的姓名、性别、年龄及工作经历。希望每份简历中的工作经历有所不同&#xff0c;并尽量减少程序中的重复代码。 采用原型模式(Prototype)来实现上述要求&#xff0c;得到如图 7.25 所示的类图。 原…

达梦数据库连接失败:Connect Failure! “Encryption module failed to load“

初次安装达梦数据库&#xff1a;V7 QT5.12.12版本开发调用数据库&#xff0c;最基础的原型调用&#xff1a; { //执行查询语句或则执行sql语句 QSqlDatabase qDb; QSqlDatabase db QSqlDatabase::addDatabase("QDM"); db.setHostName("192.168.2…

PX4 路径规划 航点规划 轨迹跟踪 Matlab/Simulink

一、无人机轨迹算法和仿真 matlab下载和安装 PX4实战之旅_超维空间科技的博客-CSDN博客 查了很多资料。无人机二次开发设计的目标是实现喷涂路径程序设计。决定使用simulink在ubuntu系统上结合jmavsim进行算法设计和仿真。 1.如果安装在ubuntu系统上面&#xff0c;MATLAB占…

基于单片机的直流电机检测与控制系统

摘要&#xff1a; 文章设计一款流电机控制系统&#xff0c;以 STC89C51 作为直流电机控制系统的主控制器&#xff0c;采用 LM293 做为驱动器实现 对直流电机的驱动&#xff0c;采用霍尔实现对直流电机速度的检测&#xff1b;本文对直流电机控制系统功能分析&#xff0c;选择确…

工业中常见大数据技术组件

随着大数据技术在各行各业的广泛应用&#xff0c;数据产品经理的角色也变得越来越重要。了解常见的大数据技术组件对于数据产品经理来说至关重要&#xff0c;因为这有助于他们更好地设计产品架构和功能模块&#xff0c;满足数据处理和分析的需求。在处理海量数据的产品中&#…

计算机视觉——OpenCV实现Lucas-Kanade 光流追踪

1.光流 光流法是计算机视觉中用于估计图像序列中物体运动的关键技术。它类似于观察夜空中的彗星&#xff0c;通过其在天空中的运动轨迹来追踪它的路径。在图像处理中&#xff0c;光流帮助我们理解像素点如何在连续的帧之间移动。 1.1 稀疏光流法 稀疏光流法关注于图像中的关…