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

FreeRTOS(9)信号量-计数型信号量

计数型信号量简介

计数型信号量与二值信号量类似, 二值信号量相当于队列长度为 1 的队列,因此二值信号量只能容纳一个资源,这也是为什么命名为二值信号量,而计数型信号量相当于队列长度大于0 的队列,因此计数型信号量能够容纳多个资源,这是在计数型信号量被创建的时候确定的。计数型信号量通常用于一下两种场合:
1. 事件计数
在这种场合下,每次事件发生后,在事件处理函数中释放计数型信号量(计数型信号量的资源数加 1),其他等待事件发生的任务获取计数型信号量(计数型信号量的资源数减 1),这么一来等待事件发生的任务就可以在成功获取到计数型信号量之后执行相应的操作。在这种场合下,计数型信号量的资源数一般在创建时设置为 0。
2. 资源管理
在这种场合下,计数型信号量的资源数代表着共享资源的可用数量,例如前面举例中停车场中的空车位。一个任务想要访问共享资源,就必须先获取这个共享资源的计数型信号量,之后在成功获取了计数型信号量之后,才可以对这个共享资源进行访问操作,当然,在使用完共享资源后也要释放这个共享资源的计数型信号量。在这种场合下,计数型信号量的资源数一般在创建时设置为受其管理的共享资源的最大可用数量。

计数型信号量相关 API 函数

函数描述
xSemaphoreCreateCounting()使用动态方式创建计数型信号量
xSemaphoreCreateCountingStatic()使用静态方式创建计数型信号量
xSemaphoreTake()获取信号量
xSemaphoreTakeFromISR()在中断中获取信号量
xSemaphoreGive()释放信号量
xSemaphoreGiveFromISR()在中断中释放信号量
vSemaphoreDelete()删除信号量

从上面中可以看出,计数型信号量除了创建函数之外,其余的获取、释放等信号量操作函数,都与二值信号量相同,因此这里重点讲解计数型信号量的创建函数。

函数 xSemaphoreCreateCounting()

此函数用于使用动态方式创建计数型信号量,创建计数型信号量所需的内存,由 FreeRTOS从 FreeRTOS 管理的堆中进行分配。该函数实际上是一个宏定义,在 semphr.h 中有定义,具体的代码如下所示:

 #define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )    xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

从上面的代码中可以看出,函数 xSemaphoreCreateCounting()实际上是调用了函数xQueueCreateCountingSemaphore(),函数 xQueueCreateCountingSemaphore()在 queue.c 文件中有
定义,具体的代码如下所示:

    QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
                                                 const UBaseType_t uxInitialCount )
    {
        QueueHandle_t xHandle = NULL;

        if( ( uxMaxCount != 0 ) &&
            ( uxInitialCount <= uxMaxCount ) )
        {
            xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

            if( xHandle != NULL )
            {
                ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;

                traceCREATE_COUNTING_SEMAPHORE();
            }
            else
            {
                traceCREATE_COUNTING_SEMAPHORE_FAILED();
            }
        }
        else
        {
            configASSERT( xHandle );
            mtCOVERAGE_TEST_MARKER();
        }

        return xHandle;
    }

从上面的代码中可以看出,计数型信号量的就是一个队列长度为计数型信号量最大资源数的队列,而队列的非空闲项目数量就是用来记录计数型信号量的可用资源的。

函数 xSemaphoreCreateCountingStatic()

此函数用于使用静态方式创建计数型信号量,创建计数型信号量所需的内存, 需要由用户手动分配并提供。 该函数实际上是一个宏定义,在 semphr.h 中有定义,具体的代码如下所示:

 #define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxSemaphoreBuffer )    xQueueCreateCountingSemaphoreStatic( ( uxMaxCount ), ( uxInitialCount ), ( pxSemaphoreBuffer ) )

从上面的代码中可以看出,函数 xSemaphoreCreateCountingStatic()实际上是调用了函数xQueueCreateCountingSemaphoreStatic(),函数 xQueueCreateCountingSemaphoreStatic()在 queue.c
文件中有定义, 其函数内容与函数 xQueueCreateCountingSemaphore()类似,只是动态创建队列的函数替换成了静态创建队列的函数。

计数型信号量操作实验

TaskHandle_t xTaskHandle_1;
TaskHandle_t xTaskHandle_2;

void vTaskFunction_1(void *pvParameters);
void vTaskFunction_2(void *pvParameters);

SemaphoreHandle_t CountSemaphore;//计数型信号量

//init
CountSemaphore = xSemaphoreCreateCounting(255,0);

xTaskCreate(vTaskFunction_1, "Task1", 8192, NULL, 1, &xTaskHandle_1 );
xTaskCreate(vTaskFunction_2, "Task2", 8192, NULL, 1, &xTaskHandle_2 );

