Freertos计数和二值信号量学习

2024-05-02 15:44

本文主要是介绍Freertos计数和二值信号量学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

信号量常用于控制对共享资源的访问和任务同步。

其中控制共享资源可以从停车场的例子去理解。比如现在这个停车场最大容量为100。这个100就是共享资源。假如要把车停进去这个停车场,就需要查看当前停车场中的数量。当前的停车数量就是信号量。信号量的增加对应停车场的车开出停车场。信号量减少代表新的车进入了停车场。停车场这个例子使用的就是计数型信号量。

再来看一个例子,比如公共电话的使用只能同时一个人使用。这时候公共电话的状态就只有2种,使用和未使用,如果用电话的这两种作为信号量的话,这个就是二值信号量。
信号量用于控制共享资源访问的场景就是相当于上锁机制,代码只有获得了这个锁的钥匙才能能够执行。换句话来说,就是信号量(Semaphore)是一种广泛用于操作系统和多线程程序设计中的同步机制,用于控制对共享资源的访问。你可以将信号量想象成一个管理共享资源的“看门人”,确保同一时间内,只有有限的线程(或进程)可以访问这些资源。这种机制类似于现实生活中的“上锁”,其中“钥匙”的数量决定了同时能访问资源的线程数量。

信号量可用于任务同步

1:确保任务顺序:可以使用信号量来确保特定的任务或操作按照预定的顺序执行。例如,如果任务B依赖于任务A的结果,你可以在任务A完成时使用signal操作,并在任务B开始时使用wait操作,从而确保任务B在任务A之后执行。
2:条件同步:在某些情况下,你可能希望等待多个条件都满足后才执行某个任务。通过适当地初始化信号量的计数值和在关键点使用wait和signal操作,可以实现这种复杂的同步逻辑。
3:屏障(Barrier):信号量可以用来实现一个同步点,所有的线程或进程必须到达这一点后才能继续执行。这通常用于分阶段执行的场景,其中每个阶段需要所有参与者的结果。
信号量还可以用于中断服务函数,因为中断服务函数中不能放进去太多的代码。所以可以在中断函数中释放信号量,中断函数不做具体的处理,具体的处理过程做成一个任务,这个任务就会获取信号量,如果获取信号量就说明中断发生了。就开始进行对应的处理。

二值信号量

二值信号量常用于互斥访问或者同步,互斥访问这里用python中多线程举例子:

from threading import Semaphore, Thread# 初始化二值信号量,起始值为1,表示资源可用
mutex = Semaphore(1)# 共享数据
shared_data = []# 定义一个操作共享数据的函数
def modify_shared_data(thread_name):mutex.wait()  # 请求访问共享资源print(f"{thread_name} is modifying shared data.")shared_data.append(thread_name)  # 修改共享数据print(f"Current shared data: {shared_data}")mutex.signal()  # 释放访问权# 创建并启动多个线程
threads = [Thread(target=modify_shared_data, args=(f"Thread-{i}",)) for i in range(5)]
for t in threads:t.start()
for t in threads:t.join()

在这个例子中,每个线程在修改共享数据shared_data之前,必须通过wait()操作获取二值信号量mutex。由于mutex是一个二值信号量,这确保了同一时刻只有一个线程可以修改shared_data。完成修改后,线程通过signal()操作释放信号量,允许其他线程访问共享资源。
用于任务同步就是,同步的目标是协调多个线程或进程的执行顺序,确保它们在正确的时间点上执行特定的操作。二值信号量在这里用于实现线程之间的同步,确保特定的执行顺序或条件被满足。

# 初始化二值信号量,起始值为0,表示任务A尚未完成
sem = Semaphore(0)def taskA():# 执行任务A的操作...print("Task A completed.")sem.signal()  # 通知任务A已完成def taskB():sem.wait()  # 等待任务A完成# 执行任务B的操作...print("Task B started after Task A.")# 启动任务A和任务B的线程
Thread(target=taskA).start()
Thread(target=taskB).start()

