2. 任务优先级抢占与时间片轮转

任务目标

在SafeRTOS中创建三个任务:两个不同优先级的任务用于验证抢占式调度(高优先级任务就绪时立即抢占低优先级任务),两个相同优先级的任务用于验证时间片轮转调度(同一优先级下多个任务按时间片交替执行)。通过串口日志观察任务执行顺序与切换行为。

核心实现

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

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

/* 任务函数声明 */
static void vHighPriorityTask(void *pvParameters);
static void vLowPriorityTask(void *pvParameters);
static void vRoundRobinTaskA(void *pvParameters);
static void vRoundRobinTaskB(void *pvParameters);

/* 任务句柄 */
static TaskHandle_t xHighTask = NULL;
static TaskHandle_t xLowTask = NULL;
static TaskHandle_t xTaskA = NULL;
static TaskHandle_t xTaskB = NULL;

/* 用于触发抢占的计数标志 */
static volatile uint32_t ulHighTaskCount = 0;

int main(void)
{
    /* 硬件初始化(时钟、UART等) */
    prvSetupHardware();

    /* 创建不同优先级任务:高优先级(3)与低优先级(1) */
    xTaskCreate(vHighPriorityTask, "High", 128, NULL, 3, &xHighTask);
    xTaskCreate(vLowPriorityTask,  "Low",  128, NULL, 1, &xLowTask);

    /* 创建相同优先级任务:优先级均为2,用于时间片轮转 */
    xTaskCreate(vRoundRobinTaskA, "RR_A", 128, NULL, 2, &xTaskA);
    xTaskCreate(vRoundRobinTaskB, "RR_B", 128, NULL, 2, &xTaskB);

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

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

/* 高优先级任务:运行5次后挂起自身,让低优先级任务有机会执行 */
static void vHighPriorityTask(void *pvParameters)
{
    for (;;)
    {
        ulHighTaskCount++;
        vUART_SendString("[High] Running\r\n");

        if (ulHighTaskCount >= 5)
        {
            /* 挂起自身,释放CPU给低优先级任务 */
            vTaskSuspend(NULL);
        }

        /* 主动让出CPU,但高优先级就绪时仍会立即抢占 */
        vTaskDelay(1);
    }
}

/* 低优先级任务:仅当高优先级任务挂起后才能运行 */
static void vLowPriorityTask(void *pvParameters)
{
    for (;;)
    {
        vUART_SendString("[Low] Running\r\n");
        vTaskDelay(10);
    }
}

/* 时间片轮转任务A */
static void vRoundRobinTaskA(void *pvParameters)
{
    for (;;)
    {
        vUART_SendString("[RR_A] Tick\r\n");
        /* 空循环消耗CPU时间,触发时间片切换 */
        for (volatile uint32_t i = 0; i < 100000; i++);
    }
}

/* 时间片轮转任务B */
static void vRoundRobinTaskB(void *pvParameters)
{
    for (;;)
    {
        vUART_SendString("[RR_B] Tick\r\n");
        for (volatile uint32_t i = 0; i < 100000; i++);
    }
}

运行与验证

环境依赖:SafeRTOS(API与FreeRTOS兼容),需提供vUART_SendString的硬件实现,并确保configUSE_PREEMPTION为1、configUSE_TIME_SLICING为1(SafeRTOS默认开启)。

编译与烧录:将上述代码加入工程,编译后烧录至目标板。若使用模拟器(如QEMU),可直接运行可执行文件。

预期串口输出(逻辑推导):

[High] Running   ← 重复5次(高优先级抢占低优先级)
[Low] Running    ← 高优先级挂起后,低优先级开始运行
[RR_A] Tick      ← 时间片轮转开始(高优先级已挂起)
[RR_B] Tick
[RR_A] Tick
[RR_B] Tick
...

判定标准:

  1. 启动后首先连续输出5次[High] Running,期间无[Low]输出——证明高优先级任务抢占低优先级任务。
  2. 第5次后出现[Low] Running——证明高优先级任务挂起后,低优先级任务获得CPU。
  3. 随后[RR_A]与[RR_B]交替输出——证明同优先级任务按时间片轮转调度。
  4. 若[RR_A]连续输出多次才切换到[RR_B],说明时间片长度设置较长或空循环耗时不足,可调整configTICK_RATE_HZ或循环次数。

关键说明

  • 高优先级任务通过vTaskSuspend(NULL)挂起自身,否则低优先级任务永远无法运行(SafeRTOS抢占式调度特性)。
  • 时间片轮转依赖configUSE_TIME_SLICING开启,且两个任务优先级必须相同。空循环用于消耗时间片,实际工程中可替换为真实工作负载。
  • 若需观察更明显的抢占效果,可将高优先级任务的vTaskDelay(1)改为更短延时或删除(但需保留某种阻塞点,否则低优先级任务饿死)。