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
...
判定标准:
- 启动后首先连续输出5次
[High] Running,期间无[Low]输出——证明高优先级任务抢占低优先级任务。 - 第5次后出现
[Low] Running——证明高优先级任务挂起后,低优先级任务获得CPU。 - 随后
[RR_A]与[RR_B]交替输出——证明同优先级任务按时间片轮转调度。 - 若
[RR_A]连续输出多次才切换到[RR_B],说明时间片长度设置较长或空循环耗时不足,可调整configTICK_RATE_HZ或循环次数。
关键说明
- 高优先级任务通过
vTaskSuspend(NULL)挂起自身,否则低优先级任务永远无法运行(SafeRTOS抢占式调度特性)。 - 时间片轮转依赖
configUSE_TIME_SLICING开启,且两个任务优先级必须相同。空循环用于消耗时间片,实际工程中可替换为真实工作负载。 - 若需观察更明显的抢占效果,可将高优先级任务的
vTaskDelay(1)改为更短延时或删除(但需保留某种阻塞点,否则低优先级任务饿死)。