原创 7. ESP32开发之freeRTOS的互斥量

2025-5-18 20:54 248 0 分类: MCU/ 嵌入式 文集: ESP32
  • 什么是互斥量
  • 互斥量的应用场合
  • 互斥量的API函数
  • 基本代码结构
  • 互斥量使用举例
  • 递归锁
  • 递归锁举例
  • 总结

什么是互斥量

在freeRTOS中,多个任务访问一块共享资源,会产生竞争现象。

比如马路上只有一个很早以前的电话亭,A、B都想要打电话,然后他们就开始打架了。但是如果A先进去了然后把门锁了,B想进去打电话的话只能在外面等,必须等到A把门锁打开。

fig:

互斥量的应用场合

像freeRTOS的多任务系统,任务A正在使用某个资源,还没用完的时候,任务B也来使用,就可能会导致问题。

就比如串口,任务A正在用串口发送数据,此时任务B也来用这个串口发送数据,这样就会导致数据混乱。

简而言之,多任务使用共享资源的情况下,就需要使用互斥量了。

这里有个特别强调的注意点:按照正常的情况来说,只要任务A获取了互斥量,其他任务都无法释放互斥量才对。但是freeRTOS中并没有实现,其他任务也可释放互斥量。所以在freeRTOS中,大家默认谁获取互斥量,就该谁释放。

互斥量的API函数

  • 创建互斥量
/*
返回值:正确返回SemaphoreHandle_t 变量,错误返回NULL
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
  • 获取
/*
参数:
xSemaphore : 创建的互斥量
xBlockTime : 获取锁等待时间,超时获取失败
返回值:
获取到,返回pdTRUE,反之,pdFALSE
*/
SemaphoreHandle_t xSemaphoreTake( xSemaphore, xBlockTime )
  • 释放
/*
参数:
xSemaphore : 创建的互斥量
返回值:
释放成功,返回pdTRUE,反之,pdFALSE
*/
SemaphoreHandle_t xSemaphoreGive( xSemaphore, xBlockTime )


基本代码结构

if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){
/*资源处理代码,这里可以称之为临界区*/
xSemaphoreGive(xSemaphore);
}


互斥量使用举例

  • 首先举一个错误例子,这样会有个对比
void taskA(void* pvPram)
{
while(1){
for(int i=0;i<5;i++){
num++;
ESP_LOGI("taskA","num is %d",num);
}

vTaskDelay(pdMS_TO_TICKS(1000));
}
}

void taskB(void* pvPram)
{
while(1){
num++;
ESP_LOGI("taskB","num is %d",num);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

void app_main(void)
{
xSemaphore=xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1);
}

打印结果如下:代码目的是taskA执行过程中,num值是连续增加的,然而打印中会发现,taskA打印并不连续,会被taskB抢占。

fig:

  • 使用mutex函数
/**
* Copyright (C) 2024-2034 HalfMoon2.
* All rights reserved.
*
* @file Filename without the absolute path
* @brief Brief description
* @author HalfMoon2
* @date 2025-05-07
* @version v0.1
*
* @revision history:
* 2025-05-07 - Initial version.
*/
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <esp_log.h>

SemaphoreHandle_t xSemaphore;
BaseType_t num=0;

void taskA(void* pvPram)
{
while(1){
/*如果在1S获取到互斥量,那么执行num++五次并打印 */
if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){
for(int i=0;i<5;i++){
num++;
ESP_LOGI("taskA","num is %d",num);
}
xSemaphoreGive(xSemaphore);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

void taskB(void* pvPram)
{
while(1){
if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){
num++;
ESP_LOGI("taskB","num is %d",num);
xSemaphoreGive(xSemaphore);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

void app_main(void)
{
xSemaphore=xSemaphoreCreateMutex();
/*为了产生竞争,两个任务优先级设置一样 */
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1);
}

打印结果:

fig:

分析:

从打印看,当任务A执行完五次num++后,才会轮到任务B执行一次。完全符合执行逻辑。

递归锁

  • 递归锁的引出

假设有两个互斥量M1、M2,taskA获取了M1,taskB获取了M2。然而taskA需要再获取M2才能执行下一步,且taskB也需要再获取M1才能执行下一步。这样就导致了taskA、taskB都无法释放互斥量。导致了死锁。

再假设有一个互斥量M1,任务A获取了M1后,执行一个函数,然而此函数中也获取了M1,此时任务A还没释放M1,这将导致任务A自己把自己锁了。

然而递归锁,可以多次获取,可以解决以上问题

  • 递归锁的函数
/*创建递归锁*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

/*获得 */
BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);

/*释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );


递归锁举例

  • 死锁举例
SemaphoreHandle_t xSemaphore;
BaseType_t num=0;

void func(BaseType_t *a)
{
//获取互斥量等待时间设置为portMAX_DELAY,因为taskA为释放互斥量,导致此处一直等待
if(xSemaphoreTakeRecursive(xSemaphore,portMAX_DELAY)==pdTRUE){
*a=*a+1;
xSemaphoreGiveRecursive(xSemaphore);
}
}


void taskA(void* pvPram)
{
while(1){
/*如果在1S获取到互斥量,那么执行num++五次并打印 */
if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){
func(&num);
ESP_LOGI("taskA","num is %d",num);

xSemaphoreGive(xSemaphore);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}


void app_main(void)
{
xSemaphore=xSemaphoreCreateMutex();
/*为了产生竞争,两个任务优先级设置一样 */
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1);
}

打印如下:程序将一直锁着,无法执行

fig:

  • 使用递归锁
SemaphoreHandle_t xSemaphore;
BaseType_t num=0;

void func(BaseType_t *a)
{
//获取互斥量等待时间设置为portMAX_DELAY,因为taskA为释放互斥量,导致此处一直等待
if(xSemaphoreTakeRecursive(xSemaphore,portMAX_DELAY)==pdTRUE){
*a=*a+1;
xSemaphoreGiveRecursive(xSemaphore);
}
}


void taskA(void* pvPram)
{
while(1){
/*如果在1S获取到互斥量,那么执行num++五次并打印 */
if(xSemaphoreTakeRecursive(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){
func(&num);
ESP_LOGI("taskA","num is %d",num);

xSemaphoreGiveRecursive(xSemaphore);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}


void app_main(void)
{
xSemaphore=xSemaphoreCreateRecursiveMutex();
/*为了产生竞争,两个任务优先级设置一样 */
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1);
}

打印如下:发现完全可以正常打印了

fig:

总结

  • 为了避免死锁,在中断中不要使用互斥量
  • 如果有嵌套使用互斥量,请使用递归锁,递归的层数由queueQUEUE_TYPE_RECURSIVE_MUTEX决定
  • 虽然freeRTOS没有实现谁锁谁开,但是开发过程中还是默认这么操作了

作者: 二月半, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-1862109.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
0
关闭 站长推荐上一条 /2 下一条