FreeRTOS信号量(一)

目录

什么是信号量?

1.信号量简介

2.二值信号量

2.1二值信号量简介

1. 首先,创建时,二值信号量默认无效

2. 之后中断释放信号量

3.信号量获取成功

4、任务再次进入阻塞态

2.2 创建二值信号量

1、函数vSemaphoreCreateBinary ()

2、函数xSemaphoreCreateBinary()

3、函数xSemaphoreCreateBinaryStatic()

2.3 二值信号量的释放

1 、函数 xSemaphoreGive()

2、函数xSemaphoreGiveFromISR()

2.5  获取信号量

1、函数 xSemaphoreTake()

2、函数xSemaphoreTakeFromISR ()

总结:


什么是信号量?

        信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步FreeRTOS 中信号量又分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其应用场景不同,但有些应用场景是可以互换着使用的, 本章我们就来学习一下 FreeRTOS 的信号量

1.信号量简介

信号量常常有两种使用方式:

1. 用于控制对共享资源的访问

2. 用于任务同步

       对于第一种使用方式:一个很常见的例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。 假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车 了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车场满了。停车场满的时你可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用 信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。再看另外一个案例: 使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态: 使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。

       信号量用于控制共享资源访问的场景相当于一个上锁机制,代码只有获得了这个锁的钥匙 才能够执行。

       第二种方式,应用于任务同步场景,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候可以通过向任务 发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话会影响的中断的实时性。裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,中 断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获 取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间非常短。这个例子就是中断与任务之间使用信号量来完成同步,当然了,任务与任务之间也可以使用信号量来完成同步。

       FreeRTOS 中还有一些其他特殊类型的信号量,比如互斥信号量和递归互斥信号量,这些具体遇到的时候在讲解。有关信号量的知识在 FreeRTOS 的官网上都有详细的讲解,包括二值信号量、计数型信号量、互斥信号量和递归互斥信号量,我们下面要讲解的这些涉及到理论性的 知识都是翻译自 FreeRTOS 官方资料,感兴趣的可以去官网看原版的英文资料。

2.二值信号量

2.1二值信号量简介

        二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号 另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问,

       和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时 候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一 一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优 先级的任务就会解除阻塞状态。

      二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空 的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。

      在实际应用中通常会使用一个任务来处理 MCU 的某个外设,比如网络应用中,一般最简单的方法就是使用一个任务去轮询的查询 MCU  ETH(网络相关外设,如 STM32  的以太网 MAC)外设是否有数据,当有数据的时候就处理这个网络数据。这样使用轮询的方式是很浪费 CPU 资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候网络任 务就进入阻塞态,把 CPU 让给其他的任务,当有数据的时候网络任务才去执行。现在使用二值 信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入 阻塞态,而网络中断服务函数(大多数的网络外设都有中断功能,比如 STM32  MAC 专用 DMA 中断,通过中断可以判断是否接收到数据)通过释放信号量来通知任务以太网外设接收到了网络 数据,网络任务可以去提取处理了。网络任务只是在一直的获取二值信号量,它不会释放信号 量,而中断服务函数是一直在释放信号量,它不会获取信号量。在中断服务函数中发送信号量 可以使用函数 xSemaphoreGiveFromISR(),也可以使用任务通知功能来替代二值信号量,而且使 用任务通知的话速度更快,代码量更少,有关任务通知的内容后面会有专门的章节介绍。

       使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及 时的处理,这样做相当于推迟了中断处理过程。也可以使用队列来替代二值信号量,在外设事件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话 任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。下面几 个步骤演示了二值信号量的工作过程。

1. 首先,创建时,二值信号量默认无效

       在图中任务 Task 通过函数 xSemaphoreTake()获取信号量,但是此时二值信号量无 效,所以任务 Task 进入阻塞态。

2. 之后中断释放信号量

        此时中断发生了,在中断服务函数中通过函数 xSemaphoreGiveFromISR()释放信号量,因此信号量变为有效。

3.信号量获取成功

由于信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相 关的处理过程。

4、任务再次进入阻塞态

     由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数 xSemaphoreTake()获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务 将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR() 释放信号量。

2.2 创建二值信号量

    同队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数如下表:

1、函数vSemaphoreCreateBinary ()

      此函数是老版本 FreeRTOS  中的创建二值信号量函数,新版本已经不再使用了,新版本的 FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,这里还保留这个函数是为了兼容那些   老版 FreeRTOS   而做 的应用层代码 。此函数 是个宏  具体创建过程是由函  xQueueGenericCreate()来完成的,在文件 semphr.h 中有如下定义:

void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )

