一、为什么需要空闲任务?
在 FreeRTOS 中,CPU 必须至少有一个任务在运行。如果所有用户任务都进入了阻塞态,那 CPU 就没有用户任务可跑了。这时,内核会自动创建一个优先级最低的空闲任务来兜底。
你可以把空闲任务理解成"备胎":大家忙的时候它不抢 CPU;大家都不忙了,它就出来运行,保证系统不空转。
二、空闲任务是什么?
空闲任务由内核在启动调度器时自动创建,不需要用户手动创建。
它的特点:
- 优先级最低,通常是 0;
- 永远处于就绪态或运行态;
- 当没有其他任务能运行时,它就开始运行。
代码里看不到它,因为它是内核自动生成的,函数名通常叫 prvIdleTask。
三、空闲任务的主要作用
1. 兜底运行
所有用户任务都阻塞时,空闲任务保证 CPU 有事情做,系统不会"死掉"。
2. 回收删除任务的内存
如果某个任务被删除,比如调用:
c
vTaskDelete(NULL);
被删除任务的栈和任务控制块(TCB)不会立即释放,而是由空闲任务去回收。
所以,如果空闲任务长时间得不到运行,删除任务后可能会出现内存泄漏。
3. 提供空闲钩子函数
用户可以通过 vApplicationIdleHook() 在空闲任务里执行一些低优先级工作,比如:
c
void vApplicationIdleHook(void)
{
// 进入低功耗模式
// 或者喂狗、统计 CPU 使用率等
}
注意:空闲钩子里不能调用会阻塞的 API ,比如 vTaskDelay、信号量等待等。
四、阻塞延时:把 CPU 让出来
1. 裸机死等 vs RTOS 阻塞
裸机里延时通常是空转:
c
delay(1000); // CPU 空转 1 秒,啥也不干
这种方式浪费 CPU。FreeRTOS 提供了阻塞延时:
c
vTaskDelay(pdMS_TO_TICKS(1000)); // 任务阻塞 1 秒,让出 CPU
任务调用阻塞延时后,会进入阻塞态,CPU 就可以去运行其他任务,或者运行空闲任务。等延时时间到了,任务再回到就绪态。
2. vTaskDelay:相对延时
常用 API:
c
void vTaskDelay(TickType_t xTicksToDelay);
参数是 tick 数,不是毫秒。通常用 pdMS_TO_TICKS() 转换:
c
// 阻塞 500ms
vTaskDelay(pdMS_TO_TICKS(500));
示例:
c
void vTask1(void *pvParameters)
{
for (;;)
{
printf("Task1 running\r\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 阻塞 1 秒
}
}
vTaskDelay 是相对延时,它的起点是调用函数的那一刻。如果任务频繁被抢占,实际周期可能会漂移。
3. vTaskDelayUntil:绝对延时
如果希望任务严格按固定周期运行,用 vTaskDelayUntil:
c
void vTaskA(void *pvParameters)
{
TickType_t xLastWakeTime = xTaskGetTickCount();
for (;;)
{
printf("TaskA periodic running\r\n");
// 绝对延时 1 秒,保证周期稳定
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
}
}
注意:xLastWakeTime 要初始化为当前 tick,不要每次循环都重新赋值,否则就变成了相对延时。
五、空闲任务和阻塞延时的关系
一句话:阻塞延时让 CPU 有机会去运行其他任务;当所有任务都阻塞时,CPU 就去运行空闲任务。
执行流程大致如下:
- 任务 A 调用
vTaskDelay,进入阻塞态; - 调度器找到下一个就绪任务,比如任务 B,开始运行;
- 如果任务 B 也阻塞了,且没有其他就绪任务,空闲任务开始运行;
- 系统滴答定时器中断不断检查延时时间;
- 任务 A 延时到期,回到就绪态;
- 由于任务 A 优先级高于空闲任务,发生任务切换,任务 A 继续运行。
所以,阻塞延时不是 CPU 在空等,而是把 CPU 让给别的任务或空闲任务,大大提高利用率。
六、几个容易踩的坑
-
空闲钩子里不要调用阻塞 API
比如
vTaskDelay、信号量等待等,否则可能导致异常。 -
删除任务后要确保空闲任务能运行
如果长期有高优先级任务占用 CPU,空闲任务无法运行,被删除任务的内存就迟迟不被回收。
-
延时单位是 tick,不是毫秒
记得用
pdMS_TO_TICKS()转换,避免实际延时和预期不一致。 -
严格周期场景用
vTaskDelayUntil用
vTaskDelay做周期任务,时间会越漂越远。
七、总结
- 空闲任务是内核自动创建的最低优先级任务,用来兜底运行、回收删除任务的内存、提供空闲钩子。
- 阻塞延时让任务主动让出 CPU,进入阻塞态,等时间到了再运行。
vTaskDelay是相对延时,vTaskDelayUntil是绝对延时。- 所有用户任务阻塞时,CPU 运行空闲任务;延时到期后,用户任务会抢占空闲任务继续运行。
理解这两个概念,就能更好地设计 FreeRTOS 任务的运行与周期控制。