2. 任务优先级反转实验

任务与目标

本案例在FreeRTOS上创建三个任务:高优先级任务H、中优先级任务M、低优先级任务L。L先获取二值信号量并进入临界区模拟慢速操作;随后H就绪并请求同一信号量,因信号量被L持有而阻塞;此时M就绪并抢占L(因L已被阻塞,M获得CPU),导致H被M间接阻塞——即优先级反转。观察此现象后,启用互斥量(含优先级继承)替代二值信号量,重复实验,验证H的阻塞时间显著缩短。

硬件与依赖

  • 开发板:STM32F407(或任意Cortex-M4内核MCU)
  • RTOS:FreeRTOS V10.4+(需开启互斥量功能,默认开启)
  • 调试方式:串口打印任务状态时间戳

代码实现

// main.c — 优先级反转与优先级继承对比实验
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"
#include <stdio.h>

// 串口发送字符串(需根据平台实现)
extern void UART_SendString(const char* str);

// 任务优先级
#define PRIO_H  3
#define PRIO_M  2
#define PRIO_L  1

// 同步对象句柄
SemaphoreHandle_t xSyncSem;      // 二值信号量(反转场景)
SemaphoreHandle_t xMutex;        // 互斥量(继承场景)

// 运行模式切换:0=反转,1=继承
volatile int mode = 0;

// 时间戳辅助
static uint32_t tick_ms(void) {
    return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}

// 低优先级任务:先获取资源,然后慢速占用
void vTaskL(void* pvParams) {
    (void)pvParams;
    for (;;) {
        if (mode == 0) {
            xSemaphoreTake(xSyncSem, portMAX_DELAY);
        } else {
            xMutexTake(xMutex, portMAX_DELAY);
        }
        UART_SendString("[L] 获取资源,开始慢速操作\r\n");

        // 模拟慢速临界区:约200ms
        TickType_t start = xTaskGetTickCount();
        while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(200)) {
            // 空转占用CPU
        }

        if (mode == 0) {
            xSemaphoreGive(xSyncSem);
        } else {
            xMutexGive(xMutex);
        }
        UART_SendString("[L] 释放资源\r\n");
        vTaskDelay(pdMS_TO_TICKS(500)); // 避免立即再次抢占
    }
}

// 中优先级任务:纯计算,无资源需求
void vTaskM(void* pvParams) {
    (void)pvParams;
    for (;;) {
        UART_SendString("[M] 运行中\r\n");
        // 持续占用CPU约100ms
        TickType_t start = xTaskGetTickCount();
        while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(100)) {
            // 空转
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// 高优先级任务:请求资源,测量阻塞时间
void vTaskH(void* pvParams) {
    (void)pvParams;
    vTaskDelay(pdMS_TO_TICKS(50)); // 确保L先运行并持有资源
    for (;;) {
        uint32_t t0 = tick_ms();
        BaseType_t ok;
        if (mode == 0) {
            ok = xSemaphoreTake(xSyncSem, pdMS_TO_TICKS(500));
        } else {
            ok = xMutexTake(xMutex, pdMS_TO_TICKS(500));
        }
        uint32_t dt = tick_ms() - t0;

        char buf[64];
        snprintf(buf, sizeof(buf), "[H] 获取%s,阻塞=%lums\r\n",
                 (ok == pdTRUE) ? "成功" : "超时", (unsigned long)dt);
        UART_SendString(buf);

        if (ok == pdTRUE) {
            if (mode == 0) {
                xSemaphoreGive(xSyncSem);
            } else {
                xMutexGive(xMutex);
            }
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 模式切换任务:每5秒切换一次
void vTaskModeSwitch(void* pvParams) {
    (void)pvParams;
    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(5000));
        mode = (mode == 0) ? 1 : 0;
        UART_SendString(mode == 0 ? "\r\n=== 模式:优先级反转(二值信号量) ===\r\n"
                                  : "\r\n=== 模式:优先级继承(互斥量) ===\r\n");
    }
}

int main(void) {
    // 硬件初始化(时钟、串口等)由平台提供
    xSyncSem = xSemaphoreCreateBinary();
    xMutex = xMutexCreate();
    if (xSyncSem == NULL || xMutex == NULL) {
        for (;;); // 创建失败停机
    }
    // 初始给L一个信号量,确保L先持有
    xSemaphoreGive(xSyncSem);

    xTaskCreate(vTaskL, "L", 128, NULL, PRIO_L, NULL);
    xTaskCreate(vTaskM, "M", 128, NULL, PRIO_M, NULL);
    xTaskCreate(vTaskH, "H", 128, NULL, PRIO_H, NULL);
    xTaskCreate(vTaskModeSwitch, "SW", 128, NULL, 0, NULL);

    vTaskStartScheduler();
    for (;;);
    return 0;
}

关键机制说明

实验通过mode变量在两种同步方式间切换。二值信号量不感知持有者优先级,当H阻塞等待L释放时,M可抢占L,导致H等待时间≈L剩余临界区时间+M运行时间。互斥量内置优先级继承:当H阻塞在互斥量上时,L的优先级被临时提升至H的优先级,使L能立即抢占M并快速完成临界区释放,从而缩短H的阻塞时间。

SVG:优先级反转与继承时序对比

优先级反转(二值信号量) L 持有资源 被抢占 继续临界区 M 运行 H 阻塞等待(被M间接阻塞) t0 t1 t2 t3 H阻塞时间 = (t3-t1) 较长 优先级继承(互斥量) L 持有资源 继承H优先级 完成释放 M 被L抢占 H 短暂阻塞 获得资源 t0' t1' t2' H阻塞时间 = (t2'-t1') 较短 对比:互斥量通过优先级继承避免M抢占L

编译与运行

  1. 将代码加入FreeRTOS工程,确保堆栈大小≥128字(任务栈)
  2. 串口波特率115200,连接PC串口助手
  3. 编译烧录后,系统自动运行,每5秒切换一次模式

验证方法与判定标准

观察串口输出:

  • 模式0(反转):H打印的阻塞时间应明显大于L剩余临界区时间(约200ms),典型值在300ms以上,因为M的100ms运行被插入
  • 模式1(继承):H打印的阻塞时间应接近L剩余临界区时间(≤200ms),且M的“运行中”打印在L释放资源前不应出现

判定通过标准:两种模式下H的阻塞时间差异显著(反转模式≥1.5倍继承模式),且继承模式下H阻塞时间不超过L最大临界区时长(200ms+调度抖动)。若反转模式阻塞时间≈继承模式,则检查是否误用互斥量或优先级配置错误。