一、为什么需要优先级继承

在FreeRTOS实时操作系统中,多个任务之间常常需要共享某些资源,比如打印机、内存缓冲区等。为了防止多个任务同时访问同一资源导致数据混乱,我们需要使用一种"锁"的机制来保护这些资源。二值信号量和互斥量就是这种"锁"的实现方式。

但是,当我们使用二值信号量时,会遇到一个棘手的问题——优先级翻转。这个问题严重时,甚至会让高优先级任务反而得不到执行,与实时系统的设计初衷相违背。优先级继承机制就是为了解决这个问题而设计的。

二、基础知识

2.1 同步与互斥的概念

我们需要先理解两个基础概念:同步互斥

同步是指任务之间的执行顺序有明确要求,必须按照一定的先后顺序执行。比如任务A必须等到任务B完成某项工作后才能继续执行,这就像排队等候一样,有明确的先后次序。

互斥则是指某个资源在某一时刻只能被一个任务访问,不能同时被多个任务使用。这就像一个厕所同一时间只能一个人使用一样具有排他性。互斥并不关心谁先谁后,只关心不能同时使用。

2.2 什么是信号量

信号量是操作系统中一种常用的同步与互斥机制。它的核心思想很简单:一个非负整数,用来表示当前可用的资源数量。

  • 当信号量值为0时,表示没有可用资源,任务无法获取
  • 当信号量值大于0时,表示还有可用资源,任务可以获取
  • 每获取一次信号量,值减1;每释放一次信号量,值加1

根据信号量值的不同,信号量又分为两种:

二值信号量:资源最多只有1个,就像一个开关,要么有资源(值为1),要么没有资源(值为0)。这种信号量常用于简单的互斥访问或任务间同步。

计数信号量:资源数量可以大于1,比如一个停车场有10个车位,那么计数信号量的最大值就是10,每停一辆车减1,每开走一辆车加1。这种信号量常用于资源管理或事件计数。

2.3 互斥量又是什么

互斥量是包含优先级继承机制的二值信号量。它与二值信号量非常相似,都只能同时被一个任务持有,但最关键的区别在于:互斥量拥有优先级继承机制,而二值信号量没有

我们可以把互斥量想象成一把带有特殊规则的"智能锁":当高优先级任务想要获取这把锁,但低优先级任务正在持有它时,操作系统会自动提升低优先级任务的优先级,让它能够更快地完成工作并释放锁。这就是所谓的"优先级继承"。

三、优先级翻转问题

3.1 问题现象

要理解优先级继承为什么重要,我们首先要明白没有它时会出什么问题。这就要从优先级翻转说起。

让我们通过一个具体的例子来说明:

假设系统中有三个任务:

  • Task1:优先级3(最高)
  • Task2:优先级2(中等)
  • Task3:优先级1(最低)

现在的情况是:Task3首先获得了某个共享资源的访问权(通过二值信号量),而Task1也需要访问这个资源。由于资源已被占用,Task1只能阻塞等待。

就在这时,Task2就绪了。由于Task2的优先级比Task3高,Task2可以抢占Task3执行。Task3被Task2打断了,只能等Task2执行完毕后才能继续运行并最终释放资源。

最终的执行顺序是:Task3 → Task2 → Task3 → Task1

问题出现了:明明Task1的优先级最高(3),Task2的优先级只是中等(2),但Task1却必须等待Task2执行完毕才能执行!这完全违背了"高优先级任务应该优先执行"的基本原则。

3.2 问题根因

优先级翻转的根源在于:高优先级任务在等待低优先级任务释放资源时,无法抢占低优先级任务。因为中间优先级的任务可以抢占低优先级任务,导致高优先级任务被"插队"了。

这种问题在实时系统中是致命的,因为实时系统要求高优先级任务必须能够尽快得到响应。如果高优先级任务因为优先级翻转而长时间得不到执行,系统将无法满足实时性要求。

四、优先级继承机制详解

4.1 基本原理

优先级继承机制的核心思想并不复杂:当高优先级任务TH在等待低优先级任务TL占用的竞争资源时,操作系统会自动把TL的优先级提升到TH的优先级。这样一来,TL就会以更高的优先级参与调度,能够更快地完成工作并释放资源,从而让TH能够尽快得到执行。

