百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

freeRTOS使用:信号量介绍和使用方法

liebian365 2025-01-17 12:13 14 浏览 0 评论


在操作系统系统中,信号量通常用于控制对共享资源的访问和任务之间进行同步,信号量在操作系统中是很常用的,也是学习freeRTOS操作系统必须要掌握的。

freeRTOS中最常用到的信号量有:二值信号量、计数信号量、互斥信号量。

有关这几个信号量分别如下:


1、二值信号量

(1)二值信号量

二值信号量是指所创建的信号量只有两个值(0 和 1),通常用于互斥访问或者同步。

二值信号量在某处被占有使用之后,其他地方想要申请这个二值信号量是无法成功申请的,只有当这个被占有的二值信号量被使用完毕并释放之后,才能被再次申请占有使用!

总而言之,二值信号量被使用之后会变为无效状态,需要被重新释放才能进入有效状态。

二值信号量就像是路口的红绿灯一样,只有两个状态:能通行和不能通行。在freeRTOS中就是能使用和不能使用。如下图示例:

在freeRTOS中,二值信号量的创建和使用的API管理函数分别如下:


(2)创建二值信号量

函数原型:

 SemaphoreHandle_t xSemaphoreCreateBinary(void)

函数描述:函数 xSemaphoreCreateBinary 用于创建二值信号量。

返回值:如果创建成功会返回二值信号量的句柄,创建失败会返回 NULL。


(3)等待二值信号量

在freeRTOS中,信号量的获取是进行了区分的,在任务或者函数中获取与在中断中是不一样的,freeRTOS中给出了不同API函数。

1)在任务代码中等待信号量

函数原型:

 xSemaphoreTake(
SemaphoreHandle_t xSemaphore, /* 信号量句柄 */ 
TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

函数描述:

函数 xSemaphoreTake 用于在任务代码中获取信号量。
	第 1 个参数是信号量句柄。
	第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

返回值:如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。

使用这个函数要注意以下问题:

此函数是用于任务代码中调用的,不可以在中断服务程序中调用此函数,中断服务程序使用的是xSemaphoreTakeFromISR。

2)在中断中等待信号量

 xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )

函数描述:函数xSemaphoreTakeFromISR用于在中断中获取信号量。

第 1 个参数是要获取的信号量的句柄。 这是创建信号量时返回的句柄。
第 2 个参数是如果采用信号量导致任务取消阻止,并且未阻止的任务的优先级高于当前运行的任务,则xSemaphoreTakeFromISR()会将pxHigherPriorityTaskWoken设置为pdTRUE。 如果xSemaphoreTakeFromISR()将此值设置为pdTRUE,则应在退出中断之前请求上下文切换。
返回值:如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。


(4)释放二值信号量

1)用于在任务代码中释放二值信号量

函数原型:

 xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

函数描述:释放信号量。函数 xSemaphoreGive 用于在任务代码中释放信号量。

xSemaphore:是信号量句柄。
返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。

注意:此函数是用于任务代码中调用的,不可以在中断服务程序中调用此函数。

2)用于在中断中释放二值信号量

函数原型:

 xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore, 
signed BaseType_t *pxHigherPriorityTaskWoken)

函数描述:函数 xSemaphoreGiveFromISR 用于中断服务程序中释放信号量。

xSemaphore:是信号量句柄。
pxHigherPriorityTaskWoken:用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。
返回值:如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE_FULL。


2、计数信号量

计数信号量是一个相当于长度大于1的队列,用于任务之间的同步和共享资源的保护。

计数信号量与二值信号量的不同在于,二值信号量只能被一个地方申请使用,只有在这个申请使用的地方了释放了才能被其他处申请使用。而计数信号量是可以创建一定数量的信号量的,多个地方可以同时申请使用,直到达到最大的计数信号量的阈值

计数信号量相关的API函数:

(1)创建计数信号量

 SemaphoreHandle_t xSemaphoreCreateCounting( 
UBaseType_t uxMaxCount, /* 支持的最大计数值 */ 
UBaseType_t uxInitialCount); /* 初始计数值 */

