别让你的ESP32任务打架!一文学会信号量和互斥量的交通规则--上

文章总结(帮你们节约时间)

  • 信号量和互斥量是ESP32多任务编程的核心同步机制,掌握它们就像学会了多线程世界的"交通规则"
  • 计数信号量适合资源池管理,二进制信号量用于任务同步,互斥量专门保护共享资源
  • 优先级反转和死锁是两大常见陷阱,但通过优先级继承和合理设计完全可以避免
  • Arduino IDE下的FreeRTOS API使用简单,但理解底层原理才能写出高效稳定的代码

你有没有遇到过这样的情况:ESP32项目中多个任务同时访问串口,结果打印出来的信息乱七八糟,像是外星人发来的密码?或者两个任务都在等待对方释放资源,最后谁也动不了,整个系统就这样"死"在那里?

这就是多任务编程中最经典的问题!就像十字路口没有红绿灯,车辆横冲直撞,不出事故才怪呢。而信号量(Semaphore)和互斥量(Mutex)就是我们的"交通信号灯",它们负责协调各个任务的执行顺序,确保系统井然有序。

ESP32作为一颗双核处理器,天生就具备多任务处理能力。当你在Arduino IDE中使用xTaskCreate()创建多个任务时,这些任务会并发执行,共享CPU时间片。但是,当多个任务需要访问同一个资源时,问题就来了!

想象一下,你有一个全局变量int counter = 0,两个任务都要对它进行递增操作。任务A读取counter的值(比如是100),正准备加1,这时任务B也读取了counter的值(还是100),然后任务A写入101,任务B也写入101。结果本来应该是102的counter,最终只变成了101!这就是典型的竞态条件(Race Condition)。

// 这是一个有问题的代码示例
int counter = 0;

