FreeRTOS 空闲任务与阻塞延时

一、为什么需要空闲任务?

在 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 就去运行空闲任务。

执行流程大致如下:

  1. 任务 A 调用 vTaskDelay,进入阻塞态;
  2. 调度器找到下一个就绪任务,比如任务 B,开始运行;
  3. 如果任务 B 也阻塞了,且没有其他就绪任务,空闲任务开始运行;
  4. 系统滴答定时器中断不断检查延时时间;
  5. 任务 A 延时到期,回到就绪态;
  6. 由于任务 A 优先级高于空闲任务,发生任务切换,任务 A 继续运行。

所以,阻塞延时不是 CPU 在空等,而是把 CPU 让给别的任务或空闲任务,大大提高利用率。

六、几个容易踩的坑

  1. 空闲钩子里不要调用阻塞 API

    比如 vTaskDelay、信号量等待等,否则可能导致异常。

  2. 删除任务后要确保空闲任务能运行

    如果长期有高优先级任务占用 CPU,空闲任务无法运行,被删除任务的内存就迟迟不被回收。

  3. 延时单位是 tick,不是毫秒

    记得用 pdMS_TO_TICKS() 转换,避免实际延时和预期不一致。

  4. 严格周期场景用 vTaskDelayUntil

    vTaskDelay 做周期任务,时间会越漂越远。

七、总结

  • 空闲任务是内核自动创建的最低优先级任务,用来兜底运行、回收删除任务的内存、提供空闲钩子。
  • 阻塞延时让任务主动让出 CPU,进入阻塞态,等时间到了再运行。
  • vTaskDelay 是相对延时,vTaskDelayUntil 是绝对延时。
  • 所有用户任务阻塞时,CPU 运行空闲任务;延时到期后,用户任务会抢占空闲任务继续运行。

理解这两个概念,就能更好地设计 FreeRTOS 任务的运行与周期控制。

相关推荐
hsjiasb3 小时前
FreeRTOS学习(二十九)——低功耗Tickless Idle
学习·学习笔记·freertos
捷瑞电子工坊1 天前
FreeRTOS 就绪列表详解:任务调度的核心枢纽
freertos·任务调度·优先级·嵌入式实时操作系统·就绪列表
hsjiasb3 天前
FreeRTOS学习(二十六)——动态内存管理heap_1到heap_5
stm32·单片机·学习·学习笔记·freertos
hsjiasb4 天前
FreeRTOS学习(二十三)——消息缓冲区(Message Buffer)
stm32·单片机·嵌入式硬件·学习·freertos
圆奋奋7 天前
FreeRTOS学习(三)- 任务调度模块
学习·开源·freertos
危桥带雨1 个月前
Freertos——任务通知
stm32·单片机·嵌入式硬件·freertos
Funing71 个月前
FreeRTOS 学习复盘(四):事件组、软件定时器与任务通知机制
操作系统·freertos·嵌入式软件
离凌寒1 个月前
十一、在zephyr上使用rtc,rng,counter,crc,wdg等外设的演示
freertos
离凌寒1 个月前
八、在zephyr配置旋转编码器+timer配置演示
freertos