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:优先级反转与继承时序对比
编译与运行
- 将代码加入FreeRTOS工程,确保堆栈大小≥128字(任务栈)
- 串口波特率115200,连接PC串口助手
- 编译烧录后,系统自动运行,每5秒切换一次模式
验证方法与判定标准
观察串口输出:
- 模式0(反转):H打印的阻塞时间应明显大于L剩余临界区时间(约200ms),典型值在300ms以上,因为M的100ms运行被插入
- 模式1(继承):H打印的阻塞时间应接近L剩余临界区时间(≤200ms),且M的“运行中”打印在L释放资源前不应出现
判定通过标准:两种模式下H的阻塞时间差异显著(反转模式≥1.5倍继承模式),且继承模式下H阻塞时间不超过L最大临界区时长(200ms+调度抖动)。若反转模式阻塞时间≈继承模式,则检查是否误用互斥量或优先级配置错误。