参数:

xSemaphore :保存创建成功的二值信号量句柄。

返回值:

NULL:           二值信号量创建失败。

其他值:          二值信号量创建成功。

2、函数xSemaphoreCreateBinary()

        此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS  的内存管 理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号 量使用函数xSemaphoreTake()是获取不到的, 此函数也是个宏, 具体创建过程是由函数 xQueueGenericCreate()来完成的,函数原型如下:

SemaphoreHandle_t    xSemaphoreCreateBinary( void )

参数:

返回值:

NULL:           二值信号量创建失败。

其他值:          创建成功的二值信号量的句柄。

3、函数xSemaphoreCreateBinaryStatic()

       此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的 RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数xQueueGenericCreateStatic() 来完成的,函数原型如下:

SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer )  

参数:

pxSemaphoreBuffer此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。

返回值:

NULL:           二值信号量创建失败。

其他值:          创建成功的二值信号量句柄。

2.3 二值信号量的释放

释放信号量的函数有两个,如下表:

       同队列一样,释放信号量也分为任务级和中断级。还有! 不管是二值信号量、计数型信号量还是互斥信号量,它们都使用上表中的函数释放信号量,递归互斥信号量有专用的释放函数。

1 、函数 xSemaphoreGive()

       此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信 号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:

BaseType_t    xSemaphoreGive( xSemaphore )

参数:

xSemaphore要释放的信号量句柄。

返回值:

pdPASS:                         释放信号量成功。

errQUEUE_FULL:      释放信号量失败。

我们再来看一下函数 xSemaphoreGive()的具体内容,此函数在文件 semphr.h 中有如下定义:

#define xSemaphoreGive( xSemaphore )                                      

xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),    

NULL,                                                                                    

semGIVE_BLOCK_TIME,                                                   

queueSEND_TO_BACK )

        可以看出任务级释放信号量就是向队列发送消息的过程,只是这里并没有发送具体的消息, 阻塞时间为 0( semGIVE_BLOCK_TIME  0),入队方式采用的后向入队,入队的时候队列结构体成员变量 uxMessagesWaiting 会加一,对于二值信号量通过判断 uxMessagesWaiting 就可以知道信号量是否有效了,当 uxMessagesWaiting  1 的话说明二值信号量有效,为 0 就无效。如果队列满的话就返回错误值 errQUEUE_FULL,提 示队列满,入队失败。

2、函数xSemaphoreGiveFromISR()

此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对  能用来在断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数 xQueueGiveFromISR() ,此函数原型如下:

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t   xSemaphore,

                                                                BaseType_t *         pxHigherPriorityTaskWoken)

参数:

xSemaphore    要释放的信号量句柄。

pxHigherPriorityTaskWoken :   标记退出此函数以后是否进行任务切换,这个变量的值由这

三个函数来设置的,用户不用进行设置,用户只需要提供一 个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退 出中断服务函数之前一定要进行一次任务切换。

返回值:

pdPASS:                         释放信号量成功。

errQUEUE_FULL:      释放信号量失败。

         在中断中释放信号量真正使用的是函数 xQueueGiveFromISR(),此函数和中断级通用入队函数 xQueueGenericSendFromISR() 极其类似! 

是针对信号量做了微        xSemaphoreGiveFromISR()不能用于在中断中释放互斥信号量,因为互斥信号量涉及到优先级继 承的问题,而中断不属于任务,没法处理中断优先级继承。大家可以参考第十三章分析函数 xQueueGenericSendFromISR()的过程来分析 xQueueGiveFromISR()

2.5  获取信号量

获取信号量也有两个函数,如下表:

       同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们都使用表中的函数获取信号量

1、函数 xSemaphoreTake()

此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信 号量的过程是由函数 xQueueGenericReceive ()来完成的,函数原型如下:

BaseType_t xSemaphoreTake(SemaphoreHandle_t      xSemaphore,   

                                                 TickType_t                      xBlockTime)

参数:

xSemaphore要获取的信号量句柄。 xBlockTime:    阻塞时间。

返回值:

pdTRUE:       获取信号量成功。

pdFALSE:     超时,获取信号量失败。

再来看一下函数 xSemaphoreTake ()的具体内容,此函数在文件 semphr.h 中有如下定义:

 #define xSemaphoreTake( xSemaphore, xBlockTime )                 

xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ),        

NULL,                                                  

( xBlockTime ),                                     

