摘要(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();
}