【FreeRTOS(二)】FreeRTOS新手入门——计数型信号量和二进制信号量的基本使用并附代码解析

简介: 【FreeRTOS(二)】FreeRTOS新手入门——计数型信号量和二进制信号量的基本使用并附代码解析

一、信号量概述

信号量(Semaphore):常用于任务的同步,通过该信号,就能够控制某个任务的执行。

信号量分两种:二进制信号量和计数型信号量。


二、计数型信号量

计数型信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但会限制任务的最大数目,访问的任务数达到可支持的最大数目时,会阻塞其他试图获取该信号量的任务,直到有任务释放了信号量。

例如:某个资源限定只能有3个任务访问,那么第4个任务访问的时候,会因为获取不到信号量而进入阻塞,等到有任务(比如任务1)释放掉该资源的时候,第4个任务才能获取到信号量从而进行资源的访问。其运行机制具体见下图


再举个形象点的例子:

有三个停车位,我停车,停车位-1,我开车走了,停车位+1。申请资源就是P操作,释放资源就是V操作



三、二进制信号量

二进制信号量和计数型信号量的唯一区别就是计数值的最大值被限定为1。

要想访问资源需要先“take”信号量,让计数值减1,用完资源后,“give”信号量,让计数值加1


四、信号量函数API

1、创建信号量

使用信号量之前,要先创建,得到一个句柄,使用信号量时,要使用句柄来表明使用哪个信号量。

二进制信号量和计数型信号量创建函数不同

创建二进制信号量函数原型:

静态创建:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
动态创建:
SemaphoreHandle_t xSemaphoreCreateBinary( void );

创建计数型信号量函数原型:

静态创建:
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, 
UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
uxMaxCount: 最大计数值 
uxInitialCount: 初始计数值
pxSemaphoreBuffer: StaticSemaphore_t 结构体指针 
返回值: 返回句柄,非NULL表示成功

动态创建:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, 
UBaseType_t uxInitialCount);

2、删除一个信号量

vSemaphoreDelete()用于删除一个信号量,包括二进制信号量、计数信号量、互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。

void vSemaphoreDelete(SemaphoreHandle_t xSemaphore)
xSemaphore——信号量句柄

3、信号量释放

xSemaphoreGive()是一个用于释放信号量的宏,真正的实现过程是调用消息队列通用发送函数。释放的信号量对象必须是已经被创建的。可以用于二进制信号量、计数型信号量、互斥信号量的释放。

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

4、信号量获取

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, 
TickType_t xTicksToWait);

xTicksToWait——如果无法马上获得信号量,阻塞一会。0不阻塞,马上返回


五、示例代码

1、使用二进制信号量来同步

创建两个任务,一个用于释放信号量,一个用于获取信号量。

#include "FreeRTOS.h"
#include "task.h"
#include "xsc_test.h"
#include <stdio.h>
#include <kernel/dpl/ClockP.h>


StackType_t xscStack1[1024] __attribute__((aligned(32)));
StackType_t xscStack2[1024] __attribute__((aligned(32)));
StaticTask_t    xscTaskObj1;
StaticTask_t    xscTaskObj2;

void vSendTask(void *pvParameters)
{
    int cnt_ok = 0;
    int cnt_err = 0;
    const TickType_t xTicksToWait = pdMS_TO_TICKS(10UL);

    while (1)
    {
        for (int i = 0; i < 3; i++)
        {
            if (xSemaphoreGive(xBinarySemaphore) == pdTRUE)
            {
                DebugP_log("Give xBinarySemaphore %d time: OK\r\n", cnt_ok++);
            }
            else
            {
                DebugP_log("Give xBinarySemaphore %d time: ERR\r\n", cnt_err++);
            }
        }
        vTaskDelay(xTicksToWait);
    }
}

void vRecvTask(void *argc)
{
    int cnt_ok = 0;
    int cnt_err = 0;
    while (1)
    {
        if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE)
        {
            DebugP_log("Get xBinarySemaphore OK:%d\r\n", cnt_ok++);
        }
        else
        {
            DebugP_log("Get xBinarySemaphore ERR:%d\r\n", cnt_err++);
        }
    }
}

//Xsc_TestInit这个函数我在main函数调用了,就相当于main函数就行
void Xsc_TestInit(void)
{
    //创建二进制信号量
    xBinarySemaphore = xSemaphoreCreateBinary();    
    if (xBinarySemaphore != NULL)
    {
        //释放信号量
        xTaskCreateStatic(vSendTask, "vSendTask", 1024, NULL, 2, xscStack1, &xscTaskObj1);
        //获取信号量
        xTaskCreateStatic(vRecvTask, "vRecvTask", 1024, NULL, 1, xscStack2, &xscTaskObj2);
    }
    else
    {
        DebugP_log("xSemaphoreCreateBinary Failed!\r\n ");
    }
}


代码解释:

连续三次释放信号量,只有第一次能成功,然后发送任务阻塞,接收任务执行,获得信号量阻塞。在vTaskDelay退出之前都是运行的空闲任务。发送任务再次执行,连续三次释放信号量,只有第一次能成功,发送任务再阻塞,接受任务唤醒,打印OK。

demo演示:

2、使用计数型信号量

创建了一个计数型信号量,最大计数值为3,初始值为0,然后创建两个任务,一个用于释放信号量,一个用于获取信号量。

#include "FreeRTOS.h"
#include "task.h"
#include "xsc_test.h"
#include <stdio.h>
#include <kernel/dpl/ClockP.h>


StackType_t xscStack1[1024] __attribute__((aligned(32)));
StackType_t xscStack2[1024] __attribute__((aligned(32)));
StaticTask_t    xscTaskObj1;
StaticTask_t    xscTaskObj2;

void vSendTask(void *pvParameters)
{
    int cnt_ok = 0;
    int cnt_err = 0;
    const TickType_t xTicksToWait = pdMS_TO_TICKS(10UL);

    while (1)
    {
        for (int i = 0; i < 4; i++)
        {
            
            if (xSemaphoreGive(xCountingSemaphore) == pdTRUE)
            {
                DebugP_log("Give xCountingSemaphore %d time: OK\r\n", cnt_ok++);
            }
            else
            {
                DebugP_log("Give xCountingSemaphore %d time: ERR\r\n", cnt_err++);
            }
        }
        vTaskDelay(xTicksToWait);
    }
}

void vRecvTask(void *argc)
{
    int cnt_ok = 0;
    int cnt_err = 0;
    while (1)
    {
        if (xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE)
        {
            DebugP_log("Get xCountingSemaphore OK:%d\r\n", cnt_ok++);
        }
        else
        {
            DebugP_log("Get xCountingSemaphore ERR:%d\r\n", cnt_err++);
        }
    }
}

//Xsc_TestInit这个函数我在main函数调用了,就相当于main函数就行
void Xsc_TestInit(void)
{
    //创建计数型信号量
    xCountingSemaphore = xSemaphoreCreateCounting(3, 0);
    if (xCountingSemaphore != NULL)
    {
        //释放信号量
        xTaskCreateStatic(vSendTask, "vSendTask", 1024, NULL, 2, xscStack1, &xscTaskObj1);
        //获取信号量
        xTaskCreateStatic(vRecvTask, "vRecvTask", 1024, NULL, 1, xscStack2, &xscTaskObj2);
    }
    else
    {
        DebugP_log("xSemaphoreCreateBinary Failed!\r\n ");
    }
}


代码解释:

发送任务连续释放4个信号量,只有前面3次成功,第4次失败,阻塞,接受任务唤醒,得到三个信号量,阻塞。发送任务再次执行。

demo演示:

目录
打赏
0
0
0
0
27
分享
相关文章
React音频播放器样式自定义全解析:从入门到避坑指南
在React中使用HTML5原生&lt;audio&gt;标签时,开发者常面临视觉一致性缺失、样式定制局限和交互体验割裂等问题。通过隐藏原生控件并构建自定义UI层,可以实现完全可控的播放器视觉风格,避免状态不同步等典型问题。结合事件监听、进度条拖拽、浏览器兼容性处理及性能优化技巧,可构建高性能、可维护的音频组件,满足跨平台需求。建议优先使用成熟音频库(如react-player),仅在深度定制需求时采用原生方案。
143 12
Javaweb之Mybatis入门程序的详细解析
本文详细介绍了一个MyBatis入门程序的创建过程,从环境准备、Maven项目创建、MyBatis配置、实体类和Mapper接口的定义,到工具类和测试类的编写。通过这个示例,读者可以了解MyBatis的基本使用方法,并在实际项目中应用这些知识。
121 11
Python入门:6.深入解析Python中的序列
在 Python 中,**序列**是一种有序的数据结构,广泛应用于数据存储、操作和处理。序列的一个显著特点是支持通过**索引**访问数据。常见的序列类型包括字符串(`str`)、列表(`list`)和元组(`tuple`)。这些序列各有特点,既可以存储简单的字符,也可以存储复杂的对象。 为了帮助初学者掌握 Python 中的序列操作,本文将围绕**字符串**、**列表**和**元组**这三种序列类型,详细介绍其定义、常用方法和具体示例。
Python入门:6.深入解析Python中的序列
Python入门:2.注释与变量的全面解析
在学习Python编程的过程中,注释和变量是必须掌握的两个基础概念。注释帮助我们理解代码的意图,而变量则是用于存储和操作数据的核心工具。熟练掌握这两者,不仅能提高代码的可读性和维护性,还能为后续学习复杂编程概念打下坚实的基础。
Python入门:2.注释与变量的全面解析
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
369 5
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
256 3
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
1988 1
C++类与对象深度解析(一):从抽象到实践的全面入门指南
C++类与对象深度解析(一):从抽象到实践的全面入门指南
102 8

推荐镜像

更多
  • DNS
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等

    登录插画

    登录以查看您的控制台资源

    管理云资源
    状态一览
    快捷访问