继续用上面的例子来说明优先级继承如何解决优先级翻转问题:

  • Task3(优先级1)持有互斥量
  • Task1(优先级3)想要获取互斥量但被阻塞
  • 此时,操作系统检测到Task1比Task3优先级高
  • 自动将Task3的优先级从1提升到3(与Task1相同)
  • 即使Task2(优先级2)就绪,也无法抢占Task3
  • Task3以优先级3执行,很快释放互斥量
  • Task1获取到互斥量,继续执行

这样一来,执行顺序就变成了:Task3 → Task1,完美地体现了高优先级任务优先执行的原则。

4.2 重要特性

需要特别注意的是:优先级继承不能完全解决优先级翻转问题,只能将其影响降到最低。这主要有以下几个原因:

首先,优先级继承只是临时提升持有互斥量的低优先级任务的优先级,并不能改变任务之间的基本调度关系。如果有多个不同优先级的任务都在等待同一个互斥量,系统只能提升持有者的优先级到等待者中最高的那一个。

其次,优先级继承会增加系统的复杂性和开销。每次获取互斥量时都需要判断是否需要继承优先级,释放时又需要判断是否需要解除继承。这些额外的操作都会消耗CPU时间。

最后,当一个任务持有多个互斥量时,优先级继承的处理会变得更加复杂。FreeRTOS的规定是:当持有者持有多个互斥量时,不能通过单个互斥量来解除或重置优先级继承的优先级。这意味着只有当持有者释放所有互斥量后,优先级才能完全恢复。

五、例程

5.1.代码

/**
 * @file freertos_priority_inheritance_demo.c
 * @brief FreeRTOS优先级继承机制演示例程
 *
 * 本例程演示了:
 * 1. 使用二值信号量时的优先级翻转问题
 * 2. 使用互斥量时的优先级继承如何解决问题
 *
 * 使用方法:
 *   将 USE_MUTEX 设为 0  -> 演示优先级翻转(二值信号量)
 *   将 USE_MUTEX 设为 1  -> 演示优先级继承(互斥量)
 *
 * @author MiniMax Agent
 * @date 2026
 */

/*============================================================================
 * FreeRTOS配置
 *============================================================================*/
#define USE_MUTEX               1       // 1=使用互斥量(启用优先级继承)
                                        // 0=使用二值信号量(显示优先级翻转)

#define configUSE_PREEMPTION    1       // 启用抢占式调度
#define configUSE_TIME_SLICING  1       // 启用时间片
#define configUSE_MUTEXES       1       // 启用互斥量
#define configMAX_PRIORITIES    5       // 最大优先级数

/*============================================================================
 * 头文件
 *============================================================================*/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"
#include <stdio.h>
#include <string.h>

/*============================================================================
 * 常量定义
 *============================================================================*/
// 任务优先级定义(数值越大优先级越高)
#define PRIORITY_LOW            1       // 低优先级任务
#define PRIORITY_MEDIUM         2       // 中优先级任务
#define PRIORITY_HIGH           3       // 高优先级任务

// 任务参数
#define TASK_DELAY_LOW          100     // 低优先级任务延迟(ms)
#define TASK_DELAY_MEDIUM       80      // 中优先级任务延迟(ms)
#define TASK_DELAY_HIGH         50      // 高优先级任务延迟(ms)

// 工作模拟时间(滴答数)
#define WORK_TIME_LOW           200     // 低优先级任务持有锁的时间
#define WORK_TIME_MEDIUM        150     // 中优先级任务工作时间

/*============================================================================
 * 全局变量
 *============================================================================*/
#if USE_MUTEX
    SemaphoreHandle_t xSharedLock;     // 互斥量(支持优先级继承)
#else
    SemaphoreHandle_t xSharedLock;     // 二值信号量(不支持优先级继承)
#endif

// 任务控制块(用于调试输出)
static TaskHandle_t xHandleTaskL = NULL;
static TaskHandle_t xHandleTaskM = NULL;
static TaskHandle_t xHandleTaskH = NULL;

