首页 > 其他分享 >FreeRTOS 快速入门(五)之信号量

FreeRTOS 快速入门(五)之信号量

时间:2024-08-22 11:24:33浏览次数:11  
标签:入门 FreeRTOS 队列 句柄 信号量 计数 任务 二值

目录


一、信号量的特性

信号量(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 加 1
		xSemaphoreGive(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_t
									uxInitialCount);

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个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,队列,句柄,信号量,计数,任务,二值
From: https://blog.csdn.net/Teminator_/article/details/141421186

相关文章

  • 想要入门跨境电商?收下这份外贸术语速查手册
    在全球商业活动中,外贸专业词汇作为一种专门术语,广泛涵盖了商品、定价、运输、付款、合同、保险等各个方面。熟练掌握这些术语,不仅能助你与国际商业伙伴顺畅交流,更能让你深入理解外贸流程和规则,提升你的专业素养。接下来,本文将为你详细介绍一些常见的外贸词汇,帮助你轻松掌握。......
  • 单链表入门
    1.概念与结构概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。2.结点与顺序表不同的是,链表⾥的每一个都是独⽴申请下来的空间,我们称之为“结点/结点”结点的组成主要有两个部分:当前结点要保存的数据......
  • Python入门最完整的基础知识大全【纯干货,建议收藏】!
    主要内容如下:1.用Python做数据分析的重要性Python在数据分析和交互、探索性计算以及数据可视化等方面都显得比较活跃,这就是Python作为数据分析的原因之一,python拥有numpy、matplotlib、scikit-learn、pandas、ipython等工具在科学计算方面十分有优势,尤其是pandas,在处理中......
  • 大模型是什么?(超详细)大模型教程入门到精通
    大模型的定义大模型是指具有数千万甚至数亿参数的深度学习模型。近年来,随着计算机技术和大数据的快速发展,深度学习在各个领域取得了显著的成果,如自然语言处理,图片生成,工业数字化等。为了提高模型的性能,研究者们不断尝试增加模型的参数数量,从而诞生了大模型这一概念。大模......
  • 【嵌入式裸机开发】智能家居入门7:最新ONENET,MQTT协议接入,最全最新(微信小程序、MQTT协
    智能家居入门7前言一、ONENET云平台创建产品与设备二、STM32端连接服务器前的准备三、STM32端实现四、微信小程序端连接服务器前的准备五、微信小程序端实现六、最终测试前言本篇文章介绍最新ONENET云平台的MQTT协议接入方法,在STM32上实现数据上云与服务器下发数据......
  • IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!
    【来源申明】本文引用了微信公众号“网优雇佣军”的《是谁偷走了我家的手机信号?》文章内容。为了更好的内容呈现,下文在引用和收录时内容有改动,转载时请注明原文来源信息,尊重原作者的劳动。1、系列文章引言1.1适合谁来阅读?本系列文章尽量使用最浅显易懂的文字、图片来组织内容......
  • 一文入门ZooKeeper
    简介官网:https://zookeeper.apache.org/index.html分布式服务协调组件,GoogleChubby的开源实现。解决分布式应用中的以下问题:配置管理、命名服务(NamingService)、集群管理、统一命名服务、状态同步。用于解决分布式数据一致性问题,提供顺序一致性、原子性、单一视图、可靠性、实......
  • 信号量、PV操作及软考高级试题解析
    信号量在并发系统中,信号量是用于控制公共资源访问权限的变量。信号量用于解决临界区问题,使得多任务环境下,进程能同步运行。此概念是由荷兰计算机科学家Dijkstra在1962年左右提出的。信号量仅仅跟踪还剩多少资源可用,不会跟踪哪些资源是可用的。信号量机制,处理进程同步和互斥的问......
  • 大模型入门书籍推荐:动手做AI Agent(PDF版免费下载)
    动手做AIAgent人工智能时代一种全新的技术Agent正在崛起。这是一种能够理解自然语言并生成对应回复以及执行具体行动的人工智能体。它不仅是内容生成工具,而且是连接复杂任务的关键纽带。本书将探索Agent的奥秘,内容包括从技术框架到开发工具,从实操项目到前沿进展,通过带着......
  • 【Three.JS零基础入门教程】第七篇:材质详解
      前期回顾:【Three.JS零基础入门教程】第一篇:搭建开发环境【Three.JS零基础入门教程】第二篇:起步案例【Three.JS零基础入门教程】第三篇:开发辅助【Three.JS零基础入门教程】第四篇:基础变换【Three.JS零基础入门教程】第五篇:项目规划【Three.JS零基础入门教程】第六篇:物......