外观
1.5 任务与任务生命周期
这节要搞懂:什么是任务,它有哪几种状态。 需要基础:1.4 ⭐ 本章重点,值得多看两遍
一、什么是任务
任务(Task)是 FreeRTOS 中最小的调度单位。每个任务都是一段独立运行的程序代码,拥有自己的栈空间和优先级,由内核决定何时让它运行。
打个比方:一个任务就像餐厅里的一名员工 —— 有自己的岗位(代码)、自己的工位(栈)、自己的重要程度(优先级)。经理(调度器)决定什么时候让谁干活。
二、任务在代码里长什么样
一个任务 = 一个自带 while(1) 的函数:
c
void task_led(void *pvParameters)
{
while (1) { // 注意:永远不能结束
led_on(); // 点亮 LED
vTaskDelay(pdMS_TO_TICKS(500)); // 睡 500 毫秒
led_off(); // 熄灭 LED
vTaskDelay(pdMS_TO_TICKS(500));
}
}你可以写好几个这样的函数,它们看起来都在"同时"运行。
任务函数不能 return,也不能自然结束例如break。 它必须是个死循环。
真要结束就调用 vTaskDelete(NULL) 主动删掉自己。任务函数跑到结尾"掉出去",程序会直接崩溃。 :::
三、任务的三个组成部分
| 组成 | 是什么 | 说明 |
|---|---|---|
| 函数体 | 你写的那段代码 | 必须是死循环 |
| 栈(Stack) | 一块专属的内存 | 存局部变量和切换时的临时数据 |
| TCB(任务控制块) | 内核用的一个结构体 | 每创建一个任务,内核就会分配一个 TCB 来记录其全部信息:它的名字、优先级、当前状态 |
这三样都是创建任务时自动分配的,你只需要告诉内核"栈要多大"。
四、创建一个任务
c
xTaskCreate(
task_led, // 1. 任务函数
"led", // 2. 任务名字,调试时用来识别
2048, // 3. 栈大小(字节)
NULL, // 4. 传给任务的参数,没有就填 NULL
5, // 5. 优先级,数字越大越优先
NULL // 6. 任务句柄,不需要就填 NULL
);六个参数,新手只要关心第 3 个和第 5 个:
| 参数 | 怎么填 |
|---|---|
| 栈大小 | 先填 2048,不够再加。太小会崩,太大浪费内存 |
| 优先级 | 一般任务填 5,需要快速响应的填大一点(比如按键填 6) |
一个跨平台的坑
在 ESP32-S3(ESP-IDF) 里,栈大小的单位是字节,填 2048 就是 2048 字节。
在原版 FreeRTOS 里单位是"字"(4 字节),填 2048 相当于 8192 字节。
本课程全部按字节算,换平台时记得查一下。
五、什么是任务生命周期
任务生命周期指的是任务从被创建到被删除的整个过程中,所经历的各种运行状态及其相互转换关系。
FreeRTOS 的任务有四种状态:
顺着箭头读:任务创建出来先进 Ready(排队);被调度器选中就变 Running(干活);干活时如果调了 vTaskDelay 或去等队列,就进 Blocked(让出 CPU 去等),条件满足后自动回到 Ready 重新排队。
Suspended 是画虚线的那个 —— 只能被别人 vTaskSuspend 按停,也只能被别人 vTaskResume 叫醒,它自己出不来。
六、用排队理解四种状态
| 状态 | 相当于 | 占着 CPU 吗 | 会自己恢复吗 |
|---|---|---|---|
| Running 运行中 | 正在里面 | ✅ 是 | — |
| Ready 就绪 | 门口排队,随时能进 | ❌ 否 | ✅ 轮到就进 |
| Blocked 阻塞 | 去旁边坐着等叫号 | ❌ 否 | ✅ 条件满足自动醒 |
| Suspended 挂起 | 被人拉走了 | ❌ 否 | ❌ 不叫不回来 |
七、阻塞和挂起的区别
新手最容易混,记住一句话:
- 阻塞:任务自己说"我等一下",时间到了或条件满足了会自动醒
- 挂起:别人把它按停了,只能靠别人叫醒(调用
vTaskResume)
99% 的情况你用的都是阻塞:
c
vTaskDelay(pdMS_TO_TICKS(500)); // 我睡 500ms,到点自己醒 → 阻塞八、为什么 vTaskDelay 这么重要
c
// ❌ 错误:傻等,CPU 被你一个人占着,别的任务都别想跑
for (int i = 0; i < 1000000; i++) { }
// ✅ 正确:睡过去,CPU 让给别人
vTaskDelay(pdMS_TO_TICKS(500));记住这条铁律
每个任务的循环里,必须有一个会"睡"的语句(vTaskDelay,或者等待队列、等待信号量)。
否则这个任务会一直霸占 CPU,其他任务全部饿死,系统还会报错: Task watchdog got triggered
本节要点
- 任务 = FreeRTOS 最小的调度单位,由代码 + 栈 + TCB 组成
- 任务函数是死循环,不能返回
- 创建时先关心两个参数:栈填 2048、优先级填 5
- 四种状态:运行 / 就绪 / 阻塞 / 挂起
- 阻塞会自己醒,挂起不会
- 每个任务循环里必须有会"睡"的语句
动手试试
- 创建两个任务,一个每 500ms 打印 "A",一个每 1000ms 打印 "B",观察输出怎么交错。
- 把其中一个任务的
vTaskDelay删掉,看看会发生什么(会报错,就是要你看到)。 - 把栈大小从 2048 改成 256,看看程序还能不能跑。