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 任务的运行与周期控制。

相关推荐
La Pulga2 天前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga3 天前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga5 天前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
La Pulga5 天前
【FreeRTOS源码学习笔记】任务创建
笔记·stm32·mcu·学习·嵌入式·freertos·实时操作系统
天吾cc10 天前
FreeRTOS 事件标志组
freertos
编码追梦人11 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
天吾cc15 天前
FreeRTOS 队列集
freertos
硅基手札15 天前
操作系统设计思想系列 06 · FreeRTOS 深度 — 极简可移植范本
架构·操作系统·freertos·实时操作系统·嵌入式开发·设计思想
天吾cc20 天前
FreeRTOS 列表和列表项
freertos
qq_4017004120 天前
RTOS址与值 void*任务传参的坑 走void*万能转的天坑
freertos