// 时间戳计数器
static volatile uint32_t ulGlobalTick = 0;

/*============================================================================
 * 函数原型
 *============================================================================*/
static void vTaskL(void *pvParameters);
static void vTaskM(void *pvParameters);
static void vTaskH(void *pvParameters);
static void vPrintLog(const char *pcTaskName, int iPriority, const char *pcMessage);
static void vBusyWait(volatile uint32_t ulCount);

/*============================================================================
 * 仿真延时函数(不使用vTaskDelay,以便观察优先级翻转)
 *============================================================================*/
static void vBusyWait(volatile uint32_t ulCount)
{
    while(ulCount--)
    {
        __asm("nop");  // 空操作,消耗CPU时间
    }
}

/*============================================================================
 * 日志输出函数
 *============================================================================*/
static void vPrintLog(const char *pcTaskName, int iPriority, const char *pcMessage)
{
    // 在实际硬件中,这里应该通过UART输出
    // 这里使用printf模拟
    printf("[Tick:%04lu] [任务:%s] [优先级:%d] : %s\n",
           (unsigned long)ulGlobalTick, pcTaskName, iPriority, pcMessage);
}

/*============================================================================
 * 低优先级任务 (TaskL)
 *
 * 任务说明:
 * - 优先级最低:PRIORITY_LOW (1)
 * - 最先获取共享资源的锁
 * - 模拟持有锁一段时间
 * - 在二值信号量模式下,会被中优先级任务抢占
 * - 在互斥量模式下,优先级会被提升
 *============================================================================*/
static void vTaskL(void *pvParameters)
{
    (void)pvParameters;  // 未使用的参数

    // 任务启动延迟,确保它最先开始执行
    vTaskDelay(pdMS_TO_TICKS(10));

    vPrintLog("TaskL", PRIORITY_LOW, "尝试获取共享资源锁...");

    // 尝试获取锁(二值信号量或互斥量)
    if(xSemaphoreTake(xSharedLock, portMAX_DELAY) == pdTRUE)
    {
        vPrintLog("TaskL", PRIORITY_LOW, "成功获取锁!开始工作...");

        // 模拟持有锁进行工作(使用忙等待,不用vTaskDelay)
        // 这样可以让其他任务有机会抢占CPU
        vBusyWait(WORK_TIME_LOW);

        vPrintLog("TaskL", PRIORITY_LOW, "工作完成,释放锁!");

        // 释放锁
        xSemaphoreGive(xSharedLock);
    }
    else
    {
        vPrintLog("TaskL", PRIORITY_LOW, "获取锁失败!");
    }

    // 任务完成,删除自己
    vPrintLog("TaskL", PRIORITY_LOW, "任务结束");
    vTaskDelete(NULL);
}

/*============================================================================
 * 中优先级任务 (TaskM)
 *
 * 任务说明:
 * - 优先级中等:PRIORITY_MEDIUM (2)
 * - 不需要访问共享资源
 * - 纯粹占用CPU时间,模拟一个繁忙的后台任务
 * - 在二值信号量模式下会抢占低优先级任务
 * - 在互斥量模式下,如果低优先级任务被提升优先级,则无法抢占
 *============================================================================*/
static void vTaskM(void *pvParameters)
{
    (void)pvParameters;

    // 任务启动延迟
    vTaskDelay(pdMS_TO_TICKS(20));

    vPrintLog("TaskM", PRIORITY_MEDIUM, "开始执行(繁忙的后台任务)...");

    // 模拟繁忙的CPU工作(不使用vTaskDelay)
    vBusyWait(WORK_TIME_MEDIUM);

    vPrintLog("TaskM", PRIORITY_MEDIUM, "工作完成!");

    // 任务完成,删除自己
    vPrintLog("TaskM", PRIORITY_MEDIUM, "任务结束");
    vTaskDelete(NULL);
}

/*============================================================================
 * 高优先级任务 (TaskH)
 *
 * 任务说明:
 * - 优先级最高:PRIORITY_HIGH (3)
 * - 需要访问共享资源
 * - 在低优先级任务持有锁时被阻塞
 * - 在二值信号量模式下,需要等待低优先级任务释放锁
 * - 在互斥量模式下,低优先级任务的优先级会被提升
 *============================================================================*/
