ESP32学习笔记_FreeRTOS(5)——Mutex

摘要(From AI):
这篇博客内容围绕 FreeRTOS 中的**互斥量(Mutex)递归互斥量(Recursive Mutex)**的使用进行了详细的介绍。整体结构清晰,涵盖了互斥量的基本概念、使用方式以及与其他同步机制(如二进制信号量)的比较,还提供了两段示例代码,演示了互斥量和递归互斥量在任务同步中的应用

前言:本文档是本人在依照B站UP:Michael_ee的视频教程进行学习时所做的学习笔记,可能存在疏漏和错误,如有发现,望指正。

文章目录

    • Mutex
      • xSemaphoreCreateMutex()
      • Example Code:Mutex Synchronization with Task Priorities in FreeRTOS
    • Recursive Mutex
      • xSemaphoreCreateRecursiveMutex()
      • xSemaphoreTakeRecursive()
      • xSemaphoreGiveRecursive()
      • Example Code:Recursive Mutex Synchronization Between Tasks in FreeRTOS

参考资料
Michael_ee 视频教程
freeRTOS官网
espressif 在线文档


Mutex

当一个任务持有互斥量时,如果另一个更高优先级的任务尝试获取同一个互斥量,持有该互斥量的任务的优先级会被提升到另一个试图获得当前互斥量的任务的优先级,以便使高优先级任务能够获取该互斥量。这种优先级提升被称为“优先级继承”,当互斥量被释放时,任务会恢复到原来的优先级

获取互斥量的任务必须始终归还互斥量,否则其他任务将无法获取该互斥量

和二进制变量的主要区别:

二进制信号量用于同步时,获取信号量(“take”)后,不需要再“归还”它。任务同步的实现是通过一个任务或中断“给予”信号量,另一个任务“获取”信号量

如果一个低优先级任务获取了二进制信号量,那么高优先级任务只能等待

互斥量则会使用优先级继承来解决这种情况,确保低优先级任务能够尽快释放互斥量(让当前任务尽快完成)

xSemaphoreCreateMutex()

创建互斥锁类型信号量,并返回可引用互斥锁的句柄

每个互斥类型的信号量都需要少量的RAM来保存信号量的状态。如果使用xSemaphoreCreateMutex()创建互斥锁,则需要内存从FreeRTOS堆中自动分配

#include “FreeRTOS.h”
#include “semphr.h”

SemaphoreHandle_t xSemaphoreCreateMutex( void );

返回值

SemaphoreHandle_t​成功创建信号量,返回值是一个句柄,通过它可以引用创建的信号量

Others​如果由于没有足够的堆内存供FreeRTOS分配,信号量数据结构而无法创建信号量

Example Code:Mutex Synchronization with Task Priorities in FreeRTOS

Task1 获取到互斥量后,Task2 进入死循环,由于 Task2 优先级比 Task1 高,此时 Task1 无法运行;一段时间后 Task3 尝试获取互斥量,但此时互斥量还在 Task1,因此 Task1 的优先级被调整至和 Task3 同优先级,比 Task2 高,可以继续运行;当 Task1 运行完毕释放互斥量时,Task3 获取互斥量,此时 Task1 被 Task2 卡住,无法再运行

有关xSemaphoreGive()的用法,详见ESP32学习笔记_FreeRTOS(4)——Semaphore
有关任务优先级,详见ESP32学习笔记_FreeRTOS(1)——Task的创建和使用

由于在ESP32、ESP32-S3 等双核 MCU 上,FreeRTOS对任务进行双核调度,此时若 Task1 和 Task2 分别处于不同的核心,Task2 无法卡住 Task1 ,需要将所有任务创建在同一个核心上才能实现目标现象

虽然 Task2 会因为出发看门狗被重启,但是不影响本示例代码进行的实验

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

#include "freeRTOS/semphr.h"

SemaphoreHandle_t mutexHandle; // 创建一个互斥信号量句柄