pdFALSE )

         取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。在讲解函数 xQueueGenericReceive()的时候说过如果队列为空并且阻塞时间为 0  的话就立即返回 errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0  的话就将任务 添加到延时列表中。如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数 据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting  减一,然后解除某些因为入队而阻塞的任务,最后返回 pdPASS 表示出对成功。互斥信号量涉及到优先级继承,处理方式不同,后面讲解互斥信号量的时候在详细的讲解。

2、函数xSemaphoreTakeFromISR ()

       此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量, 绝对不能使用此函数来获取互斥信号量  此函数是一个宏  真正执行的是函数 xQueueReceiveFromISR (),此函数原型如下:

BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t   xSemaphore,

                                                                 BaseType_t *             pxHigherPriorityTaskWoken)

参数:

xSemaphore   要获取的信号量句柄。

pxHigherPriorityTaskWoken :   标记退出此函数以后是否进行任务切换,这个变量的值由这

三个函数来设置的,用户不用进行设置,用户只需要提供一 个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退 出中断服务函数之前一定要进行一次任务切换。

返回值:

pdPASS:                 获取信号量成功。

pdFALSE:             获取信号量失败。

       在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一),然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数 pxHigherPriorityTaskWoken  设置为 pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!这个函数还是很简单的。

总结:

         FreeRTOS的二值信号量是我们学习信号量的基础,通过它,我们可以更好的了解信号量的知识,即信号量用于任务同步场景时的作用,也能够让我们了解信号量的基础操作,比如信号量创建、释放与获取函数,有了这些基础,可以让我们更加轻松的学习数值型信号量、互斥信号量。而且二值信号量防止中断执行过长的功能,也让FreeRTOS系统变得更加稳定,是实时系统的不可或缺的一部分,值得深入学习。

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

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

相关文章

51单片机-独立按键与数码管联动

独立键盘和矩阵键盘检测原理及实现 键盘的分类:编码键盘和非编码键盘 键盘上闭合键的识别由专用的硬件编码器实现,并产生键编码号或键值的称为编码键盘,如:计算机键盘。靠软件编程识别的称为非编码键盘;在单片机组成…

springboot课程答疑系统(代码+数据库+LW)

摘要 随着信息互联网信息的飞速发展,无纸化作业变成了一种趋势,针对这个问题开发一个专门适应师生交流形式的网站。本文介绍了课程答疑系统的开发全过程。通过分析企业对于课程答疑系统的需求,创建了一个计算机管理课程答疑系统的方案。文章…

解锁业务成功:大数据和 AI 如何协作以释放战略洞察

在当今这个数据主导的时代,大数据与AI的协同作用对于寻求竞争优势的组织而言愈发关键。大数据以其庞大的数据量、多样化的数据类型以及高速的数据生成能力,为AI算法提供了丰富的原材料,助力其挖掘出有价值的洞见,推动明智决策的制…

24.UE5枚举,怪物分类,龙卷风技能

2-26 枚举、怪物分类、龙旋风技能、掉落概率_哔哩哔哩_bilibili 目录 1.枚举 1.1枚举类型的创建 1.2 将枚举类型绑定到怪物蓝图上 1.3枚举类型的使用 1.3.1创建新的掉落物 1.3.2更改怪物掉落逻辑 2.龙卷风技能 2.1输入映射 2.2龙卷风发射物的创建 2.3龙卷风伤害逻辑…

故障字故障码 简单介绍

一、故障字 1.1故障字的概念 故障字(Fault Word)是一种常用的技术术语,主要应用在工业控制、嵌入式系统和通信领域,用于表示系统状态或故障信息。它是一个以位为单位的编码方式,每个位(bit)对应…

鸿蒙系统ubuntu开发环境搭建

在RISC-V等平台移植鸿蒙系统OpenHarmony,需要使用linux环境进行代码的编译,为兼顾日常办公需要,可采用WindowsUbuntu虚拟机的混合开发的环境,通过网络及文件夹共享,在主机和虚拟机之间共享文件数据。 工具准备&#x…

二叉树oj题解析

二叉树 二叉树的最近公共祖先什么是最近公共祖先?leetcode中求二叉树中最近公共祖先解题1.解题2. 根据二叉树创建字符串 二叉树的最近公共祖先 什么是最近公共祖先? 最近的公共祖先指的是这一棵树中两个节点中深度最大的且公共的祖先节点就是最近祖先节…

优先算法 —— 双指针系列 - 移动零

1. 移动零 题目链接: 283. 移动零 - 力扣(LeetCode)https://leetcode.cn/problems/move-zeroes/description/ 2. 算法原理 其实像移动零这种类型的题目都有一个名字叫做数组划分(数组分块),就是说先给一个…

C语言——数组逐元素操作练习

