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

摘要(From AI):

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

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

文章目录

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


Mutex

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

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

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

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

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

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

xSemaphoreCreateMutex()

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

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

c 复制代码
#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 会因为出发看门狗被重启,但是不影响本示例代码进行的实验

c 复制代码
#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()

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

c 复制代码
#include "FreeRTOS.h"
#include "semphr.h"

SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

返回值

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

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

xSemaphoreTakeRecursive()

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

c 复制代码
#include "FreeRTOS.h"
#include "semphr.h"

BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex, TickType_t xTicksToWait );

参数

xMutex​要获取的信号量

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

返回值

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

xSemaphoreGiveRecursive()

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

c 复制代码
#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

c 复制代码
#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();
}
相关推荐
byte轻骑兵18 分钟前
【0x003A】HCI_Write_Current_IAC_LAP命令详解
c语言·蓝牙·通信协议·hci
YunB西风英1 小时前
(STM32笔记)十二、DMA的基础知识与用法 第二部分
笔记·stm32·嵌入式硬件·学习·dma·嵌入式
大邳草民1 小时前
Math Reference Notes: 矩阵基础
笔记·线性代数·矩阵
墨楠。1 小时前
数据结构学习记录-线性表
数据结构·学习
东京老树根1 小时前
Excel 技巧09 - 通过IF筛选数据,并给对象单元格赋值背景色 (★)
笔记·学习·excel
BoBoo文睡不醒2 小时前
Luggage Lock( The 2021 ICPC Asia Shenyang Regional Contest )
笔记
羊村懒哥2 小时前
haproxy+httpd网站架构,实现负载均衡实验笔记
笔记·架构·负载均衡
边城仔2 小时前
【MySQL数据库】JDBC总结
数据库·学习·mysql·jdbc·orm·连接池·basedao
大丈夫立于天地间2 小时前
OSPF - 路由过滤的几种方法
网络·网络协议·学习·算法·智能路由器·信息与通信
AI趋势预见3 小时前
GARCH指导的神经网络在金融市场波动性预测中的应用
人工智能·深度学习·神经网络·学习·机器学习·金融