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();
}
相关推荐
摇滚侠2 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
小向是个Der3 小时前
嵌入式进阶——MCU上电后的动作
c语言·单片机·mcu·学习·链接脚本
silvamux_5 小时前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
bkspiderx5 小时前
基于Socket的广播通信及C语言实现详解
c语言·开发语言·socket·广播·socket的广播通信
蒸蒸yyyyzwd5 小时前
kafka、redis、mysql不熟悉问题总结
笔记·八股
hgy89696 小时前
深度学习(小白笔记)——迁徙
人工智能·笔记·深度学习
摇滚侠6 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 11
java·笔记·python
辣知7 小时前
辣知·化智93 被历史尘封的鬻子
学习
每天题库8 小时前
西藏安全员 A 证备考真题题库 强化重点知识刷题提升通过率
学习·安全·考试·题库·考证
Be for thing8 小时前
PyTorch基础|Tensor张量详解,张量创建、数据类型与常用操作
pytorch·学习·1024程序员节