参数说明:

uxMaxCount:设置此计数信号量支持的最大计数值。
uxInitialCount:设置计数信号量的初始值。(为0则不起作用)
返回值:如果创建成功会返回消息队列的句柄,创建失败会返回 NULL。


(2)获取信号量

1)在任务代码中获取信号量

 xSemaphoreTake( 
SemaphoreHandle_t xSemaphore, /* 信号量句柄 */ 
TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

说明:函数 xSemaphoreTake 用于在任务代码中获取信号量。

xSemaphore:是信号量句柄。
xTicksToWait:是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
返回值:如果信号量获取成功会返回 pdTRUE,否则返回 pdFALSE。

2)在中断中获取信号量

 xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )

函数描述:函数 xSemaphoreTakeFromISR 用于在中断中获取信号量。

xSemaphore:是要获取的信号量的句柄。 这是创建信号量时返回的句柄。
pxHigherPriorityTaskWoken:是如果采用信号量导致任务取消阻止,并且未阻止的任务的优先级高于当前运行的任务,则xSemaphoreTakeFromISR()会将pxHigherPriorityTaskWoken设置为pdTRUE。 如果xSemaphoreTakeFromISR()将此值设置为pdTRUE,则应在退出中断之前请求上下文切换。

返回值:如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。


(3)释放信号量

1)在任务代码中释放信号量

 xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

函数 xSemaphoreGive 用于在任务代码中释放信号量。

xSemaphore:是信号量句柄。

返回值:如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为计数信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。

2)在中断中释放信号量

 xSemaphoreGiveFromISR( 
SemaphoreHandle_t xSemaphore, /* 信号量句柄 */ 
signed BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */ )
xSemaphore:是信号量句柄。
pxHigherPriorityTaskWoken:用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。

返回值:如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE_FULL。


3、优先级反转 & 互斥信号量

在实时操作系统中,优先级反转的问题是不容忽视的,程序设计的过程中,也是要充分考虑这个问题的。

那优先级反转到底是什么呢?

优先反转是指:

假如一个系统中有高(H)、中(M)、低(L)三个优先级的任务,并有一个二值信号量。在某一个时刻二值信号量被低(L)优先级的任务使用了,并在运行过程中,高优先级任务(H)抢占了低优先级(L)的CPU使用权,但是也想要获取二值信号量被低优先(L)的任务占有着,高优先级任务(H)由此被挂起等待了,中优先级任务(M)因为不需要二值信号量,会抢占低优先级(L)任务的执行而得到运行,而高优先级任务(H)依然只能等到低优先级任务(L)释放二值信号量才能得到执行。

由此造成了高优先级任务得不到及时的执行,而低优先级任务却能比高优先级任务更多的得到执行。这就是优先级反转。

优先级互斥的示意图如下:


解决优先级反转的问题最好的办法是使用互斥信号量。

互斥信号量和二值信号量比较相似,不同之处在于互斥信号量具有优先级继承的特性,如果一个互斥信号量正在被一个低优先级的任务使用,而此时这个高优先级的任务也希望获取这个互斥信号量的话就会被阻塞。

互斥信号量能够解决优先级反转的原因:

使用互斥信号量时,高优先级的任务会把低优先级的任务的优先级先提高到和自己相同的优先级,保证低优先级的任务能够继续运行至结束这样极大减少了因为高优先级获取不到信号量被阻塞过长时间的问题。

互斥信号量的API函数:

(1)创建互斥信号量

函数原型:

 SemaphoreHandle_t xSemaphoreCreateMutex(void)

函数描述:函数 xSemaphoreCreateMutex 用于创建互斥信号量。

返回值:如果创建成功会返回互斥信号量的句柄,失败会返回 NULL。


(2)获取互斥信号量