static void vTaskH(void *pvParameters)
{
    (void)pvParameters;

    // 任务启动延迟,确保低优先级任务先获取锁
    vTaskDelay(pdMS_TO_TICKS(15));

    vPrintLog("TaskH", PRIORITY_HIGH, "尝试获取共享资源锁...");

    // 尝试获取锁(会被阻塞,直到锁可用)
    if(xSemaphoreTake(xSharedLock, portMAX_DELAY) == pdTRUE)
    {
        vPrintLog("TaskH", PRIORITY_HIGH, "成功获取锁!开始紧急工作...");

        // 模拟短暂的工作
        vTaskDelay(pdMS_TO_TICKS(10));

        vPrintLog("TaskH", PRIORITY_HIGH, "工作完成,释放锁!");

        // 释放锁
        xSemaphoreGive(xSharedLock);
    }
    else
    {
        vPrintLog("TaskH", PRIORITY_HIGH, "获取锁失败!");
    }

    // 任务完成
    vPrintLog("TaskH", PRIORITY_HIGH, "任务结束");
    vTaskDelete(NULL);
}

/*============================================================================
 * 系统心跳任务(模拟系统时间)
 *============================================================================*/
static void vHeartbeatTask(void *pvParameters)
{
    (void)pvParameters;

    while(1)
    {
        ulGlobalTick++;
        vTaskDelay(pdMS_TO_TICKS(1));  // 每1ms心跳一次
    }
}

/*============================================================================
 * 主函数
 *============================================================================*/
int main(void)
{
    // 初始化全局计数器
    ulGlobalTick = 0;

    // 根据配置创建信号量或互斥量
    #if USE_MUTEX
        printf("\n========================================\n");
        printf("  模式:使用互斥量(启用优先级继承)\n");
        printf("========================================\n\n");

        // 创建互斥量
        xSharedLock = xSemaphoreCreateMutex();

        if(xSharedLock == NULL)
        {
            printf("错误:互斥量创建失败!\n");
            while(1);
        }

        printf("互斥量创建成功!\n");
        printf("预期结果:高优先级任务TaskH应该优先于TaskM完成\n\n");

    #else
        printf("\n========================================\n");
        printf("  模式:使用二值信号量(无优先级继承)\n");
        printf("========================================\n\n");

        // 创建二值信号量
        xSharedLock = xSemaphoreCreateBinary();

        if(xSharedLock == NULL)
        {
            printf("错误:二值信号量创建失败!\n");
            while(1);
        }

        // 初始化二值信号量为1(可用状态)
        if(xSemaphoreGive(xSharedLock) != pdTRUE)
        {
            printf("错误:二值信号量初始化失败!\n");
            while(1);
        }

        printf("二值信号量创建成功!\n");
        printf("预期结果:中优先级任务TaskM会抢占TaskL,导致TaskH等待时间变长\n\n");

    #endif

    // 创建心跳任务(用于显示时间)
    xTaskCreate(vHeartbeatTask, "Heartbeat", configMINIMAL_STACK_SIZE,
                NULL, PRIORITY_LOW + 1, NULL);

    // 创建低优先级任务
    xTaskCreate(vTaskL, "TaskL", configMINIMAL_STACK_SIZE,
                NULL, PRIORITY_LOW, &xHandleTaskL);

    // 创建中优先级任务
    xTaskCreate(vTaskM, "TaskM", configMINIMAL_STACK_SIZE,
                NULL, PRIORITY_MEDIUM, &xHandleTaskM);

    // 创建高优先级任务
    xTaskCreate(vTaskH, "TaskH", configMINIMAL_STACK_SIZE,
                NULL, PRIORITY_HIGH, &xHandleTaskH);

    printf("所有任务创建完成,开始调度!\n");
    printf("----------------------------------------\n");

    // 启动调度器
    vTaskStartScheduler();

    // 正常情况下不会到达这里
    printf("调度器异常退出!\n");
    while(1);

    return 0;
}

