跳转至

优先级反转及其解决方案

1. 引言

今天分享关于优先级反转问题,实时操作系统本身对时间要求比较高,需要很强的实时性。优先级反转问题出现在实时操作系统会导致一些任务不能够实时运行造成出现一些问题。这种优先级反转问题可以看作为:是一个高优先级任务等待一个低优先级任务完成,因为低优先级任务持有一个共享资源(例如一个锁),而这个共享资源又被中优先级任务所抢占。

2. 优先级反转发生情况

  • 实时操作系统中的任务优先级
  • 共享资源和锁的使用
  • 优先级反转发生场景

在实时操作系统中,不同的任务根据其重要性被分配不同的优先级。为了避免资源竞争,任务通常需要对共享资源加锁。然而,这种锁机制可能导致优先级反转。

可以简单一句话:高优先级任务被低优先级的任务阻塞,导致高优先级任务不能立刻得到执行。但其他中等优先级的任务能够得到执行。

这样的现象看起来,高优先级的任务不能够立刻得到调度,中等优先级任务却已经在运行。

image-20240920210233789

3. 优先级反转例子

假定平台:FreeRTOS

假设我们有三个任务:

  • 任务A(高优先级,优先级3)
  • 任务B(低优先级,优先级1)
  • 任务C(中优先级,优先级2)

任务A需要访问一个共享资源,该资源由任务B持有锁。在任务B执行过程中,任务C抢占了任务B,导致任务A被阻塞。

时间轴: T0      T1      T2      T3
-----------------------------------
任务A: |       |       |XXXXXXX|-------|
任务B: |-------|       |       |-------|
任务C: |       |-------|       |-------|
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

SemaphoreHandle_t xMutex;

void vTaskA(void *pvParameters) {
    for(;;) {
        // 尝试获取共享资源
        if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
            // 访问共享资源
            // ...
            // 释放共享资源
            xSemaphoreGive(xMutex);
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void vTaskB(void *pvParameters) {
    for(;;) {
        // 获取共享资源
        if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
            // 占用共享资源一段时间
            vTaskDelay(200);
            // 释放共享资源
            xSemaphoreGive(xMutex);
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void vTaskC(void *pvParameters) {
    for(;;) {
        // 中优先级任务执行
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

int main(void) {
    xMutex = xSemaphoreCreateMutex();

    xTaskCreate(vTaskA, "Task A", configMINIMAL_STACK_SIZE, NULL, 3, NULL);
    xTaskCreate(vTaskB, "Task B", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
    xTaskCreate(vTaskC, "Task C", configMINIMAL_STACK_SIZE, NULL, 2, NULL);

    vTaskStartScheduler();

    for(;;);
}

从上面代码可以看到Task A 任务都需要去等待Task C 任务获释放资源才能得到运行。

4. 优先级反转解决措施

4.1 优先级继承协议

当一个低优先级任务持有一个共享资源并阻塞了一个高优先级任务时,低优先级任务临时继承高优先级任务的优先级,直到它释放资源。

根据上面例子解释:

任务B优先级最低,任务B运行时,获取共享资源后,被任务C占用,因为C的优先级更高;

解决点在这任务B运行时候,将低优先级任务B的优先级临时提到到与高优先级一样的级别,使得低优先级能够快速运行,快速释放临界资源,离开当前低优先级后,恢复原本的优先级。

xMutex = xSemaphoreCreateMutex();

4.2 优先级天花板

优先级天花板协议是通过为每个共享资源设置一个最高优先级(天花板优先级),确保持有资源的任务至少有这个优先级。这种方法虽然 FreeRTOS 没有直接支持,但可以通过编写自定义的锁定和解锁函数来实现。

根据上面例子解释:

当运行到任务B最低优先级时候,获取共享资源锁时候,这里将通过lock 将当前优先级提到最高,让任务B运行释放资源,再运行任务A.

void lock(SemaphoreHandle_t xMutex) {
    uxOriginalPriority = uxTaskPriorityGet(NULL);
    vTaskPrioritySet(NULL, RESOURCE_PRIORITY_CEILING);
    xSemaphoreTake(xMutex, portMAX_DELAY);
}

void unlock(SemaphoreHandle_t xMutex) {
    xSemaphoreGive(xMutex);
    vTaskPrioritySet(NULL, uxOriginalPriority);
}

// 放入到共享资源部分
void vTaskB(void *pvParameters) {
    for(;;) {
        // 尝试获取共享资源
        lock(xMutex);
        // 访问共享资源
        // ...
        unlock(xMutex);
        vTaskDelay(100);
    }
}

在这个实现中,我们定义了 lockunlock 函数来手动管理任务的优先级。每当任务锁定资源时,它的优先级会被提升到资源的天花板优先级,释放资源后恢复原来的优先级。

4.3 措施对比

优先级继承,当有优先级高的任务访问被低优先级任务占用时候,会提高占用任务的优先级;

优先级天花板是,无论发生阻塞都会提升,等于谁拿到资源的就提升当前任务的优先级到天花板级别,离开后又恢复。

还有其他解决优先级反转方式:采用禁止抢占,优化任务调度,使用非阻塞同步机制,尽量减少任务等待时间。

5. 总结

本章节简单对优先级反转相关问题进行一个简单分析,在实际的实时操作系统特别注意,任务优先级也需要一定的合理制定,对于这种可能存在优先级反转的情况,可以通过优先级继承协议和优先级天花板协议,可以有效地解决这一问题。本文若有错误可留言或后台私信!