定义一个能容纳10个元素的整形数组a&#xff0c;从键盘读取9个整数存放到前9个数组元素中。 一. 从键盘读取一个整数n和位置p(0<p<8)&#xff0c;插入n到数组a中&#xff0c;插入位置&#xff1a;下标p。要求插入点及后续的数组元素都要后移动。 代码如下&#xff1a; …

【ArcGISPro】根据yaml构建原始Pro的conda环境

使用场景 我们不小心把原始arcgispro-py3的conda环境破坏了,我们就可以使用以下方法进行修复 查找文件 在arcgis目录下找到yaml文件 如果没找到请复制以下内容到新的yaml文件 channels: - esri - defaults dependencies: - anyio=4.2.0=py311haa95532_0 - appdirs=1.4.4=p…

解决IDEA报包不存在,但实际存在的问题

前言 最近在把一个亿老项目交割给同事&#xff0c;同事在导入项目运行时遇到IDEA报包不存在&#xff0c;但实际存在的问题&#xff0c;最终通过以下方式解决 现象 在IDEA里启动运行项目&#xff0c;报某个类有问题&#xff0c;引入的包不存在。 点击这个引入的包&#xff0c;可…

使用uniapp编写APP的文件上传

使用uniapp插件文件选择、文件上传组件&#xff08;图片&#xff0c;视频&#xff0c;文件等&#xff09; - DCloud 插件市场 实用效果&#xff1a; 缺陷是只能一个一个单独上传

图算法 | 3、图分析与数据科学

图分析(Graph Analytics)在本质上是对图数据的处理与分析&#xff0c;其过程可以概括为图计算。 而图计算的范畴不仅包含数据的计算或分析&#xff0c;还包含元数据管理、模式管理、数据建模、数据清洗、转换、加载、治理、图分析与计算等一系列操作。 或许我们用大数据生命周…

66 mysql 的 表自增长锁

前言 mysql 的表锁之 AUTO_INC, 是我们自增长的时候做并发控制的锁 主要是用于 自增长生成新的 id 的时候的控制 在前面的文档中, 我们又看到 mysql 这边自增长的处理的相关的大概脉络 但是 对于一些 并发控制的细节, 我们当时 应该是直接忽略掉了 我们这里就来看一下…

Elasticsearch向量搜索:从语义搜索到图搜图只有一步之遥

续 上集说到语义搜索&#xff0c;这集接着玩一下图搜图&#xff0c;这种场景在电商中很常见——拍照搜商品。图搜图实现非常类似语义搜索&#xff0c;代码逻辑结构都很类似… 开搞 还是老地方modelscope找个Vision Transformer模型&#xff0c;这里选用vit-base-patch16-224…

HCIA笔记3--TCP-UDP-交换机工作原理

1. tcp协议 可靠的连接 1.1 报文格式 1.2 三次握手 1.3 四次挥手 为什么TIME_WAIT需要2MSL的等待时间&#xff1f; &#xff08;a&#xff09; 为了实现可靠的关闭 &#xff08;b&#xff09;为了让过期的报文在网络上消失 对于(a), 假设host发给server的last ack丢了。 ser…

docker搭建私有仓库,实现镜像的推送和拉取

1.拉取docker仓库镜像 docker pull registry 2.启动registry容器 docker run -d registry 3.查看当前仓库中存在的镜像&#xff08;一&#xff09; curl -XGET http://192.168.111.162: 5000/v2/_catalog 192.168.111.162 部署docker仓库宿主机的ip 5000 部署docker仓库映射到宿…

提取图片高频信息

提取图片高频信息 示例-输入&#xff1a; 示例-输出&#xff1a; 代码实现&#xff1a; import cv2 import numpy as npdef edge_calc(image):src cv2.GaussianBlur(image, (3, 3), 0)ddepth cv2.CV_16Sgray cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)grad_x cv2.Scharr(g…

greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用

简略图 greater<>()(a, b) a > b 返回true&#xff0c;反之返回false less<>()(a, b) a < b 返回true&#xff0c;反之返回false 在cmp中使用&#xff08;正着理解&#xff09; 规则返回true时a在前&#xff0c;反之b在前 在priority_queue中使用 &#xff…

助力企业解决降本增效的难题,Altair HPCWorks新功能创新升级

“IO一旦出现问题&#xff0c;整个计算效率会降低50%以上。License、昂贵的硬件、紧张的项目周期都会因此而卡顿&#xff0c;而HPCWorks可以帮助包括像英伟达这样的顶尖客户随时了解研发资源的实时情况和实时瓶颈。 —— Altair 企业计算部技术总监 王轶华 在2024年 Altair 技…