引言

在基于 RTOS 的嵌入式系统中,看门狗(Watchdog)用于监测系统是否正常运行。然而,当任务调度出现异常时,看门狗可能被误触发,导致系统复位。优先级反转(Priority Inversion)是引发此类问题的典型原因之一。本文将以一个实际案例,讲解如何通过互斥量与优先级继承机制排查和解决该问题。

优先级反转原理

优先级反转是指高优先级任务因等待低优先级任务释放资源而被阻塞,而中优先级任务抢占低优先级任务,导致高优先级任务长时间得不到执行。经典场景如下:

  • 任务 A(高优先级)和任务 C(低优先级)共享一个互斥量。
  • 任务 C 持有互斥量,任务 A 等待该互斥量。
  • 任务 B(中优先级)就绪,抢占任务 C,导致任务 C 无法释放互斥量。
  • 任务 A 被无限期阻塞,系统响应变慢,看门狗超时。

案例描述

假设系统包含三个任务:

  • Task_High(优先级 3):处理关键事件,需访问共享资源。
  • Task_Mid(优先级 5):执行中等优先级计算。
  • Task_Low(优先级 7):持有共享资源,执行低速操作。

系统使用独立看门狗,超时时间为 500ms。当 Task_High 等待 Task_Low 释放资源时,Task_Mid 频繁抢占,导致 Task_High 等待时间超过 500ms,看门狗复位。

排查流程

1. 确认看门狗触发原因

首先,通过日志或调试器确认复位是否由看门狗引起。在中断服务函数中记录复位标志,或使用硬件调试器查看复位原因寄存器。

// 示例:查看复位原因(以 STM32 为例)
if (RCC->CSR & RCC_CSR_WDGRSTF) {
    // 看门狗复位
    printf("Watchdog reset occurred\n");
}

2. 分析任务调度时序

使用 RTOS 的跟踪工具(如 FreeRTOS 的 trace 功能)记录任务状态变化,观察 Task_High 的阻塞时间。若发现阻塞时间远超预期,则怀疑优先级反转。

3. 检查资源访问方式

查看共享资源是否使用互斥量保护。若使用二值信号量,则不具备优先级继承能力,容易引发反转。

// 错误示例:使用二值信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
// 访问资源
xSemaphoreTake(xSemaphore, portMAX_DELAY);
// ... 访问共享资源
xSemaphoreGive(xSemaphore);

4. 引入互斥量并启用优先级继承

将二值信号量替换为互斥量,并确保 RTOS 支持优先级继承。在 FreeRTOS 中,互斥量默认支持优先级继承。

// 正确示例:使用互斥量
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// 访问资源
xSemaphoreTake(xMutex, portMAX_DELAY);
// ... 访问共享资源
xSemaphoreGive(xMutex);

优先级继承机制:当高优先级任务等待互斥量时,持有互斥量的低优先级任务会临时提升到高优先级,从而避免被中优先级任务抢占,直到释放互斥量。

配置步骤(以 FreeRTOS 为例)

  1. FreeRTOSConfig.h 中确保 configUSE_MUTEXES 为 1(默认开启)。
  2. 创建互斥量:
    SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
    
  3. 在任务中获取和释放互斥量:
    void Task_Low(void *params) {
        while (1) {
            xSemaphoreTake(xMutex, portMAX_DELAY);
            // 低速操作
            xSemaphoreGive(xMutex);
        }
    }
    
  4. 确保所有共享资源访问均使用同一互斥量。

完整代码示例

以下是一个简单的演示,模拟优先级反转场景及修复后的行为。

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

SemaphoreHandle_t xMutex;

void vTaskHigh(void *pvParameters) {
    for (;;) {
        xSemaphoreTake(xMutex, portMAX_DELAY);
        // 模拟关键处理
        vTaskDelay(pdMS_TO_TICKS(100));
        xSemaphoreGive(xMutex);
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

void vTaskMid(void *pvParameters) {
    for (;;) {
        // 模拟中等优先级任务频繁运行
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void vTaskLow(void *pvParameters) {
    for (;;) {
        xSemaphoreTake(xMutex, portMAX_DELAY);
        // 模拟低速操作,持有互斥量较长时间
        vTaskDelay(pdMS_TO_TICKS(200));
        xSemaphoreGive(xMutex);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

int main(void) {
    xMutex = xSemaphoreCreateMutex();
    if (xMutex != NULL) {
        xTaskCreate(vTaskHigh, "High", 128, NULL, 3, NULL);
        xTaskCreate(vTaskMid, "Mid", 128, NULL, 5, NULL);
        xTaskCreate(vTaskLow, "Low", 128, NULL, 7, NULL);
        vTaskStartScheduler();
    }
    for (;;);
}

在修复前(使用二值信号量),Task_High 可能等待超过 500ms;使用互斥量后,Task_Low 会临时提升优先级,快速释放资源,等待时间缩短至可接受范围。

注意事项

  • 优先级继承的局限性:它只能缓解反转,不能完全消除。若持有资源的任务被多个高优先级任务等待,可能发生“优先级冲撞”,需结合其他策略(如优先级天花板)。
  • 看门狗超时设置:应基于最坏情况下的任务执行时间,留出余量,避免误触发。
  • 避免在中断中获取互斥量:互斥量可能导致阻塞,不适合中断上下文。
  • 使用死锁检测:在开发阶段启用 RTOS 的死锁检测功能,提前发现潜在问题。
  • 测试覆盖:通过压力测试模拟高、中、低优先级任务同时运行,验证系统稳定性。

总结

优先级反转是 RTOS 开发中的经典陷阱,看门狗误触发只是其表象。通过使用互斥量并利用优先级继承机制,可以有效缩短高优先级任务的阻塞时间,避免系统复位。本文提供的排查流程和代码示例,可帮助开发者快速定位并解决此类问题,确保嵌入式系统的实时性和可靠性。