在这个例子中,任务B通过wait()操作等待二值信号量sem,这保证了它只有在任务A调用signal()操作后才会执行。这样,我们就通过二值信号量实现了任务之间的同步。
和队列类似,信号量API函数允许设置一个阻塞时间,阻塞时间是当前任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上那么优先级最高的哪个任务优先获得信号量,这么当信号量有效的时候高优先级的任务就会解除阻塞状态。
原子文档中举了这样一个例子:
在这里插入图片描述
原子文档总结了二值信号量使用的过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

函数描述
vSemaphoreCreateBinary ()动态创建 二 值 信 号 量 , 这 个 是 老 版 本FreeRTOS 中使用的创建二值信号量的 API 函数。
xSemaphoreCreateBinary()动态创建二值信号量,新版 FreeRTOS 使用此函数创建二值信号量。
xSemaphoreCreateBinaryStatic()静态创建二值信号量 。

函数 xSemaphoreCreateBinary()

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

函数 xSemaphoreCreateBinaryStatic()

此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的
RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic()
来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
参数:
pxSemaphoreBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄。

在这里插入图片描述
由于信号量不需要像队列那样需要的队列项,所以底下这个参数对应的宏定义为0
在这里插入图片描述
在这里插入图片描述

释放信号量

函数描述
xSemaphoreGive()任务级信号量释放函数
xSemaphoreGiveFromISR()中断级信号量释放函数。

1、函数 xSemaphoreGive()
此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:BaseType_t xSemaphoreGive( xSemaphore )
参数:
xSemaphore:要释放的信号量句柄。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。

#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()。

获取信号量

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

函数描述
xSemaphoreTake()任务级获取信号量函数
xSemaphoreTakeFromISR()中断级获取信号量函数

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

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

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)

xSemaphore:要获取的信号量句柄。
xBlockTime: 阻塞时间。
再来看一下函数 xSemaphoreTake ()的具体内容,此函数在文件 semphr.h 中有如下定义:

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

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

BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t * pxHigherPriorityTaskWoken)

返回值:
pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败。
在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一步),然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数 pxHigherPriorityTaskWoken 设置为pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!这个函数还是很简单的。
至于为啥要在中断中进行任务切换。在 FreeRTOS 这样的实时操作系统中,中断服务例程(ISR)的执行具有高优先级,意味着它们能够打断大多数正在执行的任务。这是为了确保对紧急事件能够快速响应。然而,这也带来了一个挑战:如何在处理完一个中断后,高效地回到最应该运行的任务上。当中断发生时,当前正在运行的任务会被暂停,以便中断服务例程(ISR)可以执行。如果在 ISR 中释放了一个信号量,有可能会唤醒一个因等待该信号量而被阻塞的任务。如果这个被唤醒的任务的优先级高于当前被中断的任务,那么理论上,我们应该立即切换到这个更高优先级的任务上去执行。这种切换是为了保证系统的实时性,确保高优先级的任务能够尽快执行。

计数型信号量简介

有些资料中也将计数型信号量叫做数值信号量,二值信号量相当于长度为 1 的队列,那么计数型信号量就是长度大于 1 的队列。同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列是否为空即可。计数型信号量通常用于如下两个场合:
1、事件计数
在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数值为 0。
2、资源管理
在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量,比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100。
FreeRTOS 提供了两个计数型信号量创建函数,如表 所示:
在这里插入图片描述
1、函数 xSemaphoreCreateCounting()
此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateCountingSemaphore(),此函数原型如下:

SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )

参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。

2、函数 xSemaphoreCreateCountingStatic()
此函数也是用来创建计数型信号量的,使用此函数创建计数型信号量的时候所需要的内存需要由用户分配。此函数也是一个宏,真正执行的是函数xQueueCreateCountingSemaphoreStatic(),
函数原型如下:

SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, 
UBaseType_t uxInitialCount, 
StaticSemaphore_t * pxSemaphoreBuffer )

参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
pxSemaphoreBuffer:指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型号量创建成功,返回计数型信号量句柄。
计数型信号量的释放和获取与二值信号量相同,

这篇关于Freertos计数和二值信号量学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/954597

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件