函数原型:

 xSemaphoreTake( 
SemaphoreHandle_t xSemaphore, /* 信号量句柄 */ 
TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

函数描述:函数 xSemaphoreTake 用于在任务代码中获取信号量。

xSemaphore:是信号量句柄。
xTicksToWait:是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

返回值:如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。


(3)释放互斥信号量

函数原型:

 xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

函数描述:函数 xSemaphoreGive 用于在任务代码中释放信号量。

xSemaphore:是信号量句柄。

返回值:如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。


4、二值信号量和计数信号量的使用示例

二值信号量和计数信号量的使用思路如下:

1)通过任务start_task创建两个任务led0_task、led2_task,一个二值信号量binary_sem,一个计数信号量count_sem。

2)信号量创建成功之后,在led0_task任务中等到信号量,如果能获取到就翻转LED2的状态。

3)在led2_task任务中定时翻转LED3的状态,表明系统正在运行中。

4)通过外部中断获取按键的按下,更新信号量的值。

注意:本例程使用的是GD32E103进行演示,通过按键更新信号量,从而改变LED灯的状态。

(1)创建任务和二值信号量、计数信号量

 /*MCU:GD32E103Vx
* RTOS:freeRTOS*/
void start_task(void *pvParameters)
{
            pvParameters =  pvParameters;
            taskENTER_CRITICAL();  //进入临界区

            binary_sem = xSemaphoreCreateBinary();     //创建二值信号量
            count_sem  = xSemaphoreCreateCounting(10,10);//创建计数信号量

            xTaskCreate((TaskFunction_t)  led0_task,
                         (const char*)    "led0_task",
                          (uint16_t)      TASK_STK_LED0_SIZE,
                          (void*)         NULL,
                          (UBaseType_t)   TASK_LED0_PRIO,
                           NULL );

            xTaskCreate((TaskFunction_t)  led2_task,
                         (const char*)    "led2_task",
                          (uint16_t)      TASK_STK_LED2_SIZE,
                          (void*)         NULL,
                          (UBaseType_t)   TASK_LED2_PRIO,
                           NULL );
            vTaskDelete(StartTask_Handler);   //删除开始任务
            taskEXIT_CRITICAL();   //退出临界区
}


(2)任务函数

void led0_task(void *pvParameters)
{
        //pvParameters =  pvParameters;
        BaseType_t err = pdFALSE;
        for(;;)
        { 
          /* 二值信号量
           if(binary_sem != NULL)
           {
              err =  xSemaphoreTake(binary_sem,portMAX_DELAY);
              if(err == pdTRUE)
              {
                  gd_eval_led_toggle(LED2);
              }
           }*/

           if(count_sem != NULL)
           {
             err = xSemaphoreTake(count_sem,portMAX_DELAY);
             if(err == pdTRUE)
             {
               gd_eval_led_toggle(LED2);
             }
           }
           vTaskDelay(100);
        }
}


void led2_task(void *pvParameters)
{
        pvParameters = pvParameters;
        for(;;)
        {
           gd_eval_led_toggle(LED3);
           vTaskDelay(500);
        }
}


(3)中断处理函数

void EXTI10_15_IRQHandler(void)
{
         BaseType_t xHigherPriorityTaskWoken;
         if(exti_interrupt_flag_get(EXTI_13) != RESET)
         {   

             exti_interrupt_flag_clear(EXTI_13); //清除中断标志位
             /*if(binary_sem != NULL)
             {
                //释放二值信号量
                xSemaphoreGiveFromISR(binary_sem,&xHigherPriorityTaskWoken);
                //必要时进行任务切换
                portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
             }*/


              if(count_sem != NULL)
              {
                xSemaphoreGiveFromISR(count_sem,&xHigherPriorityTaskWoken);
                portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
              }
         }
}


作者简介:

本人95后技术男,从事嵌入式软件开发,专注于技术成长和技术分享。目标是每天进一步一点点,通过技术改变自己的生活,创造自己的美好未来!如果你也对嵌入式感兴趣,欢迎关注我呀!


声明:

本文作者:嵌入式之入坑笔记

文章版权归作者所有,转载请注明出处!

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: