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

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 【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演示:

相关文章
|
1月前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
73 3
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
282 1
|
2月前
|
存储 编译器 C语言
C++类与对象深度解析(一):从抽象到实践的全面入门指南
C++类与对象深度解析(一):从抽象到实践的全面入门指南
50 8
|
2月前
|
JSON JavaScript 前端开发
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
89 0
|
2月前
|
存储 Java 开发者
【编程基础知识】 计算机中的数学魔法:二进制加减运算全解析
本文深入解析了计算机中二进制加减运算的原理,涵盖原码、反码和补码的概念及应用,结合具体示例,帮助读者理解计算机底层数学运算机制,适合Java开发者学习。
47 0
|
4月前
|
XML 存储 网络安全
ROS入门(二):launch文件解析
该文章是关于ROS入门的第二篇教程,详细解析了ROS中的launch文件,包括其运行方式、XML格式规范、标签使用、参数替代、条件属性以及通过简单和复杂案例来演示launch文件的使用,最后介绍了如何在参数服务器上设置参数。
ROS入门(二):launch文件解析
|
2月前
|
应用服务中间件 测试技术 nginx
Nginx入门 -- 解析Nginx中的基本概念:Keepalive
Nginx入门 -- 解析Nginx中的基本概念:Keepalive
112 0
|
4月前
|
JSON 前端开发 Java
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
【8月更文挑战第12天】SpringBootWeb极速入门-请求参数解析(02)
25 1
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
|
4月前
|
Java Shell Linux
【Linux入门技巧】新员工必看:用Shell脚本轻松解析应用服务日志
关于如何使用Shell脚本来解析Linux系统中的应用服务日志,提供了脚本实现的详细步骤和技巧,以及一些Shell编程的技能扩展。
57 0
【Linux入门技巧】新员工必看:用Shell脚本轻松解析应用服务日志
|
4月前
|
数据库 Windows
超详细步骤解析:从零开始,手把手教你使用 Visual Studio 打造你的第一个 Windows Forms 应用程序,菜鸟也能轻松上手的编程入门指南来了!
【8月更文挑战第31天】创建你的第一个Windows Forms (WinForms) 应用程序是一个激动人心的过程,尤其适合编程新手。本指南将带你逐步完成一个简单WinForms 应用的开发。首先,在Visual Studio 中创建一个“Windows Forms App (.NET)”项目,命名为“我的第一个WinForms 应用”。接着,在空白窗体中添加一个按钮和一个标签控件,并设置按钮文本为“点击我”。然后,为按钮添加点击事件处理程序`button1_Click`,实现点击按钮后更新标签文本为“你好,你刚刚点击了按钮!”。
289 0