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

2024-08-22 13:44

本文主要是介绍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 快速入门(五)之信号量的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Python中OpenCV与Matplotlib的图像操作入门指南

《Python中OpenCV与Matplotlib的图像操作入门指南》:本文主要介绍Python中OpenCV与Matplotlib的图像操作指南,本文通过实例代码给大家介绍的非常详细,对大家的学... 目录一、环境准备二、图像的基本操作1. 图像读取、显示与保存 使用OpenCV操作2. 像素级操作3.

MybatisX快速生成增删改查的方法示例

《MybatisX快速生成增删改查的方法示例》MybatisX是基于IDEA的MyBatis/MyBatis-Plus开发插件,本文主要介绍了MybatisX快速生成增删改查的方法示例,文中通过示例代... 目录1 安装2 基本功能2.1 XML跳转2.2 代码生成2.2.1 生成.xml中的sql语句头2

8种快速易用的Python Matplotlib数据可视化方法汇总(附源码)

《8种快速易用的PythonMatplotlib数据可视化方法汇总(附源码)》你是否曾经面对一堆复杂的数据,却不知道如何让它们变得直观易懂?别慌,Python的Matplotlib库是你数据可视化的... 目录引言1. 折线图(Line Plot)——趋势分析2. 柱状图(Bar Chart)——对比分析3

一文教你Java如何快速构建项目骨架

《一文教你Java如何快速构建项目骨架》在Java项目开发过程中,构建项目骨架是一项繁琐但又基础重要的工作,Java领域有许多代码生成工具可以帮助我们快速完成这一任务,下面就跟随小编一起来了解下... 目录一、代码生成工具概述常用 Java 代码生成工具简介代码生成工具的优势二、使用 MyBATis Gen

使用animation.css库快速实现CSS3旋转动画效果

《使用animation.css库快速实现CSS3旋转动画效果》随着Web技术的不断发展,动画效果已经成为了网页设计中不可或缺的一部分,本文将深入探讨animation.css的工作原理,如何使用以及... 目录1. css3动画技术简介2. animation.css库介绍2.1 animation.cs

SpringBoot快速搭建TCP服务端和客户端全过程

《SpringBoot快速搭建TCP服务端和客户端全过程》:本文主要介绍SpringBoot快速搭建TCP服务端和客户端全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录TCPServerTCPClient总结由于工作需要,研究了SpringBoot搭建TCP通信的过程

POI从入门到实战轻松完成EasyExcel使用及Excel导入导出功能

《POI从入门到实战轻松完成EasyExcel使用及Excel导入导出功能》ApachePOI是一个流行的Java库,用于处理MicrosoftOffice格式文件,提供丰富API来创建、读取和修改O... 目录前言:Apache POIEasyPoiEasyExcel一、EasyExcel1.1、核心特性

Python中模块graphviz使用入门

《Python中模块graphviz使用入门》graphviz是一个用于创建和操作图形的Python库,本文主要介绍了Python中模块graphviz使用入门,具有一定的参考价值,感兴趣的可以了解一... 目录1.安装2. 基本用法2.1 输出图像格式2.2 图像style设置2.3 属性2.4 子图和聚

一文教你Python如何快速精准抓取网页数据

《一文教你Python如何快速精准抓取网页数据》这篇文章主要为大家详细介绍了如何利用Python实现快速精准抓取网页数据,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录1. 准备工作2. 基础爬虫实现3. 高级功能扩展3.1 抓取文章详情3.2 保存数据到文件4. 完整示例

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完