4. 案例4

任务定义

在FreeRTOS多任务系统中,当高优先级任务等待低优先级任务持有的共享资源时,若中优先级任务抢占CPU,将导致高优先级任务被无限期阻塞——即优先级反转。本案例构建一个最小可复现场景:三个任务(高、中、低优先级)竞争一个共享资源,先演示无保护时高优先级任务被中优先级任务长时间阻塞的问题,再引入互斥量(含优先级继承机制)验证问题得到解决。

场景设计与代码实现

系统包含三个任务:HighTask(优先级3)、MidTask(优先级2)、LowTask(优先级1)。共享资源为一个全局计数器,由临界区模拟。LowTask先获取资源并长时间占用;MidTask在LowTask持有资源期间持续运行;HighTask等待资源。通过串口日志输出各任务进入/退出时间戳,对比使用互斥量前后的阻塞时长。


#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"

/* 共享资源 */
static volatile uint32_t shared_counter = 0;
static SemaphoreHandle_t xMutex;

/* 任务句柄 */
static TaskHandle_t hHigh, hMid, hLow;

/* 模拟占用资源的耗时操作 */
static void busy_wait(uint32_t us) {
    volatile uint32_t count = us * (configCPU_CLOCK_HZ / 1000000) / 10;
    while (count--) { __NOP(); }
}

/* 低优先级任务:先获取资源,长时间占用 */
static void vLowTask(void *arg) {
    (void)arg;
    for (;;) {
        /* 尝试获取互斥量(或直接进入临界区) */
        #ifdef USE_MUTEX
        xSemaphoreTake(xMutex, portMAX_DELAY);
        #endif

        /* 占用资源 200ms */
        shared_counter++;
        vTaskDelay(pdMS_TO_TICKS(200));

        #ifdef USE_MUTEX
        xSemaphoreGive(xMutex);
        #endif

        vTaskDelay(pdMS_TO_TICKS(50));  /* 释放CPU让其他任务运行 */
    }
}

/* 中优先级任务:无资源需求,但持续占用CPU */
static void vMidTask(void *arg) {
    (void)arg;
    TickType_t start;
    for (;;) {
        start = xTaskGetTickCount();
        /* 持续运行 100ms,模拟CPU密集型工作 */
        while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(100)) {
            busy_wait(100);
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

/* 高优先级任务:等待共享资源 */
static void vHighTask(void *arg) {
    (void)arg;
    TickType_t start, wait_time;
    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(500));  /* 等待低任务先占用资源 */

        start = xTaskGetTickCount();
        #ifdef USE_MUTEX
        xSemaphoreTake(xMutex, portMAX_DELAY);
        #else
        /* 无保护:直接访问共享资源(模拟进入临界区) */
        taskENTER_CRITICAL();
        #endif

        /* 访问资源 */
        shared_counter++;
        wait_time = xTaskGetTickCount() - start;

        #ifdef USE_MUTEX
        xSemaphoreGive(xMutex);
        #else
        taskEXIT_CRITICAL();
        #endif

        /* 打印等待时间 */
        printf("[High] waited %d ms\n", (int)(wait_time * portTICK_PERIOD_MS));
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vPriorityInversionDemo(void) {
    /* 创建互斥量 */
    xMutex = xSemaphoreCreateMutex();

    /* 创建任务:低(1)、中(2)、高(3) */
    xTaskCreate(vLowTask, "Low",  128, NULL, 1, &hLow);
    xTaskCreate(vMidTask, "Mid",  128, NULL, 2, &hMid);
    xTaskCreate(vHighTask, "High", 128, NULL, 3, &hHigh);

    /* 启动调度器 */
    vTaskStartScheduler();
}

编译与运行

本代码基于FreeRTOS内核,需在嵌入式平台(如STM32F4 + Keil/IAR)或FreeRTOS模拟器(如QEMU + Eclipse)中编译。关键配置:configUSE_MUTEXES必须为1,configUSE_PREEMPTION为1,configPRIO_BITS至少3位。串口重定向printf到调试终端。

编译时通过宏USE_MUTEX控制是否启用互斥量:


/* 不使用互斥量(演示问题) */
// #define USE_MUTEX

/* 使用互斥量(验证解决) */
#define USE_MUTEX

验证方法与判定标准

步骤1(问题复现):注释掉USE_MUTEX,编译烧录。观察串口输出,High任务每次等待时间应接近300ms(200ms低任务占用 + 100ms中任务抢占)。若等待时间持续大于200ms且不稳定,说明优先级反转发生。

步骤2(解决验证):启用USE_MUTEX,重新编译烧录。观察输出,High任务等待时间应显著缩短至约200ms(仅低任务占用时间),且不会因中任务抢占而延长。若等待时间稳定在200ms±10ms,判定互斥量优先级继承机制生效。

判定标准:对比两组日志中High任务的最大等待时间。无互斥量时最大等待时间 > 250ms;有互斥量时最大等待时间 ≤ 210ms,且中任务运行期间High任务不被阻塞,即问题解决。

优先级反转场景与互斥量解决对比 t=0 t=500ms 无互斥量(优先级反转) High(3) 阻塞 等待低任务释放(200ms) 被中抢占 运行 Mid(2) 持续运行 Low(1) 持有资源(200ms) 有互斥量(优先级继承) High(3) 阻塞 等待低任务释放(200ms) 运行 Mid(2) 被低任务继承优先级压制 Low(1) 持有资源(200ms) 继承High优先级(3) 等待≈300ms 等待≈200ms

结果判定

通过上述对比实验,可明确验证:未使用互斥量时,High任务最大等待时间约300ms(200ms低任务占用 + 100ms中任务抢占),出现优先级反转;使用互斥量后,由于优先级继承机制,Low任务在持有资源期间临时提升至High任务优先级,中任务无法抢占,High任务等待时间缩短至约200ms,问题得到解决。若实测数据符合上述趋势,即可判定案例通过。