void vTaskFunction_1(void *pvParameters)
{
    while(1) {
		xSemaphoreGive(CountSemaphore);
        vTaskDelay(100);
    }
}

void vTaskFunction_2(void *pvParameters)
{   
    BaseType_t err;
    while(1) {
        err = xSemaphoreTake(CountSemaphore,portMAX_DELAY);	
        if(err==pdTRUE)	{									  
            printf("Task2 成功获取计数信号量,剩余计数信号量 = %d \n", uxSemaphoreGetCount(CountSemaphore));
        }

		vTaskDelay(200);
    }
}

测试结果:

Task2 成功获取计数信号量,剩余计数信号量 = 0
Task2 成功获取计数信号量,剩余计数信号量 = 1 
Task2 成功获取计数信号量,剩余计数信号量 = 2 
Task2 成功获取计数信号量,剩余计数信号量 = 3 
Task2 成功获取计数信号量,剩余计数信号量 = 4 
Task2 成功获取计数信号量,剩余计数信号量 = 5 
Task2 成功获取计数信号量,剩余计数信号量 = 6 
Task2 成功获取计数信号量,剩余计数信号量 = 7 
Task2 成功获取计数信号量,剩余计数信号量 = 8 
Task2 成功获取计数信号量,剩余计数信号量 = 9 
Task2 成功获取计数信号量,剩余计数信号量 = 10

优先级翻转

在使用二值信号量和计数型信号量的时候,经常会遇到优先级翻转的问题,优先级在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果,下面展示了一个优先级翻转的例子,如下图所示:

优先级翻转示意图,如上图所示,定义:任务 H 为优先级最高的任务,任务 L 为优先级中最低的任务,任务 M 为优先级在任务 H 与任务 L 之间的任务。
(1) 任务 H 和任务 M 为阻塞状态,等待某一事件发生,此时任务 L 正在运行。
(2) 此时任务 L 要访问共享资源,因此需要获取信号量。
(3) 任务 L 成功获取信号量,并且此时信号量已无资源,任务 L 开始访问共享资源。
(4) 此时任务 H 就绪,抢占任务 L 运行。
(5) 任务 H 开始运行。
(6) 此时任务 H 要访问共享资源,因此需要获取信号量,但信号量已无资源,因此任务 H阻塞等待信号量资源。
(7) 任务 L 继续运行。
(8) 此时任务 M 就绪,抢占任务 L 运行。
(9) 任务 M 正在运行。
(10) 任务 M 运行完毕,继续阻塞。
(11) 任务 L 继续运行。
(12) 此时任务 L 对共享资源的访问操作完成,释放信号量,任务 H 成功获取信号量,解除阻塞并抢占任务 L 运行。
(13) 任务 H 得以运行。
从上面优先级翻转的示例中,可以看出,任务 H 为最高优先级的任务,因此任务 H 执行的操作需要有较高的实时性,但是由于优先级翻转的问题,导致了任务 H 需要等到任务 L 释放信号量才能够运行,并且,任务 L 还会被其他介于任务 H 与任务 L 任务优先级之间的任务 M 抢占,因此任务 H 还需等待任务 M 运行完毕,这显然不符合任务 H 需要的高实时性要求。

相关文章:

  • 半导体工艺(七)干法刻蚀1.0
  • EaseUS Todo Backup Pro v16.0 数据备份还原软件
  • neo4j中常用cql命令汇总(基础版)
  • VS Code远程Docker开发配置指南——完美速通
  • idea 2023社区版自动生成 serialVersionUID
  • 搜广推校招面经四十六
  • FastDDS中Utils定义的那些数据结构(二)
  • redis增加ip白名单
  • 多数元素——面试经典150题(力扣)
  • 30天学习Java第四天——JVM规范
  • Chrome 扩展开发 API实战:Sessions (六)
  • 使用Python实现ICO文件生成工具
  • TensorFLow深度学习实战(11)——风格迁移详解
  • 电脑突然没有声音的可能原因与应对方法
  • NineData:解锁多云与混合云环境下的智能数据管理
  • 艾尔登复刻Ep1——客户端制作、场景切换、网络控制
  • Spring Boot 读取 ZooKeeper (ZK) 属性的总结指南
  • Lsposed模块原理详解
  • AI概率学预测足球大小球让球数据分析
  • 工作记录 2017-01-06
  • 第五轮伊美核问题谈判将于5月23日在罗马举行
  • 纪念|演员朱媛媛:她的表演将日常琐碎升华为艺术真实
  • 特朗普集团在越南开建度假村,探讨在胡志明市建摩天大楼
  • 中外科研人员合作揭开固态电池短路成因
  • 马斯克:大幅削减政治支出,仍将执掌特斯拉至少5年,除非去世
  • 关税战导致中国商品冲击周边市场?“对美出口减少并未导致对东盟出口激增”