freertos源码分析DAY3(二值/计数信号量)

 

目录

1. 二值信号量

1.1. 二值信号量的创建

1.2 任务中二值信号操作函数

1.2.1 二值信号量的释放

1.2.2 等待二值信号量资源函数

1.3 中断中二值信号量操作函数

1.3.1 中断中释放二值信号量

1.3.2 中断中接收信号量

2.  计数信号量

2.1 计数信号量的创建


 
         信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资
源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。( 在多任务系统中,各任务之间需要同步或使用互斥实现来临界资源的保护,信号量可以为用户提供这方面功能的支持
     
          抽象的来说可以将信号量看为一个标志位,它是一个非负数整数,所有获取它的任务都会将该整数-1( 对应消息队列的出队情况 )。当其减到0时,所有获取它的任务都会被阻塞( 对应消息队列中成员出队,但队列中已无队列成员可以出队的情况 )。而当有任务释放它时,就会将该整数+1( 对应消息队列入队情况,但和标准的消息队列入队还是有些区别( 信号量在队列满的其情况下,入队(释放信号量),也不会将当前任务阻塞 )。
       
        这节介绍的信号量的作用主要是用于同步,所以这里将其称为 “同步信号量” ,在freertos中功能偏向于同步的信号量主要有“二值信号量”以及“计数信号量”,这里首先介绍二值信号量;

1. 二值信号量

        二值信号量可以看作是一个二值的标志位,它主要被用于进行任务之间的同步。

1.1. 二值信号量的创建

        二值信号量的创建,具体代码如下所示:(此代码定义在semphc.h文件中

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

        可以看到它其实就是调用的消息队列创建函数,只不过这里创建了一个特殊的消息队列,它的队长为1,单个空间大小为0(也就是无需队列空间),而最后一个参数并无实质性作用,只是作为标记区分我创建的是上面类型的消息队列;(具体创建的源码过程在上节“消息队列”有详细分析,这里不再做分析

        二值信号量创建出来具体如下所示:(可以看到其并无队列空间

        

信号量被创建后,uxMessagesWaiting变量为默认0(信号量创建后,默认情况下是无可用资源的),若想要其在默认情况下有可用资源则需调用如下函数:(它同样是在头文件semphc中定义

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define vSemaphoreCreateBinary( xSemaphore )																							\{																																	\( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );	\if( ( xSemaphore ) != NULL )																									\{																																\( void ) xSemaphoreGive( ( xSemaphore ) );																					\}																														\}
#endif

 可以看到,这个API在信号量被创建后,立即释放了一个资源。

1.2 任务中二值信号操作函数

1.2.1 二值信号量的释放

二值信号量释放函数具体如下所示:

#define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

可以看到它实际调用的是 “消息队列通用发送” 函数xQueueGenericSend这里首先看其参数:

1、其将信号量队列传入其中,但无要发送到队列中的对象(无消息发送到队列);

2、阻塞时间为0(若信号量队列已有资源,此时有任务再次释放信号量则这个任务不会被阻塞);

3、消息插入到队列的方式(这里其实哪种方式都行,因为根本没有消息入队,信号量只是用了队列中的消息个数的统计变量作为其有无资源的指示项);

这里只对信号量的释放过程做一下简单的说明(通用发送函数发送消息的详细过程在 “消息队列” 那里已经分析过了),所以这里只做简单分析:

1、进入通用发送消息函数后,其首先会通过统计消息个数变量判断,消息队列是否满;

   (1)满的话就直接退出函数,不阻塞任务;(因为信号量给的阻塞值是0

   (2)不满的话就继续进行如下操作:

2、若此时信号量队列无资源(队列未满情况),以FIFO的方式将消息插入到队列中,由于信号量的消息队列大小为0,所以其单个空间大小为0,而消息插入到队列后入队指针会向后移动“单个空间大小的位置”(这里的入队指针就没移动位置,因为单个空间大小为0),但记录消息个数的变量uxMessagesWaiting会+1,表示当前信号量有资源了,可以被拿走;

3、信号量资源被释放后,其会查找当前信号量消息等待接收队列中是否有任务,若有任务则将它们恢复就绪(这就实现了任务同步,只有有资源时,才会将相应的任务唤醒并执行);

4、信号量完成资源释放操作后,就会退出当前信号量释放函数;(之后等待任务调度到等待这个信号量的任务,此时就使用信号量完成了一次任务间的同步的操作

        

1.2.2 等待二值信号量资源函数

  等待二值信号量函数具体如下所示:

#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )

不难发现等待二值信号量函数的底层调用的是通用接收消息函数,同样这里首先看其参数:

1、传入前面创建了的信号量队列;

2、无变量接收此消息;

3、阻塞时间可通过宏自行设定(为了实现真正的任务同步,一般设置为最大0xFFFFFFFFUL,表永久阻塞);

4、 false:要做具体的队列操作,而不是只读消息队列中的消息;(队列操作会让uxMessagesWaiting变量--,所以这里必须要进行队列操作,否则不能做将信号量取走的操作

信号量接收等待函数,这里同样只做简略介绍,因为队列通用发送消息函数在前面已经向详细分析过了:

1、首先判断消息队列是否有消息(是否有资源):

        (1)无资源,阻塞当前任务,直到等待的信号量被释放,当前任务才会被就绪;

        (2)有资源则做如下操作:

2、 此时信号量队列是有资源的,进行消息出队操作(出队指针首先会向后移动uxItemSize大小的位置,之后其会从其指向的队列空间中的拷贝消息到相应的消息队列空间,而uxItemSize在信号量中是0,所以这里指针并没有移动,也并没有消息出队);

3、进行出队操作后,会将uxMessagesWaiting-1,表示当前信号量队列中的消息,被任务拿走了一个;

4、之后其会检查消息队列的发送延时列表,看有没有要发送消息的任务(由于信号量释放函数中,设置的延时时间是0,所以信号量的发送延时列表中并不会有任务);

1.3 中断中二值信号量操作函数

1.3.1 中断中释放二值信号量

中断中释放二值信号量的API具体如下所示:

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

可以看到这个API的底层调用的并不是 “消息队列在中断中发送消息函数” ,它用了一个新的底层实现xQueueGiveFromISR,具体如下:(此函数和中断中发送消息函数的区别是,这个函数没有入队操作

BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, BaseType_t * const pxHigherPriorityTaskWoken )
{BaseType_t xReturn;UBaseType_t uxSavedInterruptStatus;Queue_t * const pxQueue = ( Queue_t * ) xQueue;uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;if( uxMessagesWaiting < pxQueue->uxLength ){const int8_t cTxLock = pxQueue->cTxLock;pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;if( cTxLock == queueUNLOCKED ){#if ( configUSE_QUEUE_SETS == 1 ){//xxxxxxxxxxxxxxxxx}#else{if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}}}}#endif }else{pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else{xReturn = errQUEUE_FULL;}}portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}

参数:xQueue(需要释放的信号量队列

           pxHigherPriorityTaskWoken (任务切换指示变量


xQueueGiveFromISR函数具体做了如下操作:(以下操作都是在临界段中进行的

1、首先其会判断当前信号量队列是否已满;

        (1)满就直接退出函数,返回队列满错误;

        (2)不满就进行如下操作:(信号量队列还能接收资源

2、将当前队列消息个数+1后,判断当前信号量队列是否被锁;(信号量队列被上锁的情况,就只有任务等待信号量被挂载到接受等待阻塞链表时才会将其上锁,发送情况下是不可能被上锁的,因为信号量的发送任务是不会阻塞任务的

         (1)被上锁:将上锁标志+1,之后中断结束后,会回到通用消息接收函数中,将队列解锁,在解锁过程就会将中断中错过恢复的任务,都恢复就绪;

         (2)没被上锁:检查消息接收阻塞队列中是否有任务,若有则将其恢复,之后再判断当前恢复任务的优先级是否大于当前运行任务,若是则将指导任务切换调度的变量置1;

3、退出临界段并恢复到进入中断前的basepri寄存器配置;(退出此函数后,中断中会通过任务切换指导变量去指导是否需要任务切换

此函数的具体用法如下所示:

SemaphoreHandle_t   xSem_Binary; //定义一个信号量xSem_Binary = xSemaphoreCreateBinary(); //创建二值信号量//某某中断中
void xxx_Handler(void)
{BaseType_t task_switch = false;xSemaphoreGiveFromISR( xSem_Binary, &task_switch)if(task_switch == ture){portYIELD_FROM_ISR();}
}

总结:

        此函数和xQueueGenericSendFromISR唯一的区别就是,xQueueGiveFromISR中没有进行入队操作,它将入队操作直接换为了对uxMessagesWaiting的++;

1.3.2 中断中接收信号量

中断中等待二值信号量的API具体如下所示:(底层实际调用的是xQueueReceiveFromISR中断中接收消息函数

#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )

先看其传入的参数:

xSemaphore :传入当前要接收资源的信号量队列;

NULL:没有要接收消息的对象;

pxHigherPriorityTaskWoken :任务切换标志指示;(这里指示的是发送延时列表中恢复的任务,若发送延时列表中恢复的任务优先级>当前运行任务,则这位置1,表现在需要进行一次任务切换,但其实这个参数永远不会变为1,因为信号量发送任务不会阻塞任务

中断中接收信号量这个函数用的很少,看了源码后也感觉这个函数没什么用处,因为信号量的所有发送API压根就不会阻塞信号量发送任务(发送不了就下个周期再发),所以这个函数后面的指示参数压根就不会为1,这个函数有点奇葩。

2.  计数信号量

        计数信号量可以被认为长度大于 1 的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。
        在实际让使用中,常将计数信号量用于事件计数( 记录事件发生次数 )与资源管理。
计数信号量用于事件计数时:
        当某个事件发生时,任务或者中断将释放一个信号量( 信号量计数值+1 ),当处理被事件时( 一般在任务中处理 ),处理任务会取走该信号量( 信号量计数值- 1 ),信号量的计数值则表示还有多少个事件没被处理。

        

计数信号量用于资源管理时:

        信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。(计数值大小,为系统可访问资源的次数)

2.1 计数信号量的创建

        创建计数信号量的API具体如下所示:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif

        可以看到,创建计数信号量的底层函数,并不是调用的消息队列创建函数,而是一个新的实现xQueueCreateCountingSemaphore(本质还是调用了xQueueGenericCreate,只是加了几个安全性操作),具体如下:

#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ){QueueHandle_t xHandle;xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );if( xHandle != NULL ){( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;}return xHandle;}#endif

参数:

uxMaxCount:   要创建计数信号量队列的长度;

uxInitialCount :计数信号量队列初始化的长度;


xQueueCreateCountingSemaphore具体做了一下操作:

1、首先此函数调用了xQueueGenericCreate通用消息队列创建函数,并将创建好的队列句柄返回给了临时变量xHandle;

2、创建好的队列,将其队列中的消息个数初始化为传入形参的大小,最后返回队列句柄;


总结:

        此函数底层就是调用了消息队列创建函数,与二值信号量创建不同的一点就是,它的队列长度>=1,且被初始了消息个数;

        计数信号量的其他操作均和二值信号量相同(信号量的释放/接受),只不过它的队列长度可能要长一些。所以在消耗计数信号量时,计数信号量就不会被一次性消耗完毕,这个特性也造就了计数信号量可以被作为 “资源管理工具/用于事件计数” 来使用。

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

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

相关文章

Backpropagation

在使用gradient descent的时候&#xff0c;神经网络参数θ有很多参数&#xff08;w&#xff0c;b&#xff09;。那gradient就会是一个有成千上万维的vector。所以&#xff0c;Backpropagation在做的事情就是有效的把它&#xff08;上图左侧的向量&#xff09;计算出来。 复习一…

C++初学者指南-5.标准库(第一部分)--标准算法介绍

C初学者指南-5.标准库(第一部分)–标准算法介绍 文章目录 C初学者指南-5.标准库(第一部分)--标准算法介绍C的标准算法是&#xff1a;第一个示例组织输入范围自定义可调用参数并行执行(C17)迭代器和范围的类别错误消息命名空间std::ranges中的算法 (C20)算法参数图标相关内容 C的…

TS真的比JS更好吗?

前言 在讨论TypeScript&#xff08;TS&#xff09;是否比JavaScript&#xff08;JS&#xff09;更好时&#xff0c;我们需要明确“更好”这一概念的上下文和衡量标准。TypeScript和JavaScript在多个方面有着明显的区别&#xff0c;但它们并不是简单的“好”与“不好”的关系&a…

国产数据库VastBase与C/C++程序适配

背景 2022年底的项目&#xff0c;记录一下。 某项目后台使用C程序开发&#xff0c;使用的是OCI连接Oracle数据库。现需要做去O国产化适配改造。 本文聊聊C/C应用程序如何使用VastBase替换Oracle。 编译适配 开发包获取 从VastBase官方或接口人处获取OCI开发包&#xff0c;包含…

线程池笔记

笔记梳理 前言.PHONYC标准库头文件C/C通用或C特有头文件mkdirc_str()snprintfvsnprintfumaskopen函数可变参数列表va_startva_endfunctionalstatic_castpthread_cond_init_threads.emplace_backstd::bindstd::placeholdersThreadPool(const ThreadPool<T> &tp) dele…

博客前端项目学习day01

这里写自定义目录标题 登录创建项目配置环境变量&#xff0c;方便使用登录页面验证码登陆表单 在VScode上写前端&#xff0c;采用vue3。 登录 创建项目 检查node版本 node -v 创建一个新的项目 npm init vitelatest blog-front-admin 中间会弹出询问是否要安装包&#xff0c…

前端Vue组件化实践:打造自定义等宽tabs标签组件

在前端开发的世界里&#xff0c;随着业务复杂度的提升和需求的多样化&#xff0c;传统的整体式开发方式已经难以满足快速迭代和高效维护的需求。组件化开发作为一种重要的解决方案&#xff0c;正逐渐受到广大开发者的青睐。本文将结合Vue框架&#xff0c;探讨如何通过组件化开发…

标签-镜像拉取策略-重启策略-pod优雅终止-pod中容器资源限制-容器类型-容器守护进程-日志排错-容器命令exec-cp

一.标签 1.概述&#xff1a; 标签是附加到kubernets对象&#xff08;比如pod&#xff09;上的键值对&#xff0c;标签可以在创建时附加到对象&#xff0c;随后也可以随时添加修改&#xff1b;标签不支持唯一性。 在k8s中大多数资源都是通过标签进行关联的&#xff08;如pod与s…

DP(3) | 0-1背包 | Java | LeetCode 1049, 494, 474 做题总结

1049. 最后一块石头的重量 II 和 LC 416.分割等和子集 类似 思路&#xff08;我没有思路&#xff09;&#xff1a; 两块石头相撞&#xff0c;这里没有想到的一个点是&#xff0c;相撞的两个石头要几乎相似 以示例1为例&#xff0c;stones [2,7,4,1,8,1]&#xff0c;如果从左到…

【数组、特殊矩阵的压缩存储】

目录 一、数组1.1、一维数组1.1.1 、一维数组的定义方式1.1.2、一维数组的数组名 1.2、二维数组1.2.1、二维数组的定义方式1.2.2、二维数组的数组名 二、对称矩阵的压缩存储三、三角矩阵的压缩存储四、三对角矩阵的压缩存储五、稀疏矩阵的压缩存储 一、数组 概述&#xff1a;数…

HyperSD - 会画草图就能玩AI绘画,AI一键手绘,实时同步 本地一键整合包下载

字节跳动的Lightning团队发布的新图像模型蒸馏算法Hyper-SD&#xff0c;是一项在图像处理和机器学习领域的重要进展。这项技术通过创新的方法提升了模型在不同推理步骤下的性能&#xff0c;同时保持了模型大小的精简。 基于这个算法模型&#xff0c;一个很实用的功能出现了&am…

绝区零 双闪 双闪-三轴 工具

绝区零 双闪 双闪-三轴 工具 0. 演示视频 绝区零&#xff1a;≈100%的极致双闪和双闪-三轴 绝区零&#xff1a;手残党也能打双闪-三轴 1. 基本信息 作者: GMCY系列: 工具系列仓库: GitHub | Gitee话题(GitHub): Tools \ ZenlessZoneZero创建时间: 2024/07/14 2. 声明 !!! 使…

【Linux】重定向 | 为什么说”一切皆文件?“

目录 前言 1.文件描述符分配规则 2.dup2 重定向接口 3.重定向 3.1>输出重定向 3.2>>追加重定向 3.3<输入重定向 3.4 shell 模拟实现< > 3.5 理解> 4. 理解“Linux 下一切皆文件” 前言 问&#xff1a;fd 为什么默认从 3 开始&#xff0c;而不是…

LeetCode热题100刷题15:200. 岛屿数量、所有可达路径、118. 杨辉三角、287. 寻找重复数、84. 柱状图中最大的矩形

200. 岛屿数量 借助DFS寻找整个图的连通分量数&#xff0c;DFS将一个连通分量中的节点标记为visited&#xff0c;res记录连通分量数&#xff0c;最后返回res class Solution { public:const int dir[4][2] {1,0,0,1,-1,0,0,-1};void dfs(vector<vector<char>>&a…

代码随想录——不同路径Ⅱ(Leetcode 63)

题目链接 动态规划 class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m obstacleGrid.length;int n obstacleGrid[0].length;int[][] dp new int[m][n];// 遇到障碍则从(0,0)到达for(int i 0; i < m && obstacleGrid[i][0] …

前端挑战:Tkinter布局与设计【三种布局】

前端挑战:Tkinter布局与设计【三种布局】 文章目录 前端挑战:Tkinter布局与设计【三种布局】前言Frame 窗口组件代码效果Tkinter的布局grid 网格布局效果展示:代码讲解pack 布局基本使用左右布局place 布局代码预览前言 作为一个前端开发,习惯性的用HTML去解决客户端的问题…

2024.7.14周报

目录 摘要 ABSTRACT 一、文献阅读 一、题目 二、摘要 三、文献解读 一、Introduction 二、KINN框架 三、主要结果 四、Conclusion 二、KAN 一、KAN与MLP区别 二、KAN网络解析 三、激活函数参数化&#xff08;B-splines&#xff09; 三、网络架构代码 摘要 本周…

Kafka基础入门篇(深度好文)

Kafka简介 Kafka 是一个高吞吐量的分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;&#xff0c;主要应用与大数据实时处理领域。 1. 以时间复杂度为O(1)的方式提供消息持久化能力。 2. 高吞吐率。&#xff08;Kafka 的吞吐量是MySQL 吞吐量的30…

输入法发展历史

输入法的发展历史&#xff0c;尤其是中文输入法&#xff0c;是一个相当丰富和多元的话题&#xff0c;它反映了技术进步、用户需求变化以及计算机和移动设备界面设计的演进。以下是一个概览&#xff1a; 早期阶段 1970s&#xff1a;朱邦复在1976年发明了仓颉输入法&#xff0c;…

python:绘制一元四次函数的曲线

编写 test_x4_x2_4x.py 如下 # -*- coding: utf-8 -*- """ 绘制函数 y x^4x^24x-3 在 -2<x<2 的曲线 """ import numpy as np from matplotlib import pyplot as plt# 用于正常显示中文标题&#xff0c;负号 plt.rcParams[font.sans-s…