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();
}
相关推荐
秦.赢渠梁19 小时前
各种通信(三):GPS模块数据解析
c语言
摇滚侠19 小时前
Spring Boot 3零基础教程,WEB 开发 HTTP 缓存机制 笔记29
spring boot·笔记·缓存
大白的编程日记.19 小时前
【Linux学习笔记】线程同步与互斥之生产者消费者模型
linux·笔记·学习
新子y19 小时前
【小白笔记】strip的含义
笔记·python
好奇龙猫20 小时前
AI学习:SPIN -win-安装SPIN-工具过程 SPIN win 电脑安装=accoda 环境-第五篇:代码修复]
人工智能·学习
MIXLLRED20 小时前
YOLO学习——训练进阶和预测评价指标
深度学习·学习·yolo
摇滚侠20 小时前
Spring Boot 3零基础教程,WEB 开发 内容协商 接口返回 YAML 格式的数据 笔记35
spring boot·笔记·后端
Chunyyyen20 小时前
【第十八周】自然语言处理的学习笔记03
笔记·学习·自然语言处理
聪明的笨猪猪20 小时前
Java JVM “类加载与虚拟机执行” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
心.c21 小时前
如何学习Lodash源码?
前端·javascript·学习