void taskA(void *parameter) {
   
   
    for(;;) {
   
   
        counter++;  // 危险!非原子操作
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void taskB(void *parameter) {
   
   
    for(;;) {
   
   
        counter++;  // 危险!非原子操作
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

这就是为什么我们需要信号量和互斥量的原因!它们就像是代码世界的"门禁系统",确保在任何时刻只有被授权的任务才能访问关键资源。

基础概念:信号量和互斥量的本质差异

在深入学习之前,我们必须搞清楚信号量和互斥量的本质区别。很多初学者经常把它们搞混,就像把"借钱"和"排队"搞混一样。

**信号量(Semaphore)**就像是一个"令牌桶"。想象一下网吧的上机卡,网吧总共有20台电脑,就发放20张上机卡。顾客想上网就必须先拿到卡,用完后归还。如果卡都被拿光了,后来的顾客就得等待。信号量的值表示可用资源的数量,它可以是任意非负整数。

**互斥量(Mutex)**则更像是一把"独占锁"。想象一下公共厕所的门锁,同一时间只能有一个人使用,其他人必须在外面等待。互斥量只有两种状态:锁定或未锁定,专门用于保护临界区。

让我们用一个生动的比喻来理解:

  • 信号量像是停车场的车位。停车场有100个车位,就相当于计数为100的信号量。每来一辆车,可用车位减1;每走一辆车,可用车位加1。当车位满了,新来的车就得等待。

  • 互斥量像是单人卫生间的门锁。不管外面排了多少人,里面同时只能有一个人。这个人用完出来后,下一个人才能进去。

从技术角度来看,二者的区别在于:

  1. 语义不同:信号量用于资源计数和任务同步,互斥量专门用于互斥访问
  2. 所有权不同:信号量没有所有权概念,任何任务都可以释放;互斥量有所有权,只有获取者才能释放
  3. 优先级继承:互斥量支持优先级继承,信号量不支持
  4. 递归性:互斥量可以递归获取,信号量不行

在Arduino ESP32开发中,FreeRTOS提供了丰富的API来操作这些同步原语:

// 信号量相关API
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
SemaphoreHandle_t xSemaphoreCreateBinary(void);
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

// 互斥量相关API
SemaphoreHandle_t xSemaphoreCreateMutex(void);
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);

信号量深度解析:从计数到二进制的奇妙世界

计数信号量:资源池的智能管理者

计数信号量就像是一个智能的资源分配器。它维护着一个内部计数器,表示当前可用资源的数量。每当一个任务需要资源时,计数器减1;当任务释放资源时,计数器加1。

让我们来看一个实际的例子:假设你的ESP32项目需要管理3个SPI设备,但只有一条SPI总线。这时候计数信号量就派上用场了!

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

// 创建一个计数信号量,最大计数为3,初始计数为3
SemaphoreHandle_t spiSemaphore;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建计数信号量:最多3个任务可以同时使用SPI资源
    spiSemaphore = xSemaphoreCreateCounting(3, 3);
    
    if (spiSemaphore == NULL) {
   
   
        Serial.println("信号量创建失败!");
        return;
    }
    
    // 创建多个任务来模拟SPI设备访问
    xTaskCreate(spiTask, "SPI_Task_1", 2048, (void*)1, 1, NULL);
    xTaskCreate(spiTask, "SPI_Task_2", 2048, (void*)2, 1, NULL);
    xTaskCreate(spiTask, "SPI_Task_3", 2048, (void*)3, 1, NULL);
    xTaskCreate(spiTask, "SPI_Task_4", 2048, (void*)4, 1, NULL);
    xTaskCreate(spiTask, "SPI_Task_5", 2048, (void*)5, 1, NULL);
    
    Serial.println("所有任务创建完成!");
}

void spiTask(void *parameter) {
   
   
    int taskId = (int)parameter;
    
    for(;;) {
   
   
        // 尝试获取SPI资源,最多等待1秒
        if (xSemaphoreTake(spiSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
            Serial.printf("任务%d获得SPI资源,开始传输数据...\n", taskId);
            
            // 模拟SPI数据传输(耗时操作)
            vTaskDelay(2000 / portTICK_PERIOD_MS);
            
            Serial.printf("任务%d完成SPI传输,释放资源\n", taskId);
            
            // 释放SPI资源
            xSemaphoreGive(spiSemaphore);
        } else {
   
   
            Serial.printf("任务%d等待SPI资源超时!\n", taskId);
        }
        
        // 等待一段时间再次尝试
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void loop() {
   
   
    // 主循环可以做其他事情
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

运行这个程序,你会看到类似这样的输出:

所有任务创建完成!
任务1获得SPI资源,开始传输数据...
任务2获得SPI资源,开始传输数据...
任务3获得SPI资源,开始传输数据...
任务4等待SPI资源超时!
任务5等待SPI资源超时!
任务1完成SPI传输,释放资源
任务2完成SPI传输,释放资源
任务3完成SPI传输,释放资源
任务4获得SPI资源,开始传输数据...
任务5获得SPI资源,开始传输数据...

看到了吗?前3个任务同时获得了SPI资源,而任务4和5必须等待。这就是计数信号量的威力!它精确地控制了同时访问资源的任务数量。

但是,你可能会问:为什么不直接用一个全局变量来计数呢?答案是:原子性!信号量的获取和释放操作是原子的,不会被任务切换打断。而普通的变量操作可能会被中断,导致竞态条件。

二进制信号量:简单而强大的同步工具

二进制信号量就像是一个简化版的计数信号量,它的计数值只能是0或1。你可以把它想象成一个"开关"或者"标志位"。

二进制信号量最常用的场景是任务同步。比如,你希望任务A完成某个操作后,任务B才能开始执行。这时候二进制信号量就是最佳选择!

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t binarySemaphore;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建二进制信号量
    binarySemaphore = xSemaphoreCreateBinary();
    
    if (binarySemaphore == NULL) {
   
   
        Serial.println("二进制信号量创建失败!");
        return;
    }
    
    // 创建生产者和消费者任务
    xTaskCreate(producerTask, "Producer", 2048, NULL, 2, NULL);
    xTaskCreate(consumerTask, "Consumer", 2048, NULL, 1, NULL);
    
    Serial.println("生产者-消费者任务创建完成!");
}

void producerTask(void *parameter) {
   
   
    int dataCounter = 0;
    
    for(;;) {
   
   
        // 模拟数据生产过程
        dataCounter++;
        Serial.printf("生产者:正在生产数据 #%d...\n", dataCounter);
        vTaskDelay(2000 / portTICK_PERIOD_MS);
        
        Serial.printf("生产者:数据 #%d 生产完成!通知消费者\n", dataCounter);
        
        // 通知消费者数据已准备好
        xSemaphoreGive(binarySemaphore);
        
        // 等待一段时间再生产下一个数据
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void consumerTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("消费者:等待数据...");
        
        // 等待生产者的通知
        if (xSemaphoreTake(binarySemaphore, portMAX_DELAY) == pdTRUE) {
   
   
            Serial.println("消费者:收到通知,开始处理数据...");
            
            // 模拟数据处理过程
            vTaskDelay(1500 / portTICK_PERIOD_MS);
            
            Serial.println("消费者:数据处理完成!\n");
        }
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

这个例子展示了二进制信号量在任务同步中的应用。生产者任务生产数据后,通过xSemaphoreGive()通知消费者;消费者任务通过xSemaphoreTake()等待通知。

你会看到类似这样的输出:

生产者-消费者任务创建完成!
消费者:等待数据...
生产者:正在生产数据 #1...
生产者:数据 #1 生产完成!通知消费者
消费者:收到通知,开始处理数据...
消费者:数据处理完成!

消费者:等待数据...
生产者:正在生产数据 #2...
生产者:数据 #2 生产完成!通知消费者
消费者:收到通知,开始处理数据...
消费者:数据处理完成!

信号量的创建与配置策略

在ESP32的Arduino环境中,创建信号量有几种不同的方式,每种方式都有其特定的用途:

// 1. 创建计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(
    UBaseType_t uxMaxCount,     // 最大计数值
    UBaseType_t uxInitialCount  // 初始计数值
);

// 2. 创建二进制信号量
SemaphoreHandle_t xSemaphoreCreateBinary(void);

// 3. 使用静态内存创建信号量(高级用法)
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
    UBaseType_t uxMaxCount,
    UBaseType_t uxInitialCount,
    StaticSemaphore_t *pxSemaphoreBuffer
);

创建策略的选择原则:

  1. 资源池管理:使用计数信号量,最大计数等于资源数量
  2. 任务同步:使用二进制信号量,初始状态通常为空(计数为0)
  3. 事件通知:使用二进制信号量,可以替代传统的标志位
  4. 内存受限环境:考虑使用静态创建方式,避免堆内存碎片

让我们看一个更复杂的例子,展示如何在一个实际的IoT项目中使用信号量:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "WiFi.h"

// 信号量句柄
SemaphoreHandle_t wifiSemaphore;        // WiFi连接状态信号量
SemaphoreHandle_t sensorSemaphore;      // 传感器数据信号量
SemaphoreHandle_t uartSemaphore;        // 串口访问信号量

// 全局变量
float temperature = 0.0;
float humidity = 0.0;
bool wifiConnected = false;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建各种信号量
    wifiSemaphore = xSemaphoreCreateBinary();
    sensorSemaphore = xSemaphoreCreateBinary();
    uartSemaphore = xSemaphoreCreateMutex();  // 串口需要互斥访问
    
    if (wifiSemaphore == NULL || sensorSemaphore == NULL || uartSemaphore == NULL) {
   
   
        Serial.println("信号量创建失败!");
        return;
    }
    
    // 创建各种任务
    xTaskCreate(wifiTask, "WiFi_Task", 4096, NULL, 3, NULL);
    xTaskCreate(sensorTask, "Sensor_Task", 2048, NULL, 2, NULL);
    xTaskCreate(dataUploadTask, "Upload_Task", 4096, NULL, 1, NULL);
    xTaskCreate(displayTask, "Display_Task", 2048, NULL, 1, NULL);
    
    Serial.println("IoT系统启动完成!");
}

void wifiTask(void *parameter) {
   
   
    WiFi.begin("YourWiFiSSID", "YourPassword");
    
    for(;;) {
   
   
        if (WiFi.status() == WL_CONNECTED && !wifiConnected) {
   
   
            wifiConnected = true;
            safePrint("WiFi连接成功!");
            xSemaphoreGive(wifiSemaphore);  // 通知其他任务WiFi已连接
        } else if (WiFi.status() != WL_CONNECTED && wifiConnected) {
   
   
            wifiConnected = false;
            safePrint("WiFi连接断开!");
        }
        
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }
}

void sensorTask(void *parameter) {
   
   
    for(;;) {
   
   
        // 模拟读取传感器数据
        temperature = random(200, 350) / 10.0;  // 20.0 - 35.0°C
        humidity = random(300, 800) / 10.0;     // 30.0 - 80.0%
        
        safePrint("传感器数据更新:温度=" + String(temperature) + "°C, 湿度=" + String(humidity) + "%");
        
        // 通知其他任务数据已更新
        xSemaphoreGive(sensorSemaphore);
        
        vTaskDelay(10000 / portTICK_PERIOD_MS);  // 每10秒更新一次
    }
}

void dataUploadTask(void *parameter) {
   
   
    for(;;) {
   
   
        // 等待传感器数据更新
        if (xSemaphoreTake(sensorSemaphore, portMAX_DELAY) == pdTRUE) {
   
   
            // 等待WiFi连接
            if (xSemaphoreTake(wifiSemaphore, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
                safePrint("开始上传数据到云端...");
                
                // 模拟数据上传过程
                vTaskDelay(2000 / portTICK_PERIOD_MS);
                
                safePrint("数据上传完成!");
                
                // 重新释放WiFi信号量,让其他任务也能使用
                xSemaphoreGive(wifiSemaphore);
            } else {
   
   
                safePrint("WiFi未连接,跳过数据上传");
            }
        }
    }
}

void displayTask(void *parameter) {
   
   
    for(;;) {
   
   
        safePrint("=== 系统状态 ===");
        safePrint("WiFi状态: " + String(wifiConnected ? "已连接" : "未连接"));
        safePrint("当前温度: " + String(temperature) + "°C");
        safePrint("当前湿度: " + String(humidity) + "%");
        safePrint("================\n");
        
        vTaskDelay(15000 / portTICK_PERIOD_MS);  // 每15秒显示一次状态
    }
}

// 安全的串口打印函数
void safePrint(String message) {
   
   
    if (xSemaphoreTake(uartSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
        Serial.println("[" + String(millis()) + "] " + message);
        xSemaphoreGive(uartSemaphore);
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

这个例子展示了一个完整的IoT系统中信号量的使用:

  1. WiFi信号量:用于通知其他任务WiFi连接状态
  2. 传感器信号量:用于通知数据更新
  3. 串口互斥量:保护串口访问,避免输出混乱

信号量的获取与释放机制

信号量的获取和释放是多任务编程的核心操作。理解这些操作的内部机制,对于编写高效的并发程序至关重要。

获取信号量(Take)的过程:

BaseType_t xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,  // 信号量句柄
    TickType_t xTicksToWait        // 等待时间(时钟节拍数)
);

当任务调用xSemaphoreTake()时,FreeRTOS会执行以下步骤:

  1. 检查信号量计数:如果计数大于0,立即减1并返回成功
  2. 计数为0时的处理:如果计数为0且等待时间为0,立即返回失败
  3. 进入等待状态:如果计数为0且等待时间大于0,任务进入阻塞状态
  4. 超时处理:如果在指定时间内没有获取到信号量,返回超时错误

释放信号量(Give)的过程:

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

释放信号量的过程相对简单:

  1. 增加计数:将信号量计数加1(如果未达到最大值)
  2. 唤醒等待任务:如果有任务在等待此信号量,唤醒优先级最高的任务
  3. 任务切换:如果被唤醒的任务优先级更高,可能发生任务切换

让我们通过一个详细的例子来观察这个过程:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t testSemaphore;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建一个初始计数为0的二进制信号量
    testSemaphore = xSemaphoreCreateBinary();
    
    // 创建多个等待任务
    xTaskCreate(waitingTask, "Waiter_1", 2048, (void*)1, 1, NULL);
    xTaskCreate(waitingTask, "Waiter_2", 2048, (void*)2, 1, NULL);
    xTaskCreate(waitingTask, "Waiter_3", 2048, (void*)3, 1, NULL);
    
    // 创建信号量释放任务
    xTaskCreate(signalingTask, "Signaler", 2048, NULL, 2, NULL);
    
    Serial.println("信号量测试开始!");
}

void waitingTask(void *parameter) {
   
   
    int taskId = (int)parameter;
    
    for(;;) {
   
   
        Serial.printf("任务%d:开始等待信号量...\n", taskId);
        
        TickType_t startTime = xTaskGetTickCount();
        
        // 尝试获取信号量,最多等待5秒
        if (xSemaphoreTake(testSemaphore, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
            TickType_t endTime = xTaskGetTickCount();
            TickType_t waitTime = endTime - startTime;
            
            Serial.printf("任务%d:成功获取信号量!等待时间:%d ms\n", 
                         taskId, waitTime * portTICK_PERIOD_MS);
            
            // 模拟使用资源
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            
            Serial.printf("任务%d:使用完毕,准备下次等待\n", taskId);
        } else {
   
   
            Serial.printf("任务%d:等待信号量超时!\n", taskId);
        }
        
        // 等待一段时间再次尝试
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

void signalingTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("信号发送者:等待3秒后释放信号量...");
        vTaskDelay(3000 / portTICK_PERIOD_MS);
        
        Serial.println("信号发送者:释放信号量!");
        xSemaphoreGive(testSemaphore);
        
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

运行这个程序,你会看到类似这样的输出:

信号量测试开始!
任务1:开始等待信号量...
任务2:开始等待信号量...
任务3:开始等待信号量...
信号发送者:等待3秒后释放信号量...
信号发送者:释放信号量!
任务1:成功获取信号量!等待时间:3000 ms
任务1:使用完毕,准备下次等待
任务1:开始等待信号量...
信号发送者:等待3秒后释放信号量...
信号发送者:释放信号量!
任务2:成功获取信号量!等待时间:8000 ms
任务2:使用完毕,准备下次等待

从输出可以看出,每次只有一个任务能够获取到二进制信号量,其他任务必须等待。这就是信号量的"排队"机制!

等待时间的艺术:

等待时间的设置是一门艺术,需要在响应性和资源利用率之间找到平衡:

// 不同的等待策略
xSemaphoreTake(semaphore, 0);                    // 立即返回,不等待
xSemaphoreTake(semaphore, 100 / portTICK_PERIOD_MS);  // 等待100ms
xSemaphoreTake(semaphore, portMAX_DELAY);        // 永久等待
  • 立即返回(0):适用于非关键操作,失败了可以稍后重试
  • 有限等待:适用于有实时性要求的场景,避免任务长时间阻塞
  • 永久等待:适用于必须获取资源才能继续的场景,但要小心死锁

优先级反转问题的深入剖析

优先级反转是多任务系统中一个非常经典的问题,它就像是"小鬼当家"——低优先级任务居然能够阻塞高优先级任务!

什么是优先级反转?

想象这样一个场景:

  • 任务H(高优先级)需要访问某个共享资源
  • 任务L(低优先级)正在使用这个资源
  • 任务M(中等优先级)开始运行,抢占了任务L的CPU时间

结果就是:任务H被任务L阻塞,而任务L又被任务M抢占,导致任务H实际上被中等优先级的任务M间接阻塞了!这就是优先级反转。

让我们用代码来重现这个经典问题:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t resourceSemaphore;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建一个互斥量来保护共享资源
    resourceSemaphore = xSemaphoreCreateMutex();
    
    // 创建三个不同优先级的任务
    xTaskCreate(lowPriorityTask, "Low_Priority", 2048, NULL, 1, NULL);
    xTaskCreate(mediumPriorityTask, "Medium_Priority", 2048, NULL, 2, NULL);
    xTaskCreate(highPriorityTask, "High_Priority", 2048, NULL, 3, NULL);
    
    Serial.println("优先级反转演示开始!");
}

void lowPriorityTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("低优先级任务:尝试获取资源...");
        
        if (xSemaphoreTake(resourceSemaphore, portMAX_DELAY) == pdTRUE) {
   
   
            Serial.println("低优先级任务:获取资源成功,开始长时间操作...");
            
            // 模拟长时间的资源使用
            for (int i = 0; i < 10; i++) {
   
   
                Serial.printf("低优先级任务:正在使用资源... %d/10\n", i + 1);
                vTaskDelay(500 / portTICK_PERIOD_MS);
            }
            
            Serial.println("低优先级任务:释放资源");
            xSemaphoreGive(resourceSemaphore);
        }
        
        vTaskDelay(10000 / portTICK_PERIOD_MS);
    }
}

void mediumPriorityTask(void *parameter) {
   
   
    vTaskDelay(2000 / portTICK_PERIOD_MS);  // 延迟启动
    
    for(;;) {
   
   
        Serial.println("中等优先级任务:开始CPU密集型操作...");
        
        // 模拟CPU密集型操作(不使用共享资源)
        for (int i = 0; i < 20; i++) {
   
   
            Serial.printf("中等优先级任务:计算中... %d/20\n", i + 1);
            vTaskDelay(200 / portTICK_PERIOD_MS);
        }
        
        Serial.println("中等优先级任务:操作完成");
        vTaskDelay(8000 / portTICK_PERIOD_MS);
    }
}

void highPriorityTask(void *parameter) {
   
   
    vTaskDelay(3000 / portTICK_PERIOD_MS);  // 延迟启动,让低优先级任务先获取资源
    
    for(;;) {
   
   
        Serial.println("高优先级任务:急需访问共享资源!");
        
        TickType_t startTime = xTaskGetTickCount();
        
        if (xSemaphoreTake(resourceSemaphore, portMAX_DELAY) == pdTRUE) {
   
   
            TickType_t endTime = xTaskGetTickCount();
            TickType_t waitTime = endTime - startTime;
            
            Serial.printf("高优先级任务:终于获取到资源!等待时间:%d ms\n", 
                         waitTime * portTICK_PERIOD_MS);
            
            // 高优先级任务通常需要快速完成
            Serial.println("高优先级任务:快速使用资源");
            vTaskDelay(100 / portTICK_PERIOD_MS);
            
            Serial.println("高优先级任务:释放资源");
            xSemaphoreGive(resourceSemaphore);
        }
        
        vTaskDelay(15000 / portTICK_PERIOD_MS);
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

运行这个程序,你会看到一个有趣的现象:高优先级任务明明应该优先执行,但却被迫等待低优先级任务释放资源。而在等待期间,中等优先级任务却能够抢占CPU,进一步延长了高优先级任务的等待时间!

优先级继承:解决方案的精髓

FreeRTOS提供了一个优雅的解决方案:优先级继承(Priority Inheritance)。当高优先级任务被低优先级任务持有的互斥量阻塞时,低优先级任务会临时继承高优先级任务的优先级,直到释放互斥量。

这就像是"临时提拔"——为了让高优先级任务能够尽快获得资源,系统临时提升了低优先级任务的优先级,让它能够抢占中等优先级任务的CPU时间,尽快完成工作并释放资源。

// 使用支持优先级继承的互斥量
SemaphoreHandle_t priorityInheritanceMutex;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建支持优先级继承的互斥量
    priorityInheritanceMutex = xSemaphoreCreateMutex();
    
    // 注意:FreeRTOS的互斥量默认支持优先级继承
    // 这与普通的二进制信号量不同!
    
    xTaskCreate(demonstratePriorityInheritance, "Demo_Task", 2048, NULL, 1, NULL);
    
    Serial.println("优先级继承演示");
}

void demonstratePriorityInheritance(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("=== 优先级继承机制说明 ===");
        Serial.println("1. 互斥量自动支持优先级继承");
        Serial.println("2. 当高优先级任务被阻塞时,持有互斥量的低优先级任务会临时提升优先级");
        Serial.println("3. 这样可以防止中等优先级任务无限期地抢占CPU");
        Serial.println("4. 互斥量释放后,任务优先级恢复正常");
        Serial.println("=============================\n");
        
        vTaskDelay(10000 / portTICK_PERIOD_MS);
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

信号量 vs 互斥量在优先级反转方面的区别:

这是一个很多人容易搞混的地方:

  • 二进制信号量:不支持优先级继承,可能发生优先级反转
  • 互斥量:自动支持优先级继承,有效防止优先级反转

所以,当你需要保护共享资源时,应该使用互斥量而不是二进制信号量!

互斥量完全指南:独占资源的守护神

互斥量的工作原理与内存模型

互斥量(Mutex)这个名字来源于"Mutual Exclusion"(互相排斥),它就像是一把智能锁,确保同一时间只有一个任务能够访问被保护的资源。

与信号量不同,互斥量有一个重要的概念:所有权(Ownership)。只有获取了互斥量的任务才能释放它,这就像是"谁借的钱谁还"一样简单明了。

互斥量的内存结构:

在ESP32的FreeRTOS实现中,互斥量实际上是一个特殊的信号量,但它包含了额外的信息:

typedef struct {
   
   
    UBaseType_t uxRecursiveCallCount;    // 递归调用计数
    void *pxMutexHolder;                 // 持有者任务句柄
    UBaseType_t uxBasePriority;          // 持有者的原始优先级
    // ... 其他内部字段
} MutexControlBlock_t;

这个结构让互斥量能够:

  1. 跟踪哪个任务持有了互斥量
  2. 实现优先级继承机制
  3. 支持递归获取(对于递归互斥量)
  4. 防止错误的释放操作

让我们通过一个详细的例子来理解互斥量的工作原理:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t sharedResourceMutex;
int sharedCounter = 0;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建互斥量
    sharedResourceMutex = xSemaphoreCreateMutex();
    
    if (sharedResourceMutex == NULL) {
   
   
        Serial.println("互斥量创建失败!");
        return;
    }
    
    // 创建多个任务来竞争共享资源
    xTaskCreate(incrementTask, "Increment_1", 2048, (void*)"任务A", 2, NULL);
    xTaskCreate(incrementTask, "Increment_2", 2048, (void*)"任务B", 2, NULL);
    xTaskCreate(incrementTask, "Increment_3", 2048, (void*)"任务C", 2, NULL);
    xTaskCreate(monitorTask, "Monitor", 2048, NULL, 1, NULL);
    
    Serial.println("互斥量演示开始!");
}

void incrementTask(void *parameter) {
   
   
    const char* taskName = (const char*)parameter;
    
    for(;;) {
   
   
        Serial.printf("%s:尝试获取互斥量...\n", taskName);
        
        // 获取互斥量,保护共享资源
        if (xSemaphoreTake(sharedResourceMutex, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
            Serial.printf("%s:成功获取互斥量,开始修改共享资源\n", taskName);
            
            // 临界区开始 - 只有一个任务能执行这里的代码
            int oldValue = sharedCounter;
            
            // 模拟复杂的操作过程
            Serial.printf("%s:读取当前值 = %d\n", taskName, oldValue);
            vTaskDelay(1000 / portTICK_PERIOD_MS);  // 模拟处理时间
            
            sharedCounter = oldValue + 1;
            Serial.printf("%s:更新后的值 = %d\n", taskName, sharedCounter);
            
            vTaskDelay(500 / portTICK_PERIOD_MS);   // 模拟额外处理
            // 临界区结束
            
            Serial.printf("%s:释放互斥量\n", taskName);
            xSemaphoreGive(sharedResourceMutex);
        } else {
   
   
            Serial.printf("%s:获取互斥量超时!\n", taskName);
        }
        
        // 等待一段时间再次尝试
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

void monitorTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.printf("监控任务:当前共享计数器值 = %d\n", sharedCounter);
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

运行这个程序,你会看到类似这样的输出:

互斥量演示开始!
任务A:尝试获取互斥量...
任务B:尝试获取互斥量...
任务C:尝试获取互斥量...
任务A:成功获取互斥量,开始修改共享资源
任务A:读取当前值 = 0
监控任务:当前共享计数器值 = 0
任务A:更新后的值 = 1
任务A:释放互斥量
任务B:成功获取互斥量,开始修改共享资源
任务B:读取当前值 = 1
任务B:更新后的值 = 2
任务B:释放互斥量
监控任务:当前共享计数器值 = 2

从输出可以看出,即使有多个任务同时竞争,互斥量确保了每次只有一个任务能够修改共享资源,避免了数据竞争。

递归互斥量:解决重入问题的利器

有时候,我们会遇到这样的情况:一个任务已经获取了互斥量,但在执行过程中又需要再次获取同一个互斥量。如果使用普通的互斥量,这会导致死锁!

递归互斥量就是为了解决这个问题而设计的。它允许同一个任务多次获取同一个互斥量,只要获取和释放的次数匹配即可。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t recursiveMutex;

void setup() {
   
   
    Serial.begin(115200);
    
    // 创建递归互斥量
    recursiveMutex = xSemaphoreCreateRecursiveMutex();
    
    if (recursiveMutex == NULL) {
   
   
        Serial.println("递归互斥量创建失败!");
        return;
    }
    
    xTaskCreate(recursiveTask, "Recursive_Task", 2048, NULL, 1, NULL);
    
    Serial.println("递归互斥量演示开始!");
}
```cpp
void recursiveTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("开始递归互斥量演示...");
        
        // 调用需要互斥量保护的函数
        processDataLevel1();
        
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }
}

void processDataLevel1() {
   
   
    Serial.println("Level 1: 尝试获取递归互斥量...");
    
    if (xSemaphoreTakeRecursive(recursiveMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
        Serial.println("Level 1: 成功获取互斥量");
        
        // 模拟一些处理
        vTaskDelay(500 / portTICK_PERIOD_MS);
        
        // 调用另一个也需要同一个互斥量的函数
        processDataLevel2();
        
        Serial.println("Level 1: 释放互斥量");
        xSemaphoreGiveRecursive(recursiveMutex);
    } else {
   
   
        Serial.println("Level 1: 获取互斥量失败!");
    }
}

void processDataLevel2() {
   
   
    Serial.println("Level 2: 尝试获取递归互斥量...");
    
    if (xSemaphoreTakeRecursive(recursiveMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
        Serial.println("Level 2: 成功获取互斥量(递归)");
        
        // 模拟更多处理
        vTaskDelay(300 / portTICK_PERIOD_MS);
        
        // 甚至可以再次递归
        processDataLevel3();
        
        Serial.println("Level 2: 释放互斥量");
        xSemaphoreGiveRecursive(recursiveMutex);
    } else {
   
   
        Serial.println("Level 2: 获取互斥量失败!");
    }
}

void processDataLevel3() {
   
   
    Serial.println("Level 3: 尝试获取递归互斥量...");
    
    if (xSemaphoreTakeRecursive(recursiveMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
        Serial.println("Level 3: 成功获取互斥量(再次递归)");
        
        // 最终的处理逻辑
        Serial.println("Level 3: 执行核心业务逻辑");
        vTaskDelay(200 / portTICK_PERIOD_MS);
        
        Serial.println("Level 3: 释放互斥量");
        xSemaphoreGiveRecursive(recursiveMutex);
    } else {
   
   
        Serial.println("Level 3: 获取互斥量失败!");
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

这个例子展示了递归互斥量的强大之处:同一个任务可以多次获取同一个互斥量,只要确保获取和释放的次数匹配即可。

递归互斥量的内部计数机制:

递归互斥量内部维护一个计数器,记录被同一个任务获取的次数:

第1次获取:计数器 = 1
第2次获取:计数器 = 2
第3次获取:计数器 = 3
...
第1次释放:计数器 = 2
第2次释放:计数器 = 1
第3次释放:计数器 = 0(互斥量真正释放)

使用递归互斥量的经典场景:

  1. 函数调用链:函数A调用函数B,函数B调用函数C,它们都需要同一个互斥量
  2. 面向对象编程:类的公有方法调用私有方法,都需要保护同一个资源
  3. 状态机实现:不同状态处理函数可能相互调用,需要保护状态变量

互斥量的超时机制与错误处理

在实际项目中,合理的超时机制和错误处理是确保系统稳定性的关键。没有人愿意看到自己的ESP32因为一个任务无限期等待而整个系统"卡死"。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t criticalResourceMutex;
int criticalResource = 0;

void setup() {
   
   
    Serial.begin(115200);
    
    criticalResourceMutex = xSemaphoreCreateMutex();
    
    // 创建不同超时策略的任务
    xTaskCreate(impatientTask, "Impatient", 2048, NULL, 2, NULL);
    xTaskCreate(patientTask, "Patient", 2048, NULL, 1, NULL);
    xTaskCreate(resourceHogTask, "ResourceHog", 2048, NULL, 1, NULL);
    
    Serial.println("超时机制演示开始!");
}

void impatientTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("急性子任务:我等不了太久!");
        
        // 只等待500ms
        if (xSemaphoreTake(criticalResourceMutex, 500 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
            Serial.println("急性子任务:太好了,立即获取到资源!");
            
            criticalResource += 10;
            Serial.printf("急性子任务:快速处理,资源值 = %d\n", criticalResource);
            
            vTaskDelay(200 / portTICK_PERIOD_MS);  // 快速处理
            
            xSemaphoreGive(criticalResourceMutex);
            Serial.println("急性子任务:快速释放资源");
        } else {
   
   
            Serial.println("急性子任务:等不及了,去做别的事情!");
            
            // 执行备用方案
            Serial.println("急性子任务:执行备用处理逻辑");
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
        
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void patientTask(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("耐心任务:我可以等待较长时间");
        
        // 等待5秒
        TickType_t startTime = xTaskGetTickCount();
        
        if (xSemaphoreTake(criticalResourceMutex, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
   
   
            TickType_t waitTime = xTaskGetTickCount() - startTime;
            Serial.printf("耐心任务:等待了 %d ms 后获取到资源\n", 
                         waitTime * portTICK_PERIOD_MS);
            
            criticalResource += 50;
            Serial.printf("耐心任务:仔细处理,资源值 = %d\n", criticalResource);
            
            vTaskDelay(1000 / portTICK_PERIOD_MS);  // 仔细处理
            
            xSemaphoreGive(criticalResourceMutex);
            Serial.println("耐心任务:处理完成,释放资源");
        } else {
   
   
            Serial.println("耐心任务:连我都等超时了,肯定有问题!");
            
            // 记录错误或采取恢复措施
            logError("MUTEX_TIMEOUT", "耐心任务等待互斥量超时");
        }
        
        vTaskDelay(4000 / portTICK_PERIOD_MS);
    }
}

void resourceHogTask(void *parameter) {
   
   
    vTaskDelay(2000 / portTICK_PERIOD_MS);  // 延迟启动
    
    for(;;) {
   
   
        Serial.println("资源占用者:我要长时间使用资源!");
        
        if (xSemaphoreTake(criticalResourceMutex, portMAX_DELAY) == pdTRUE) {
   
   
            Serial.println("资源占用者:开始长时间占用资源...");
            
            // 模拟长时间的复杂操作
            for (int i = 0; i < 10; i++) {
   
   
                criticalResource += 1;
                Serial.printf("资源占用者:处理步骤 %d/10,资源值 = %d\n", 
                             i + 1, criticalResource);
                vTaskDelay(800 / portTICK_PERIOD_MS);
            }
            
            Serial.println("资源占用者:终于完成了,释放资源");
            xSemaphoreGive(criticalResourceMutex);
        }
        
        vTaskDelay(15000 / portTICK_PERIOD_MS);  // 长时间休息
    }
}

void logError(const char* errorCode, const char* description) {
   
   
    Serial.printf("错误日志 [%s]: %s (时间: %d ms)\n", 
                 errorCode, description, millis());
    
    // 在实际项目中,这里可以:
    // 1. 写入EEPROM或SD卡
    // 2. 发送到远程服务器
    // 3. 触发看门狗重启
    // 4. 激活备用系统
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

超时策略的选择指南:

  1. 立即返回(0):用于非关键操作,失败后有备用方案
  2. 短超时(100-1000ms):用于实时性要求高的操作
  3. 中等超时(1-5秒):用于重要但不紧急的操作
  4. 长超时(5-30秒):用于关键操作,但需要防止死锁
  5. 永久等待(portMAX_DELAY):只在确定不会死锁时使用

错误处理的最佳实践:

// 错误处理模板
BaseType_t mutexResult = xSemaphoreTake(myMutex, timeoutTicks);

switch (mutexResult) {
   
   
    case pdTRUE:
        // 成功获取互斥量
        // 执行临界区代码
        xSemaphoreGive(myMutex);
        break;
        
    case pdFALSE:
        // 获取失败(超时)
        Serial.println("警告:获取互斥量超时");
        
        // 选择处理策略:
        // 1. 执行备用方案
        // 2. 记录错误日志
        // 3. 通知用户
        // 4. 重试操作
        handleMutexTimeout();
        break;
        
    default:
        // 不应该到达这里
        Serial.println("错误:未知的互斥量返回值");
        break;
}

死锁问题的识别与预防

死锁是多任务编程中最令人头疼的问题之一。想象两个人在狭窄的桥上相遇,都不肯让路,结果谁也过不去。在程序中,死锁就是两个或多个任务相互等待对方释放资源,导致所有任务都无法继续执行。

经典的死锁场景:

// 这是一个会导致死锁的危险代码示例!
SemaphoreHandle_t mutexA;
SemaphoreHandle_t mutexB;

void task1(void *parameter) {
   
   
    for(;;) {
   
   
        // 任务1:先获取A,再获取B
        xSemaphoreTake(mutexA, portMAX_DELAY);
        Serial.println("任务1:获取到互斥量A");
        
        vTaskDelay(100 / portTICK_PERIOD_MS);  // 模拟处理时间
        
        xSemaphoreTake(mutexB, portMAX_DELAY);
        Serial.println("任务1:获取到互斥量B");
        
        // 执行需要两个资源的操作
        Serial.println("任务1:使用资源A和B");
        
        xSemaphoreGive(mutexB);
        xSemaphoreGive(mutexA);
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void task2(void *parameter) {
   
   
    for(;;) {
   
   
        // 任务2:先获取B,再获取A(顺序相反!)
        xSemaphoreTake(mutexB, portMAX_DELAY);
        Serial.println("任务2:获取到互斥量B");
        
        vTaskDelay(100 / portTICK_PERIOD_MS);  // 模拟处理时间
        
        xSemaphoreTake(mutexA, portMAX_DELAY);  // 死锁发生在这里!
        Serial.println("任务2:获取到互斥量A");
        
        // 这里的代码永远不会执行
        Serial.println("任务2:使用资源A和B");
        
        xSemaphoreGive(mutexA);
        xSemaphoreGive(mutexB);
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

死锁预防的黄金法则:

  1. 资源排序法:所有任务都按相同顺序获取多个互斥量
  2. 超时机制:避免使用portMAX_DELAY,总是设置合理的超时时间
  3. 资源分层:将资源分为不同层次,只能从低层向高层获取
  4. 银行家算法:在获取资源前检查是否会导致死锁

让我们看一个正确的实现:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t mutexA;
SemaphoreHandle_t mutexB;

void setup() {
   
   
    Serial.begin(115200);
    
    mutexA = xSemaphoreCreateMutex();
    mutexB = xSemaphoreCreateMutex();
    
    xTaskCreate(safeTask1, "Safe_Task_1", 2048, NULL, 1, NULL);
    xTaskCreate(safeTask2, "Safe_Task_2", 2048, NULL, 1, NULL);
    xTaskCreate(deadlockDetector, "Deadlock_Detector", 2048, NULL, 3, NULL);
    
    Serial.println("死锁预防演示开始!");
}

// 安全的任务实现:使用资源排序法
bool acquireMultipleMutexes(SemaphoreHandle_t first, SemaphoreHandle_t second, 
                           const char* taskName, TickType_t timeout) {
   
   
    Serial.printf("%s:尝试获取第一个互斥量...\n", taskName);
    
    if (xSemaphoreTake(first, timeout) == pdTRUE) {
   
   
        Serial.printf("%s:获取第一个互斥量成功\n", taskName);
        
        Serial.printf("%s:尝试获取第二个互斥量...\n", taskName);
        if (xSemaphoreTake(second, timeout) == pdTRUE) {
   
   
            Serial.printf("%s:获取第二个互斥量成功\n", taskName);
            return true;
        } else {
   
   
            Serial.printf("%s:获取第二个互斥量超时,释放第一个\n", taskName);
            xSemaphoreGive(first);
            return false;
        }
    } else {
   
   
        Serial.printf("%s:获取第一个互斥量超时\n", taskName);
        return false;
    }
}

void releaseMultipleMutexes(SemaphoreHandle_t first, SemaphoreHandle_t second, 
                           const char* taskName) {
   
   
    Serial.printf("%s:释放第二个互斥量\n", taskName);
    xSemaphoreGive(second);
    
    Serial.printf("%s:释放第一个互斥量\n", taskName);
    xSemaphoreGive(first);
}

void safeTask1(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("安全任务1:开始工作");
        
        // 统一顺序:先A后B
        if (acquireMultipleMutexes(mutexA, mutexB, "安全任务1", 2000 / portTICK_PERIOD_MS)) {
   
   
            Serial.println("安全任务1:同时使用资源A和B");
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            
            releaseMultipleMutexes(mutexA, mutexB, "安全任务1");
        } else {
   
   
            Serial.println("安全任务1:无法获取所需资源,执行备用方案");
        }
        
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void safeTask2(void *parameter) {
   
   
    for(;;) {
   
   
        Serial.println("安全任务2:开始工作");
        
        // 同样的顺序:先A后B(避免死锁)
        if (acquireMultipleMutexes(mutexA, mutexB, "安全任务2", 2000 / portTICK_PERIOD_MS)) {
   
   
            Serial.println("安全任务2:同时使用资源A和B");
            vTaskDelay(800 / portTICK_PERIOD_MS);
            
            releaseMultipleMutexes(mutexA, mutexB, "安全任务2");
        } else {
   
   
            Serial.println("安全任务2:无法获取所需资源,执行备用方案");
        }
        
        vTaskDelay(4000 / portTICK_PERIOD_MS);
    }
}

void deadlockDetector(void *parameter) {
   
   
    TickType_t lastActivityTime = xTaskGetTickCount();
    
    for(;;) {
   
   
        TickType_t currentTime = xTaskGetTickCount();
        TickType_t elapsedTime = currentTime - lastActivityTime;
        
        if (elapsedTime > (10000 / portTICK_PERIOD_MS)) {
   
   
            Serial.println("死锁检测器:警告!系统可能发生死锁");
            Serial.printf("死锁检测器:已经 %d 秒没有活动\n", 
                         elapsedTime * portTICK_PERIOD_MS / 1000);
            
            // 在实际项目中,这里可以:
            // 1. 重启系统
            // 2. 记录错误日志
            // 3. 发送警报
            // 4. 尝试恢复
        }
        
        lastActivityTime = currentTime;
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }
}

void loop() {
   
   
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

死锁检测的高级技巧:

// 死锁检测和恢复机制
class DeadlockDetector {
   
   
private:
    struct TaskInfo {
   
   
        TaskHandle_t handle;
        TickType_t lastActiveTime;
        const char* name;
    };
    
    TaskInfo monitoredTasks[10];
    int taskCount;
    
public:
    void addTask(TaskHandle_t task, const char* name) {
   
   
        if (taskCount < 10) {
   
   
            monitoredTasks[taskCount].handle = task;
            monitoredTasks[taskCount].name = name;
            monitoredTasks[taskCount].lastActiveTime = xTaskGetTickCount();
            taskCount++;
        }
    }
    
    void updateTaskActivity(TaskHandle_t task) {
   
   
        for (int i = 0; i < taskCount; i++) {
   
   
            if (monitoredTasks[i].handle == task) {
   
   
                monitoredTasks[i].lastActiveTime = xTaskGetTickCount();
                break;
            }
        }
    }
    
    bool checkForDeadlock(TickType_t timeoutMs) {
   
   
        TickType_t currentTime = xTaskGetTickCount();
        TickType_t timeoutTicks = timeoutMs / portTICK_PERIOD_MS;
        
        for (int i = 0; i < taskCount; i++) {
   
   
            if (currentTime - monitoredTasks[i].lastActiveTime > timeoutTicks) {
   
   
                Serial.printf("检测到可能的死锁:任务 %s 已经 %d ms 没有活动\n",
                             monitoredTasks[i].name,
                             (currentTime - monitoredTasks[i].lastActiveTime) * portTICK_PERIOD_MS);
                return true;
            }
        }
        
        return false;
    }
};

优先级继承算法的实现细节

优先级继承是FreeRTOS互斥量的一个重要特性,它能够有效防止优先级反转问题。让我们深入了解这个算法的工作原理。

优先级继承的工作流程:

  1. 高优先级任务被阻塞:当高优先级任务尝试获取被低优先级任务持有的互斥量时
  2. 临时提升优先级:低优先级任务临时继承高优先级任务的优先级
  3. 抢占中等优先级任务:提升后的低优先级任务能够抢占中等优先级任务的CPU时间
  4. 优先级恢复:当互斥量被释放时,任务优先级恢复到原始值
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t priorityInheritanceMutex;

void setup() {
   
   
    Serial.begin(115200);
    
    priorityInheritanceMutex = xSemaphoreCreateMutex();
    
    // 创建不同优先级的任务来演示优先级继承
    xTaskCreate(lowPriorityTask, "Low_Priority", 2048, NULL, 1, NULL);
    xTaskCreate(medium
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值