5.2结果分析


 * 【场景一:USE_MUTEX = 0 (二值信号量,无优先级继承)】
 *
 * 预期输出:
 * [Tick:0010] [任务:TaskL] [优先级:1] : 尝试获取共享资源锁...
 * [Tick:0010] [任务:TaskL] [优先级:1] : 成功获取锁!开始工作...
 * [Tick:0015] [任务:TaskH] [优先级:3] : 尝试获取共享资源锁...
 * [Tick:0015] [任务:TaskH] [优先级:3] : 成功获取锁!开始紧急工作...
 * [Tick:0020] [任务:TaskM] [优先级:2] : 开始执行(繁忙的后台任务)...
 * [Tick:0020] [任务:TaskM] [优先级:2] : 工作完成!
 * [Tick:0035] [任务:TaskL] [优先级:1] : 工作完成,释放锁!
 * [Tick:0035] [任务:TaskH] [优先级:3] : 工作完成,释放锁!
 * [Tick:0035] [任务:TaskM] [优先级:2] : 任务结束
 * [Tick:0035] [任务:TaskL] [优先级:1] : 任务结束
 * [Tick:0035] [任务:TaskH] [优先级:3] : 任务结束
 *
 * 分析:
 * - TaskL最先获取锁,开始工作
 * - TaskH随后获取锁,但被阻塞(等待TaskL释放)
 * - TaskM在TaskH之后启动,但由于TaskL持有锁,TaskM可以抢占TaskL
 * - 这就是"优先级翻转":高优先级TaskH反而要等TaskM完成

 * 【场景二:USE_MUTEX = 1 (互斥量,有优先级继承)】
 *
 * 预期输出:
 * [Tick:0010] [任务:TaskL] [优先级:1] : 尝试获取共享资源锁...
 * [Tick:0010] [任务:TaskL] [优先级:1] : 成功获取锁!开始工作...
 * [Tick:0015] [任务:TaskH] [优先级:3] : 尝试获取共享资源锁...
 * [Tick:0015] [任务:TaskH] [优先级:3] : 成功获取锁!开始紧急工作...
 * [Tick:0035] [任务:TaskL] [优先级:1] : 工作完成,释放锁!
 * [Tick:0035] [任务:TaskH] [优先级:3] : 工作完成,释放锁!
 * [Tick:0035] [任务:TaskM] [优先级:2] : 开始执行(繁忙的后台任务)...
 * [Tick:0035] [任务:TaskM] [优先级:2] : 工作完成!
 * [Tick:0035] [任务:TaskM] [优先级:2] : 任务结束
 * [Tick:0035] [任务:TaskL] [优先级:1] : 任务结束
 * [Tick:0035] [任务:TaskH] [优先级:3] : 任务结束
 *
 * 分析:
 * - TaskL最先获取锁,开始工作
 * - TaskH尝试获取锁时,检测到TaskL优先级低于自己
 * - 系统自动将TaskL的优先级从1提升到3(与TaskH相同)
 * - TaskM虽然就绪,但优先级为2,低于提升后的TaskL(3)
 * - TaskL得以快速完成并释放锁
 * - TaskH立即获取锁并执行
 * - 最后才是TaskM执行
 * - 这就是"优先级继承"的效果!
 */

六、总结


  1. 优先级翻转(Priority Inversion)
     - 定义:高优先级任务等待低优先级任务释放资源,而中优先级任务
            抢占了低优先级任务,导致高优先级任务反而晚完成
     - 原因:二值信号量不支持优先级继承
     - 危害:破坏实时系统的确定性,高优先级任务响应时间不可预测
 
  2. 优先级继承(Priority Inheritance)
     - 定义:当高优先级任务等待低优先级任务持有的资源时,
            系统临时提升低优先级任务的优先级
     - 实现:通过互斥量(Mutex)实现
     - 效果:让持有资源的低优先级任务尽快完成,释放资源
 
  3. 二值信号量 vs 互斥量
     - 二值信号量:用于任务同步,不支持优先级继承
     - 互斥量:用于资源保护,支持优先级继承
 
  4. 使用建议
     - 保护临界资源(需要互斥访问):使用互斥量
     - 任务间同步(一个任务等待另一个任务):使用信号量
     - 永远不要在中断服务程序(ISR)中使用互斥量
 

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