2. 任务优先级抢占

任务说明

本案例在RTOS中创建两个不同优先级的任务:低优先级任务Task_Low与高优先级任务Task_High。Task_Low执行较长时间的计算操作,Task_High在运行过程中被周期性地唤醒。目标是通过串口日志观察高优先级任务就绪后立即抢占正在运行的低优先级任务,从而直观理解基于优先级的抢占式调度机制。

实现原理

采用FreeRTOS作为实现载体。系统使用抢占式调度器,当高优先级任务进入就绪态时,若当前运行的是低优先级任务,调度器会立即保存低优先级任务上下文并切换到高优先级任务执行。本案例中Task_Low通过循环累加模拟长时间工作,Task_High通过延时函数周期性进入就绪态,每次就绪都会触发抢占。

代码实现


#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* 串口发送函数(硬件相关,需根据平台实现) */
extern void UART_SendString(const char *str);

/* 任务句柄 */
TaskHandle_t xTaskLowHandle = NULL;
TaskHandle_t xTaskHighHandle = NULL;

/* 低优先级任务:模拟长时间计算 */
void vTaskLow(void *pvParameters)
{
    volatile uint32_t ulCounter;
    const char *pcMsg = "[LOW] Running...\r\n";

    for (;;)
    {
        UART_SendString(pcMsg);

        /* 模拟长时间工作:循环累加约占用数毫秒 */
        for (ulCounter = 0; ulCounter < 200000; ulCounter++)
        {
            /* 空操作,仅消耗CPU时间 */
        }
    }
}

/* 高优先级任务:周期性唤醒并抢占 */
void vTaskHigh(void *pvParameters)
{
    const char *pcMsg = "[HIGH] Preempted! Low task suspended.\r\n";

    for (;;)
    {
        /* 延时期间低优先级任务得以运行 */
        vTaskDelay(pdMS_TO_TICKS(50));

        /* 延时结束,本任务进入就绪态,抢占低优先级任务 */
        UART_SendString(pcMsg);

        /* 短暂运行后再次阻塞,让出CPU */
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

int main(void)
{
    /* 硬件初始化(时钟、串口等)由平台相关代码完成 */

    /* 创建低优先级任务,优先级为1 */
    xTaskCreate(vTaskLow, "Task_Low", 128, NULL, 1, &xTaskLowHandle);

    /* 创建高优先级任务,优先级为2 */
    xTaskCreate(vTaskHigh, "Task_High", 128, NULL, 2, &xTaskHighHandle);

    /* 启动调度器 */
    vTaskStartScheduler();

    /* 正常情况下不会执行到这里 */
    for (;;)
    {
    }
}

代码说明

两个任务均使用无限循环结构。Task_Low优先级为1,内部通过volatile变量累加模拟耗时操作,每次循环都会输出标记信息。Task_High优先级为2,先延时50ms让低优先级任务运行,延时结束后进入就绪态,此时因优先级高于当前运行的Task_Low,调度器立即执行任务切换。Task_High输出抢占信息后延时10ms主动阻塞,将CPU让给低优先级任务,形成周期性的抢占-释放过程。

SVG时序图

任务优先级抢占时序图 Task_Low (P1) Task_High (P2) 调度器状态 t0 t1 t2 t3 t4 运行中 运行中 挂起 挂起 阻塞 抢占运行 阻塞 抢占运行 调度 LOW 切换→HIGH 调度 LOW 切换→HIGH 低优先级任务运行 高优先级任务运行 任务挂起/阻塞

编译与运行

本案例适用于任何支持FreeRTOS的嵌入式平台(如STM32、ESP32等)。需完成以下步骤:

  1. 将上述代码加入FreeRTOS工程,确保包含FreeRTOS.h和task.h头文件路径;
  2. 实现UART_SendString函数,使其通过串口输出字符串;
  3. 根据平台配置系统时钟和中断,确保FreeRTOS心跳正常;
  4. 编译并烧录到目标板。

验证方法

打开串口终端(波特率与平台配置一致),观察输出日志。预期输出模式如下:


[LOW] Running...
[LOW] Running...
[HIGH] Preempted! Low task suspended.
[LOW] Running...
[LOW] Running...
[HIGH] Preempted! Low task suspended.
...

判定标准:若串口日志中周期性出现[HIGH]消息,且[HIGH]消息之间间隔约60ms(50ms延时+10ms运行),同时[LOW]消息在[HIGH]消息出现时中断、之后继续输出,则说明高优先级任务成功抢占低优先级任务,验证通过。若日志中[LOW]消息连续输出而[HIGH]消息长时间不出现,则检查任务优先级设置或延时配置是否正确。