FreeRTOS 快速入门(五)之信号量
目录
- 一、信号量的特性
- 1、信号量跟队列的对比
- 2、两种信号量的对比
- 二、信号量
- 1、二值信号量
- 1.1 二值信号量用于同步
- 1.2 二值信号量用于互斥
- 2、计数信号量
- 三、信号量函数
- 1、创建
- 2、删除
- 3、give/take
一、信号量的特性
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,其实信号量主要的功能就是实现任务之间的同步与互斥,实现的方式主要就是依靠队列(信号量是特殊的队列)的任务阻塞机制。
1、信号量跟队列的对比
差异列表如下:
| 队列 | 信号量 |
|---|---|
| 可以容纳多个数据,创建队列时有两部分内存: 队列结构体、存储数据的空间 | 只有计数值,无法容纳其他数据。 创建信号量时,只需要分配信号量结构体 |
| 生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值已经达到最大时返回失败 |
| 消费者:没有数据时可以阻塞 | 消费者:没有资源时可以阻塞 |
由上面的表格可以看出:信号量相比队列更节省空间,因为实现同步与互斥不需要传递数据,所以信号量没有队列后面的环形存储区,信号量主要就是依靠计数值 uxMessagesWaiting(在队列中表示队列现有消息个数,在信号量中表示有效信号量个数)。
其实,创建信号量就对应创建特殊队列,获取信号量就对应队列出队,释放信号量就对应队列入队,学好了队列就基本学好了信号量。
2、两种信号量的对比
信号量的计数值都有限制:限定了最大值。如果最大值被限定为 1,那么它就是二值信号量;如果最大值不是 1,它就是计数型信号量。
| 二值信号量 | 计算型信号量 |
|---|---|
| 被创建时初始值为 0 | 被创建时初始值可以设定 |
| 其他操作是一样的 | 其他操作是一样的 |
二、信号量
首先来看一下信号的种类,相关的信号量函数放在第三章。
1、二值信号量
所谓二值信号量其实就是一个队列长度为1,没有数据存储器的队列,而二值则表示计数值uxMessagesWaiting只有0和1两种状态(就是队列空与队列满两种情况),uxMessagesWaiting在队列中表示队列中现有消息数量,而在信号量中则表示信号量的数量。
uxMessagesWaiting为 0 表示:信号量资源被获取了.uxMessagesWaiting为 1 表示:信号量资源被释放了
把这种只有 0 和 1 两种情况的信号量称之为二值信号量。
由于二值信号量就是特殊的队列,其实它的运转机制就是利用了队列的阻塞机,从而达到实现任务之间的同步与互斥(有优先级反转的缺陷)。
1.1 二值信号量用于同步
在多任务系统中,经常会使用二值信号量来实现任务之间或者任务与中断之间的同步,比如,某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位,则任务在等待的过程也会消耗 CPU 的资源,如下所示:
// 任务一
void Task1Function(void *param)
{volatile int i = 0;while (1) {for (i = 0; i < 10000000; ++i) {sumj++;}flagCalcEnd = 1;vTaskDelete(NULL);}
}// 任务二
void Task2Function(void *param)
{while (1) {if (flagCalcEnd)printf("sum = %d\r\n", sum);}
}
总体工作流程如下:任务二等待任务一(等待 flagCalcEnd 置一),计算完(sum 的值累加一百万次)。然后进行数据处理(这里简单打印 sum 的值)。
上面的代码看似没问题,其实存在有两个问题:
- 使用了全局变量
flagCalcEnd,(如果同时读写flagCalcEnd则会出问题)。 - 任务二在等待任务一计算完 sum 的值的过程中,任务二也会参与任务调度消耗 CPU 资源(假设只有这两个任务,优先级相同,且支持时间片轮转,则在任务一在计算 sum 值的过程中,任务一与任务二轮流执行相同时间片,只不过任务二就一直判断
flagCalcEnd的值是否为1,相当于就是浪费 CPU 的资源)
所以二值信号量就可以解决这个问题,在任务一计算 sum 的值的过程中,任务二应该进入阻塞态让出 CPU 的使用权,在任务二阻塞期间任务一就可以独占 CPU 全速计算 sum 的值,代码如下所示:
// 任务一
void Task1Function(void *param)
{volatile int i = 0;while (1) {for (i = 0; i < 10000000; ++i) {sumj++;}// 等待 sum 计算完成释放信号量,信号量计数值 uxMessagesWaiting 加 1xSemaphoreGive(xSemcalc); vTaskDelete(NULL);}
}// 任务二
void Task2Function(void *param)
{while (1) {flagCalcEnd = 0;// 若 sum 未计算完成,则获取信号量失败,任务会进入阻塞状态,其他任务得以调度// 若 sum 计算完成(信号量为 1),则任务被唤醒 sum 得以打印xSemaphoreTake(xSemcalc, portMAX_DELAY);flagCalcEnd = 1;printf("sum = %d\r\n", sum);}
}
1.2 二值信号量用于互斥
我们在串口接收中,我们并不知道什么时候有数据发送过来(等数据过来标记一次),还有一个处理串口接收到的数据,在任务系统中不可能时时刻刻去判断是否有串口有数据过来(判断标志位),所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来。
二值信号量一般不用于任务之间的互斥(任务之间互斥的访问一个临界资源,同一时间只能一个任务可以使用),因为它有优先级反转的缺点,解决互斥的方式就是使用互斥信号量(具有优先级继承的机制能减少优先级反转的影响),关于优先级反转,优先级继承等下一讲讲互斥量的时候在讲。
2、计数信号量
计数值信号量也与二值信号量一样也是特殊的队列,二值信号量是长度为 1 的队列,而计数值信号量是长度大于 0 的队列,他们本质的区别就是应用场景不同:二值信号量常用于同步,计数值信号量常用于事件计数、资源管理,其实如果限定计数值信号量计数值最大值只能为 1 则就等同于二值信号量。
计数值信号量的应用场景:
- 事件计数
在这种场合下,每次事件发生后,在事件处理函数中释放计数型信号量(计数型信号量的资源数加 1),其他等待事件发生的任务获取计数型信号量(计数型信号量的资源数减 1),这种场景下,计数型信号量的资源数一般在创建时设置为 0。 - 资源管理
在这种场合下,计数型信号量的资源数代表着共享资源的可用数量,一个任务想要访问共享资源,就必须先获取这个共享资源的计数型信号量,之后在成功获取了计数型信号量之后,才可以对这个共享资源进行访问操作,当然,在使用完共享资源后也要释放这个共享资源的计数型信号量。在这种场合下,计数型信号量的资源数一般在创建时设置为受其管理的共享资源的最大可用数量。
三、信号量函数
使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。
1、创建
使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。对于二值信号量、计数型信号量,它们的创建函数不一样:
| 二值信号量 | 计数型信号量 | |
|---|---|---|
| 动态创建 | xSemaphoreCreateBinary计数值初始值为 0 | xSemaphoreCreateCounting |
vSemaphoreCreateBinary(过时了)计数值初始值为 1 | ||
| 静态创建 | xSemaphoreCreateBinaryStatic | xSemaphoreCreateCountingStatic |
创建二值信号量的函数原型如下:
/* 创建一个二值信号量,返回它的句柄。* 此函数内部会分配信号量结构体* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );/* 创建一个二值信号量,返回它的句柄。* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t*pxSemaphoreBuffer );
创建计数型信号量的函数原型如下:
/* 创建一个计数型信号量,返回它的句柄。* 此函数内部会分配信号量结构体* uxMaxCount: 最大计数值* uxInitialCount: 初始计数值* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_tuxInitialCount);/* 创建一个计数型信号量,返回它的句柄。* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针* uxMaxCount: 最大计数值* uxInitialCount: 初始计数值* pxSemaphoreBuffer: StaticSemaphore_t结构体指针* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,UBaseType_t uxInitialCount,StaticSemaphore_t*pxSemaphoreBuffer );
2、删除
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。vSemaphoreDelete 可以用来删除二值信号量、计数型信号量,函数原型如下:
/** xSemaphore: 信号量句柄,你要删除哪个信号量*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
3、give/take
二值信号量、计数型信号量的 give、take 操作函数是一样的。这些函数也分为 2 个版本:给任务使用,给 ISR 使用。列表如下:
| 在任务中使用 | 在 ISR 中使用 | |
|---|---|---|
| give | xSemaphoreGive | xSemaphoreGiveFromISR |
| take | xSemaphoreTake | xSemaphoreTakeFromISR |
xSemaphoreGive的函数原型如下:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
| 参数 | 说明 |
|---|---|
| xSemaphore | 信号量句柄,释放哪个信号量 |
| 返回值 | pdTRUE 表示成功, 如果二值信号量的计数值已经是 1,再次调用此函数则返回失败; 如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败 |
xSemaphoreGiveFromISR的函数原型如下:
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
| 参数 | 说明 |
|---|---|
| xSemaphore | 信号量句柄,释放哪个信号量 |
| pxHigherPriorityTaskWoken | 如果释放信号量导致更高优先级的任务变为了就绪态, 则 *pxHigherPriorityTaskWoken = pdTRUE |
| 返回值 | pdTRUE 表示成功, 如果二值信号量的计数值已经是 1,再次调用此函数则返回失 败; 如果计数型信号量的计数值已经是最大值,再次调用此函数则返 回失败 |
xSemaphoreTake的函数原型如下:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
| 参数 | 说明 |
|---|---|
| xSemaphore | 信号量句柄,获取哪个信号量 |
| xTicksToWait | 如果无法马上获得信号量,阻塞一会: 0:不阻塞,马上返回 portMAX_DELAY: 一直阻塞直到成功 其他值: 阻塞的 Tick 个数,可以使用 pdMS_TO_TICKS() 来指定阻塞时间为若干 ms |
| 返回值 | pdTRUE 表示成功 |
xSemaphoreTakeFromISR的函数原型如下:
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
| 参数 | 说明 |
|---|---|
| xSemaphore | 信号量句柄,获取哪个信号量 |
| pxHigherPriorityTaskWoken | 如果获取信号量导致更高优先级的任务变为了就绪态, 则 *pxHigherPriorityTaskWoken = pdTRUE |
| 返回值 | pdTRUE 表示成功 |
相关文章:
FreeRTOS 快速入门(五)之信号量
目录 一、信号量的特性1、信号量跟队列的对比2、两种信号量的对比 二、信号量1、二值信号量1.1 二值信号量用于同步1.2 二值信号量用于互斥 2、计数信号量 三、信号量函数1、创建2、删除3、give/take 一、信号量的特性 信号量(Semaphore)是一种实现任务…...
centos 服务器之间实现免密登录
为了在CentOS服务器之间实现免密登录,你需要使用SSH的公钥认证机制 比如两台centos系统的服务器A 和服务器B 首先我们实现从A服务器可以免密登录到服务器B上 首先生成公钥和秘钥: ssh-keygen -t rsa 生成了公钥和秘钥之后: ssh-copy-id r…...
RabbitMq实现延迟队列功能
1、rabbitmq服务端打开延迟插件 (超过 4294967295毫秒 ≈ 1193 小时 ≈ 49.7 天 这个时间会立即触发) 注意:只有RabbitMQ 3.6.x以上才支持 在下载好之后,解压得到.ez结尾的插件包,将其复制到RabbitMQ安装目录下的plug…...
redis内存淘汰策略
1. redis内存淘汰策略 日常常用:allkeys-lru:在键空间中移除最近最少使用的key。1.1 为什么需要使用redis内存淘汰策略? 因为我们服务器中的内存是有限的,不会无限多,所以需要对一些不常用的key进行内存清理.1.2 redis内存淘汰策略有哪些? redis默认…...
实时洞察应用健康:使用Spring Boot集成Prometheus和Grafana
1. 添加Prometheus和Actuator依赖 在pom.xml中添加Spring Boot Actuator和Micrometer Prometheus依赖: <dependencies> <!--监控功能Actuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring…...
生信圆桌x生信豆芽菜:生物信息学新手的学习与成长平台
生信豆芽菜是一个专门为生物信息学初学者创建的学习与交流平台,致力于帮助新手们快速入门并掌握生信分析的基础知识与技能。随着生物信息学在科研中的重要性日益提升,越来越多的学生和研究人员开始接触这一领域。生信豆芽菜正是为了满足这些新手的需求&a…...
创客匠人标杆对话(上):她如何通过“特长+赛道”实现财富升级
老蒋创客圈第64期对话标杆直播连麦,本期我们邀请到【iAMU蒙特梭利翻转星球】平台创始人申晓慧老师。 为我们揭秘“如何挖掘人生首个百万,实现财富升级?”,深度分享如何提炼用户痛点,高效引流新用户?如何通…...
最少钱学习并构建大模型ollama-llama3 8B
学习大模型时可能面临一些困难,这些困难可能包括: 计算资源限制:训练大模型通常需要大量的计算资源,包括CPU、GPU等。如果设备资源有限,可能会导致训练时间长、效率低下或无法完成训练。 内存限制:大模型通…...
AVI视频损坏了怎么修复?轻松几步解决你的困扰
在数字化时代,视频已成为我们记录生活、分享经验和传递信息的重要方式。AVI作为一种常见的视频格式,因其无损质量的特点而受到广泛欢迎。然而,有时候我们可能会遇到AVI视频文件损坏的情况,导致无法正常播放。别担心,本…...
【C++】map、set基本用法
欢迎来到我的Blog,点击关注哦💕 前言: C的STL已经学习很大一部分了,接下来介绍的是map set是c的是两种关联容器。 简单介绍 map set: 两者都使用红黑树作为底层数据结构来存储元素。map是一种键值对容器,其中每个键…...
模型 闭环原理
系列文章 分享 模型,了解更多👉 模型_思维模型目录。反馈驱动,持续循环,缺陷亦被放大。 1 闭环原理的应用 1.1 闭环原理解读 AI自我训练,从人工智能变成人工智障 这里主要使用闭环原理来解释 AI 自我训练导致的问题。…...
3007. 价值和小于等于 K 的最大数字(24.8.21)
前言 感谢皇家笨阿宝的指导 题目 给你一个整数 k 和一个整数 x 。整数 num 的价值是它的二进制表示中在 x,2x,3x 等位置处设置位的数目(从最低有效位开始)。下面的表格包含了如何计算价值的例子。 XnumBinary RepresentationPri…...
微服务 - 分布式锁的实现与处理策略
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有疑问和建议,请私信或评论留言! 分布式锁的实现与处理…...
Catf1ag CTF Web(九)
前言 Catf1agCTF 是一个面向所有CTF(Capture The Flag)爱好者的综合训练平台,尤其适合新手学习和提升技能 。该平台由catf1ag团队打造,拥有超过200个原创题目,题目设计注重知识点的掌握,旨在帮助新手掌握C…...
QT QFileDialog 类
QFileDialog 类 QFileDialog 类 QFileDialog 是 Qt 库中的一个类,用于提供文件选择对话框, 允许用户选择文件或目录。QFileDialog 提供了多种静态方法和实例方法, 用于创建和配置文件对话框,并获取用户选择的文件或目录。 QObje…...
了解 K-Means 聚类的工作原理(详细指南)
一、说明 K-means 的目标是将一组观测值划分为 k 个聚类,每个观测值分配给均值(聚类中心或质心)最接近的聚类,从而充当该聚类的代表。 在本文中,我们将全面介绍 k 均值聚类(最常用的聚类方法之一࿰…...
预警先行,弯道哨兵让行车更安全
预警先行,弯道哨兵让行车更安全”这句话深刻体现了现代交通安全理念中预防为主、科技赋能的重要性。在道路交通中,尤其是复杂多变的弯道区域,交通事故的发生率往往较高,因此,采取有效的预警措施和引入先进的交通辅助设…...
预约咨询小程序搭建开发,uniapp前端,PHP语言开发
目录 前言: 一、预约小程序搭建功能介绍 二、示例代码片段 前言: 预约咨询小程序适合需付费咨询和交流的场景:比如讲师,摄影,婚庆,美发,律师,心理等等支持商家入驻支持视频、图文、线下、电话等方式在线支付咨询。 一、预约小程…...
极速文件预览!轻松部署 kkFileView 于 Docker 中!
大家好,这几天闲的难受,决定给自己找点事做。博主的项目中有个文件预览的小需求,原有方案是想将文件转换成 PDF 进行预览。本着能借鉴就绝对不自己写的原则。今天就让我们简单试用一下 kkFileView 文件预览服务,一起探索它的强大功…...
某验九宫格分类识别
注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 九宫格分类如下 这种就是最简单的分类识别了,用迁移学习resnet训练即可,下面来看成品 训练代码查看往期文章中就有,部分代码如下: DEVICE = torch.device(…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑
精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑 在电子商务领域,转化率与网站性能是决定商业成败的核心指标。今天,我们将深入解析不同类型电商平台的转化率基准,探讨页面加载速度对用户行为的…...
CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)
漏洞概述 漏洞名称:Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号:CVE-2023-25194 CVSS评分:8.8 影响版本:Apache Kafka 2.3.0 - 3.3.2 修复版本:≥ 3.4.0 漏洞类型:反序列化导致的远程代…...
OpenGL-什么是软OpenGL/软渲染/软光栅?
软OpenGL(Software OpenGL)或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式(包括几何处理、光栅化、着色等),不依赖GPU硬件加速。这种模式通常性能较低,但兼容性极强,常用于不支持硬件加速…...