void Task1(void *pvParam)
{
    BaseType_t Status;

    while (true)
    {
        printf("Task1 is running\n");
        Status = xSemaphoreTake(mutexHandle, 1000);
        if (Status == pdPASS)
        {
            printf("Task1 get the mutex\n");
            for (int i = 0; i < 50; i++)
            {
                printf("i in task1: %d\n", i);
                vTaskDelay(pdMS_TO_TICKS(1000));
            }
            xSemaphoreGive(mutexHandle);
            vTaskDelay(pdMS_TO_TICKS(5000));
        }
        else
        {
            printf("Task1 failed to get the mutex\n");
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
}

void Task2(void *pvParam)
{
    printf("Task2 is running\n");
    vTaskDelay(pdMS_TO_TICKS(1000)); // 给 Task1 一些时间来获取互斥信号量

    while (true)
    {
        ; // 任务 2 会直接把整个程序卡住,只有比它优先级高的任务才能执行
        // vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void Task3(void *pvParam)
{
    BaseType_t Status;
    printf("Task3 is running\n");
    vTaskDelay(pdMS_TO_TICKS(1000));

    while (true)
    {
        Status = xSemaphoreTake(mutexHandle, 1000); // Task3 尝试获取互斥信号量
                                                    // 将会失败,因为 Task1 已经获取了互斥信号量
                                                    // 将 Task1 的优先级升高至 Task3 的优先级
                                                    // 此时 Task1 继续运行
        if (Status == pdPASS)
        {
            printf("Task3 get the mutex\n");
            for (int i = 0; i < 10; i++)
            {
                printf("i in task3: %d\n", i);
                vTaskDelay(pdMS_TO_TICKS(1000));
            }
            xSemaphoreGive(mutexHandle);
            vTaskDelay(pdMS_TO_TICKS(5000));
        }
        else
        {
            printf("Task3 failed to get the mutex\n");
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
}

void app_main(void)
{
    mutexHandle = xSemaphoreCreateMutex(); // 创建一个互斥信号量

    vTaskSuspendAll(); // 挂起任务调度

    xTaskCreatePinnedToCore(Task1, "Task1", 1024 * 5, NULL, 1, NULL, 0); // 由于 ESP32-S3 的双核调度,需要将所有任务创建在同一个核心上才能实现目标现象
    xTaskCreatePinnedToCore(Task2, "Task2", 1024 * 5, NULL, 2, NULL, 0);
    xTaskCreatePinnedToCore(Task3, "Task3", 1024 * 5, NULL, 3, NULL, 0);

    xTaskResumeAll(); // 恢复任务调度
}

Recursive Mutex

递归互斥锁是指在调用时,已经获取了当前互斥锁的任务可以继续多次获取互斥锁用于处理不同数据(如占用了一个资源后接着占用下一个资源,使用普通的二进制变量或互斥锁需要通过创建多个变量来实现,而使用递归互斥锁则只需再获取一次即可,释放时也只需释放相同的次数)

xSemaphoreCreateRecursiveMutex()

创建递归互斥锁类型的信号量,并返回可引用递归互斥锁的句柄

#include “FreeRTOS.h”
#include “semphr.h”

SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

返回值

SemaphoreHandle_t​创建互斥锁成功,返回值是一个句柄,通过它可以引用创建的互斥锁

Others​如果由于没有足够的堆内存供FreeRTOS分配,信号量数据结构而无法创建信号量

xSemaphoreTakeRecursive()

获取一个递归互斥锁类型的信号量,该信号量之前已经使用xSemaphoreCreateRecursiveMutex()创建

#include “FreeRTOS.h”
#include “semphr.h”

BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex, TickType_t xTicksToWait );

参数

xMutex​要获取的信号量

xTicksToWait​任务等待信号量可用的最大时间(以 FreeRTOS 系统时钟节拍为单位)

返回值

  • pdPASS​信号量获取成功
  • pdFAIL​信号量获取失败

xSemaphoreGiveRecursive()

释放一个递归互斥锁类型的信号量

#include “FreeRTOS.h”
#include “semphr.h”

BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex );

参数

xMutex​要释放的信号量

xTicksToWait​任务等待信号量可用的最大时间(以 FreeRTOS 系统时钟节拍为单位)

返回值

  • pdPASS​信号量释放成功
  • pdFAIL​信号量释放失败

Example Code:Recursive Mutex Synchronization Between Tasks in FreeRTOS

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

#include "freeRTOS/semphr.h"

SemaphoreHandle_t mutexHandle; // 创建一个互斥信号量句柄

void Task1(void *pvParam)
{
    printf("Task1 is running\n");
    vTaskDelay(pdMS_TO_TICKS(1000));

    while (true)
    {
        printf("A new loop for task1\n");

        printf("Task1 is running\n");
        xSemaphoreTakeRecursive(mutexHandle, portMAX_DELAY);
        printf("Task1 get A\n");
        for (int i = 0; i < 10; i++)
        {
            printf("i for A in task1: %d\n", i);
            vTaskDelay(pdMS_TO_TICKS(1000));
        }

        xSemaphoreTakeRecursive(mutexHandle, portMAX_DELAY);
        printf("Task1 get B\n");
        for (int i = 0; i < 10; i++)
        {
            printf("i for B in task1: %d\n", i);
            vTaskDelay(pdMS_TO_TICKS(1000));
        }

        printf("Task1 release B\n");
        xSemaphoreGiveRecursive(mutexHandle);
        vTaskDelay(pdMS_TO_TICKS(3000));

        printf("Task1 release A\n");
        xSemaphoreGiveRecursive(mutexHandle);
        vTaskDelay(pdMS_TO_TICKS(3000));
        // 只有当 Task1 连续释放两次信号量的时候,Task2 才能获取到信号量
    }
}

void Task2(void *pvParam)
{
    printf("Task2 is running\n");
    vTaskDelay(pdMS_TO_TICKS(1000));

    while (true)
    {
        printf("A new loop for task2\n");

        xSemaphoreTakeRecursive(mutexHandle, portMAX_DELAY);
        printf("Task2 get A\n");
        for (int i = 0; i < 10; i++)
        {
            printf("i for A in task2: %d\n", i);
            vTaskDelay(pdMS_TO_TICKS(1000));
        }

        printf("Task2 release A\n");
        xSemaphoreGiveRecursive(mutexHandle);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

void app_main(void)
{
    mutexHandle = xSemaphoreCreateRecursiveMutex(); // 创建一个递归互斥信号量

    vTaskSuspendAll();

    xTaskCreatePinnedToCore(Task1, "Task1", 1024 * 5, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(Task2, "Task2", 1024 * 5, NULL, 2, NULL, 0);

    xTaskResumeAll();
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

osg中实现模型的大小、颜色、透明度的动态变化

以博饼状模型为对象,实现了模型大小、颜色、透明度的动态变化。 需要注意的是一点: // 创建材质对象osg::ref_ptr<osg::Material> material = new osg::Material;material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0, 1.0, 0.0, 0.5));// 获取模型的…

VSCode使用纪要

1、常用快捷键 1&#xff09;注释 ctrl? 单行注释&#xff0c; altshifta 块注释&#xff0c; 个人测试&#xff0c;ctrl? 好像也能块注释 2&#xff09;开多个项目 可以先开一个新窗口&#xff0c;再新窗口打开另一个项目&#xff0c;这时就是同时打开多个项目了。 打开…

Jmeter 简单使用、生成测试报告(一)

一、下载Jmter 去官网下载&#xff0c;我下载的是apache-jmeter-5.6.3.zip&#xff0c;解压后就能用。 二、安装java环境 JMeter是基于Java开发的&#xff0c;运行JMeter需要Java环境。 1.下载JDK、安装Jdk 2.配置java环境变量 3.验证安装是否成功&#xff08;java -versio…

Linux 服务器挖矿木马防护实战:快速切断、清理与加固20250114

Linux 服务器挖矿木马防护实战&#xff1a;快速切断、清理与加固 引言 挖矿木马作为一种常见的恶意软件&#xff0c;对服务器资源和安全构成严重威胁。据安全机构统计&#xff0c;2023 年全球约 45%的 Linux 服务器遭受过挖矿木马攻击&#xff0c;平均每台被感染服务器每月造…

Linux Kernel 之十 详解 PREEMPT_RT、Xenomai 的架构、源码、构建及使用

概述 现在的 RTOS 基本可以分为 Linux 阵营和非 Linux 阵营这两大阵营。非 Linux 阵营的各大 RTOS 都是独立发展,使用上也相对独立;而 Linux 阵营则有多种不同的实现方法来改造 Linux 以实现实时性要求。本文我们重点关注 Linux 阵营的实时内核实现方法! 本文我们重点关注 …

计算机网络(四)——网络层

目录 一、功能 二、IP数据报分片 三、DHCP动态主机配置协议 四、网络地址转换&#xff08;NAT&#xff09;技术 五、无分类编址CIDR 六、ARP地址解析协议 七、ICMP网际控制报文协议 八、IPv4和IPv6的区别 九、IPv4向IPv6的两种过渡技术——双栈协议和隧道技术 十、路由…

apache-skywalking-apm-10.1.0使用

apache-skywalking-apm-10.1.0使用 本文主要介绍如何使用apache-skywalking-apm-10.1.0&#xff0c;同时配合elasticsearch-8.17.0-windows-x86_64来作为存储 es持久化数据使用。 步骤如下&#xff1a; 一、下载elasticsearch-8.17.0-windows-x86_64 1、下载ES(elasticsear…

CVE-2025-22777 (CVSS 9.8):WordPress | GiveWP 插件的严重漏洞

漏洞描述 GiveWP 插件中发现了一个严重漏洞&#xff0c;该插件是 WordPress 最广泛使用的在线捐赠和筹款工具之一。该漏洞的编号为 CVE-2025-22777&#xff0c;CVSS 评分为 9.8&#xff0c;表明其严重性。 GiveWP 插件拥有超过 100,000 个活跃安装&#xff0c;为全球无数捐赠平…

【声音场景分类--论文阅读】

1.基于小波时频图特征在声音场景分类 基于小波时频图特征在声音场景分类任务中的表现 2.增强增强高效音频分类网络 https://arxiv.org/pdf/2204.11479v5 https://github.com/Alibaba-MIIL/AudioClassfication 音频分类网络如图4所示。在此阶段&#xff0c;主要重点是建立一…

java导出pdf文件

java导出pdf&#xff0c;前端下载 1、制作pdf模板2、获取pdf导出中文需要的文件3、实现4、前端发起请求并生成下载链接 使用注意点 因为原来制作的pdf表单内容过于复杂&#xff0c;下面代码只包含前两行的操作。 本次操作需要前端向后端发起请求&#xff0c;后端返回数据给前端…

1月13日学习

[HITCON 2017]SSRFme 直接给了源代码&#xff0c;题目名称还是ssrf&#xff0c;那么该题大概率就是SSRF的漏洞&#xff0c;进行代码审计。 <?php// 检查是否存在 HTTP_X_FORWARDED_FOR 头&#xff0c;如果存在&#xff0c;则将其拆分为数组&#xff0c;并将第一个 IP 地址…

在一个sql select中作多个sum并分组

有表如下&#xff1b; 单独的对某一个列作sum并分组&#xff0c;结果如下&#xff1b; 对于表的第7、8行&#xff0c;num1都有值&#xff0c;num2都是null&#xff0c;对num2列作sum、按id分组&#xff0c;结果在id为4的行会显示一个null&#xff1b; 同时对2个列作sum&#x…

[Deep Learning] Anaconda+CUDA+CuDNN+Pytorch(GPU)环境配置-2025

文章目录 [Deep Learning] AnacondaCUDACuDNNPytorch(GPU)环境配置-20250. 引子1. 安装Anaconda1.1 安装包下载&#xff1a;1.2 启用安装包安装1.3 配置(系统)环境变量1.4 验证Anaconda是否安装完毕1.5 Anaconda换源 2. 安装CUDACuDNN2.1 判断本机的CUDA版本2.2 下载适合自己CU…

不需要配置文件实现Javaweb项目的启动

1.首先看一下web.xml主要配置内容 <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://xm…

【网络篇】IP知识

IPv4首部与IPv6首部 IPv4相对于IPv6的好处&#xff1a; 1.IPv6可自动配置&#xff0c;即使没有DHCP服务器也可以实现自动分配IP地址&#xff0c;实现即插即用。 2.IPv6包首部长度采用固定40字节&#xff0c;删除了选项字段&#xff0c;以及首部校验和&#xff0c;简化了首部…

MyBatis核心流程

目录 数据处理的发展 MyBatis概述 ​编辑 MyBatis核心流程 观察测试类 重要对象和流程 SqlSessionFactory [初始化] 创建SqlSession会话对象 创建XxxMapper[代理]对象 执行SQL操作 [复杂一丢丢] ​编辑 数据处理的发展 1.原生JDBC 2. DBUtils工具类 [jdbctemp..] 3. …

低代码独特架构带来的编译难点及多线程解决方案

前言 在当今软件开发领域&#xff0c;低代码平台以其快速构建应用的能力&#xff0c;吸引了众多开发者与企业的目光。然而&#xff0c;低代码平台独特的架构在带来便捷的同时&#xff0c;也给编译过程带来了一系列棘手的难点。 一&#xff0c;低代码编译的难点 &#xff08;1…

Spring Security单点登录

本文介绍了Spring Security单点登录的概念和基本原理。单点登录是指用户只需登录一次&#xff0c;即可在多个相互信任的系统中实现无缝访问和授权。通过Spring Security框架的支持&#xff0c;可以实现有效的用户管理和权限控制。最后&#xff0c;本文提供了实际应用案例&#…

查找某个年龄段的用户信息TCP头格式为什么需要 TCP 协议? TCP 工作在哪一层?

查找某个年龄段的用户信息 select device_id,gender,age from user_profile where age>20 and age<23; TCP头格式 序列号&#xff1a;在建立连接时由计算机生成的随机数作为其初始值&#xff0c;通过 SYN 包传给接收端主机&#xff0c;每发送一次数据&#xff0c;就「累…

后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权

目录 后端注册拦截器 实现对 WebMvcConfigurer 接口的类实现 静态变量 方法重写 注册 Spring Framework拦截器 Sa-Token中SaServletFilter拦截器 思考 为什么使用两个拦截器 1. Spring Framework 拦截器 2. SaServletFilter 为什么要注册两个拦截器&#xff1f; 总结 …