当前位置: 首页 > news >正文

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 的值)。

上面的代码看似没问题,其实存在有两个问题:

  1. 使用了全局变量 flagCalcEnd,(如果同时读写 flagCalcEnd 则会出问题)。
  2. 任务二在等待任务一计算完 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),其他等待事件发生的任务获取计数型信号量(计数型信号量的资源数减 1),这种场景下,计数型信号量的资源数一般在创建时设置为 0。
  2. 资源管理
    在这种场合下,计数型信号量的资源数代表着共享资源的可用数量,一个任务想要访问共享资源,就必须先获取这个共享资源的计数型信号量,之后在成功获取了计数型信号量之后,才可以对这个共享资源进行访问操作,当然,在使用完共享资源后也要释放这个共享资源的计数型信号量。在这种场合下,计数型信号量的资源数一般在创建时设置为受其管理的共享资源的最大可用数量。

三、信号量函数

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

1、创建

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

二值信号量计数型信号量
动态创建xSemaphoreCreateBinary
计数值初始值为 0
xSemaphoreCreateCounting
vSemaphoreCreateBinary(过时了)
计数值初始值为 1
静态创建xSemaphoreCreateBinaryStaticxSemaphoreCreateCountingStatic

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

/* 创建一个二值信号量,返回它的句柄。* 此函数内部会分配信号量结构体* 返回值: 返回句柄,非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 中使用
givexSemaphoreGivexSemaphoreGiveFromISR
takexSemaphoreTakexSemaphoreTakeFromISR
  1. xSemaphoreGive 的函数原型如下:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
参数说明
xSemaphore信号量句柄,释放哪个信号量
返回值pdTRUE 表示成功,
如果二值信号量的计数值已经是 1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败
  1. xSemaphoreGiveFromISR 的函数原型如下:
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
参数说明
xSemaphore信号量句柄,释放哪个信号量
pxHigherPriorityTaskWoken如果释放信号量导致更高优先级的任务变为了就绪态,
*pxHigherPriorityTaskWoken = pdTRUE
返回值pdTRUE 表示成功,
如果二值信号量的计数值已经是 1,再次调用此函数则返回失
败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返
回失败
  1. xSemaphoreTake 的函数原型如下:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
参数说明
xSemaphore信号量句柄,获取哪个信号量
xTicksToWait如果无法马上获得信号量,阻塞一会:
0:不阻塞,马上返回
portMAX_DELAY: 一直阻塞直到成功
其他值: 阻塞的 Tick 个数,可以使用 pdMS_TO_TICKS() 来指定阻塞时间为若干 ms
返回值pdTRUE 表示成功
  1. 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 一、信号量的特性 信号量&#xff08;Semaphore&#xff09;是一种实现任务…...

centos 服务器之间实现免密登录

为了在CentOS服务器之间实现免密登录&#xff0c;你需要使用SSH的公钥认证机制 比如两台centos系统的服务器A 和服务器B 首先我们实现从A服务器可以免密登录到服务器B上 首先生成公钥和秘钥&#xff1a; ssh-keygen -t rsa 生成了公钥和秘钥之后&#xff1a; ssh-copy-id r…...

RabbitMq实现延迟队列功能

1、rabbitmq服务端打开延迟插件 &#xff08;超过 4294967295毫秒 ≈ 1193 小时 ≈ 49.7 天 这个时间会立即触发&#xff09; 注意&#xff1a;只有RabbitMQ 3.6.x以上才支持 在下载好之后&#xff0c;解压得到.ez结尾的插件包&#xff0c;将其复制到RabbitMQ安装目录下的plug…...

redis内存淘汰策略

1. redis内存淘汰策略 日常常用&#xff1a;allkeys-lru&#xff1a;在键空间中移除最近最少使用的key。1.1 为什么需要使用redis内存淘汰策略? 因为我们服务器中的内存是有限的,不会无限多,所以需要对一些不常用的key进行内存清理.1.2 redis内存淘汰策略有哪些? redis默认…...

实时洞察应用健康:使用Spring Boot集成Prometheus和Grafana

1. 添加Prometheus和Actuator依赖 在pom.xml中添加Spring Boot Actuator和Micrometer Prometheus依赖&#xff1a; <dependencies> <!--监控功能Actuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring…...

生信圆桌x生信豆芽菜:生物信息学新手的学习与成长平台

生信豆芽菜是一个专门为生物信息学初学者创建的学习与交流平台&#xff0c;致力于帮助新手们快速入门并掌握生信分析的基础知识与技能。随着生物信息学在科研中的重要性日益提升&#xff0c;越来越多的学生和研究人员开始接触这一领域。生信豆芽菜正是为了满足这些新手的需求&a…...

创客匠人标杆对话(上):她如何通过“特长+赛道”实现财富升级

老蒋创客圈第64期对话标杆直播连麦&#xff0c;本期我们邀请到【iAMU蒙特梭利翻转星球】平台创始人申晓慧老师。 为我们揭秘“如何挖掘人生首个百万&#xff0c;实现财富升级&#xff1f;”&#xff0c;深度分享如何提炼用户痛点&#xff0c;高效引流新用户&#xff1f;如何通…...

最少钱学习并构建大模型ollama-llama3 8B

学习大模型时可能面临一些困难&#xff0c;这些困难可能包括&#xff1a; 计算资源限制&#xff1a;训练大模型通常需要大量的计算资源&#xff0c;包括CPU、GPU等。如果设备资源有限&#xff0c;可能会导致训练时间长、效率低下或无法完成训练。 内存限制&#xff1a;大模型通…...

AVI视频损坏了怎么修复?轻松几步解决你的困扰

在数字化时代&#xff0c;视频已成为我们记录生活、分享经验和传递信息的重要方式。AVI作为一种常见的视频格式&#xff0c;因其无损质量的特点而受到广泛欢迎。然而&#xff0c;有时候我们可能会遇到AVI视频文件损坏的情况&#xff0c;导致无法正常播放。别担心&#xff0c;本…...

【C++】map、set基本用法

欢迎来到我的Blog&#xff0c;点击关注哦&#x1f495; 前言: C的STL已经学习很大一部分了&#xff0c;接下来介绍的是map set是c的是两种关联容器。 简单介绍 map set&#xff1a; 两者都使用红黑树作为底层数据结构来存储元素。map是一种键值对容器&#xff0c;其中每个键…...

模型 闭环原理

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。反馈驱动&#xff0c;持续循环&#xff0c;缺陷亦被放大。 1 闭环原理的应用 1.1 闭环原理解读 AI自我训练&#xff0c;从人工智能变成人工智障 这里主要使用闭环原理来解释 AI 自我训练导致的问题。…...

3007. 价值和小于等于 K 的最大数字(24.8.21)

前言 感谢皇家笨阿宝的指导 题目 给你一个整数 k 和一个整数 x 。整数 num 的价值是它的二进制表示中在 x&#xff0c;2x&#xff0c;3x 等位置处设置位的数目&#xff08;从最低有效位开始&#xff09;。下面的表格包含了如何计算价值的例子。 XnumBinary RepresentationPri…...

微服务 - 分布式锁的实现与处理策略

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有疑问和建议&#xff0c;请私信或评论留言&#xff01; 分布式锁的实现与处理…...

Catf1ag CTF Web(九)

前言 Catf1agCTF 是一个面向所有CTF&#xff08;Capture The Flag&#xff09;爱好者的综合训练平台&#xff0c;尤其适合新手学习和提升技能 。该平台由catf1ag团队打造&#xff0c;拥有超过200个原创题目&#xff0c;题目设计注重知识点的掌握&#xff0c;旨在帮助新手掌握C…...

QT QFileDialog 类

QFileDialog 类 QFileDialog 类 QFileDialog 是 Qt 库中的一个类&#xff0c;用于提供文件选择对话框&#xff0c; 允许用户选择文件或目录。QFileDialog 提供了多种静态方法和实例方法&#xff0c; 用于创建和配置文件对话框&#xff0c;并获取用户选择的文件或目录。 QObje…...

了解 K-Means 聚类的工作原理(详细指南)

一、说明 K-means 的目标是将一组观测值划分为 k 个聚类&#xff0c;每个观测值分配给均值&#xff08;聚类中心或质心&#xff09;最接近的聚类&#xff0c;从而充当该聚类的代表。 在本文中&#xff0c;我们将全面介绍 k 均值聚类&#xff08;最常用的聚类方法之一&#xff0…...

预警先行,弯道哨兵让行车更安全

预警先行&#xff0c;弯道哨兵让行车更安全”这句话深刻体现了现代交通安全理念中预防为主、科技赋能的重要性。在道路交通中&#xff0c;尤其是复杂多变的弯道区域&#xff0c;交通事故的发生率往往较高&#xff0c;因此&#xff0c;采取有效的预警措施和引入先进的交通辅助设…...

预约咨询小程序搭建开发,uniapp前端,PHP语言开发

目录 前言&#xff1a; 一、预约小程序搭建功能介绍 二、示例代码片段 前言&#xff1a; 预约咨询小程序适合需付费咨询和交流的场景&#xff1a;比如讲师,摄影,婚庆&#xff0c;美发,律师,心理等等支持商家入驻支持视频、图文、线下、电话等方式在线支付咨询。 一、预约小程…...

极速文件预览!轻松部署 kkFileView 于 Docker 中!

大家好&#xff0c;这几天闲的难受&#xff0c;决定给自己找点事做。博主的项目中有个文件预览的小需求&#xff0c;原有方案是想将文件转换成 PDF 进行预览。本着能借鉴就绝对不自己写的原则。今天就让我们简单试用一下 kkFileView 文件预览服务&#xff0c;一起探索它的强大功…...

某验九宫格分类识别

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 九宫格分类如下 这种就是最简单的分类识别了,用迁移学习resnet训练即可,下面来看成品 训练代码查看往期文章中就有,部分代码如下: DEVICE = torch.device(…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

MySQL 主从同步异常处理

阅读原文&#xff1a;https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主&#xff0c;遇到的这个错误&#xff1a; Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一&#xff0c;通常表示&#xff…...

高效的后台管理系统——可进行二次开发

随着互联网技术的迅猛发展&#xff0c;企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心&#xff0c;成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统&#xff0c;它不仅支持跨平台应用&#xff0c;还能提供丰富…...