FreeRTOS基础篇(三)——任务管理
FreeRTOS基础篇(三)——任务管理
前言:
本文将系统性地讲解 FreeRTOS 中任务的创建、运行、状态转换以及阻塞与唤醒机制,帮助读者理解如何利用任务实现复杂的实时应用逻辑。通过实际代码示例,您将掌握任务的基本使用方式,并为后续学习队列、信号量、事件组等高级功能打下坚实基础。让我们从“任务”出发,逐步揭开 FreeRTOS 多任务并发的神秘面纱。
1 介绍
FreeRTOS的 任务管理 是其核心功能之一,具有轻量、高效、可移植性强等特点。主要具有以下特点。
- 多任务并发执行
FreeRTOS支持多任务并法执行(通过时间片轮转和优先级抢占实现),每个任务都是一个独立的函数,拥有自己的 栈空间 和 上下文 环境。值得解释下这里所说的栈空间是什么,它包含函数内部的局部变量、函数调用时的参数信息、函数调用后的返回地址,而上下文是指程序计数器(PC指针)、堆栈指针(SP)、通用寄存器(R0, R1, …)。正是因为每个任务都拥有这两样私有的东西,FreeRTOS才能实现多个任务相互独立、互不干扰地并发执行,从而构建出复杂可靠的嵌入式实时系统。
FreeRTOS的任务是以函数形式编写的,任务入口的接口形式为 void task_function(void *parameter) ,多个任务共享CPU,由FreeRTOS的内核调度器统一管理调度。多个相同优先级的任务执行时,FreeRTOS采用时间片轮转(Round-Robin)机制实现,每个人轮流执行一个时间片(由系统节拍 tick 决定,默认通常为1ms),公平执行优先级相同的任务。
- 基于优先级的任务调度
FreeRTOS的每个任务都有一个 优先级 ,从0到configMAX_PRIORITIES - 1,数值越大优先级越高。高优先级的任务一旦就绪,会立刻抢占当前正在执行的优先级比自己低的任务,因此他是一种实时性的操作系统(软实时)。configMAX_PRIORITIES最大值为256(为8位无符数),因此优先级最多有256级,范围为0~255。由于FreeRTOS的每个任务都有独立的栈空间和上下文存储,因此最大任务数量受限于内存。
- 任务状态
FreeRTOS的任务有五种状态。
| 状态 | 说明 |
|---|---|
| Running | 当前正在运行的任务 |
| Ready | 已准备好运行,等待被调度 |
| Blocked | 等待事件(如延时、队列、信号量) |
| Suspended | 被显式挂起(暂停执行) |
| Deleted | 已删除(仅在启用任务删除功能时存在) |
调度器会根据状态切换任务,实现高效的资源利用。

状态特性对比。
| 状态 | 参与调度 | 占用CPU | 资源占用 | 唤醒方式 |
|---|---|---|---|---|
| 运行 | 是 | 是 | CPU资源 | - |
| 就绪 | 是 | 否 | 内存资源 | 自动调度 |
| 阻塞 | 否 | 否 | 内存资源 | 事件/时间触发 |
| 挂起 | 否 | 否 | 内存资源 | 手动恢复 |
| 删除 | 否 | 否 | 否 | 不可恢复 |
2 任务运用
2.1 创建任务
创建任务的接口为 xTaskCreate ,有以下几个参数。
1 | xTaskCreate( |
其他参数都好理解,重点介绍下任务句柄,可以理解为任务的“遥控器”,任务句柄是一个指向任务控制块(TCB)的指针,拿到任务句柄则可通过句柄管理和操作该任务。比如可以通过任务句柄删除任务、修改任务优先级、挂起任务、恢复任务、任务通知(实现高效的IPC)。
执行 vTaskStartScheduler 函数后FreeRTOS开始接管任务的调度,正常情况下该函数不会返回,如果程序往后执行,说明调度器运行出现故障,通常是内存不足。以下是一个创建任务的Demo。
1 |
|
vTaskDelay 用于任务延时和调度,使当前任务进入阻塞状态,参数指定了阻塞的时间长度,让出 CPU 给其他就绪态任务。注意,任务是阻塞,CPU并不会阻塞而是去执行其他任务,等到阻塞时间到了之后。FreeRTOS 使用节拍(tick)作为时间单位,通常 1 tick = 1ms(可配置)。
2.2 阻塞任务
阻塞任务的方式有多种方式,当一个任务进入 Blocked(阻塞)状态 时,它暂时不参与调度,直到某个条件满足(如延时结束、收到信号量、队列有数据等)。这能有效释放 CPU 资源给其他任务。
- 延迟阻塞
通过让任务自身调用 vTaskDelay 函数,实现该任务暂停指定时间。
1 | void vTask1(void *pvParameters) |
注意,阻塞时间是从调度 vTaskDelay 函数开始计算的,所以调度的周期为 Task执行的时间 + 阻塞时间 ,如果当成周期任务使用,则会存在累积误差,当对周期精度要求不高时也可这样使用。
- 绝对时间阻塞
通过让任务自身调用 vTaskDelayUntil 函数,实现该任务精确的周期调度。使任务以一个固定的周期执行。它指定一个绝对时间,确保任务以固定的频率执行。第一个参数指向一个变量,该变量保存任务最后一次解除阻塞的时间。函数会根据这个时间和指定的周期来计算下一次唤醒时间,从而保持固定的执行间隔。
1 | void vTaskPeriodicExample( void *pvParameters ) |
| 特性 | vTaskDelay | vTaskDelayUntil |
|---|---|---|
| 延迟方式 | 相对延迟,从调用时刻开始算 | 绝对延迟,固定周期 |
| 执行周期 | 不固定,受任务执行时间影响 | 固定,不受任务执行时间影响 |
| 参数 | 一个参数:延迟的tick数 | 两个参数:上次唤醒时间指针和周期 |
| 适用场景 | 不需要精确周期的延迟 | 需要精确周期执行的任务 |
- 等待队列(Queue)数据阻塞
如果任务从空队列读取数据,默认会自动阻塞,直到有数据或超时(第三个参数是最大等待时间,时间单位是Tick数),如果接收队列时已经有数据,则任务不会阻塞而是继续执行(通常会处理接收到的数据)。
函数原型为。
1 | BaseType_t xQueueReceive( QueueHandle_t xQueue, |
以下为示例。
1 | QueueHandle_t xQueue; |
其他任务可以通过发送接口 xQueueSend ,通过同一个队列句柄给接收任务发送数据,函数原型为。
1 | BaseType_t xQueueSend( QueueHandle_t xQueue, |
示例如下。
1 | // 发送数据到队列,如果队列满则等待最多100个tick |
第一个参数为队列,第二个参数为要传入队列的数据,第三个参数为最大的等待时间。第三个参数是考虑到往队列写入数据时,如果队列满了,最大的等待时间,如果队列满并且还为超过最大阻塞时间,则该任务会阻塞住,指导队列非满写入数据或者超过最大阻塞时间。当最大阻塞时间设为宏 portMAX_DELAY 时(需要配置configUSE_PORT_OPTIMISED_TASK_SELECTION为0,并且FreeRTOSConfig.h中必须定义configUSE_MAX_DELAY为1),表示阻塞时间为无限等待,这个宏的值被定义为全F。
- 等待信号量阻塞
信号量也可用于控制任务同步。 xSemaphoreTake 接口会阻塞等待信号量,第一个参数时信号量句柄,第二个参数与队列的方式类似,是最大等待时间(时间单位是Tick数),可以设置为 portMAX_DELAY 表示无限等待。
1 | SemaphoreHandle_t xBinarySem; |
在其他任务中释放信号量。
1 | // 另一个任务或中断中释放信号量 |
- 手动挂起任务
通过接口 vTaskSuspend 强制让任务进入 Suspended 状态(不是 Blocked),需显式恢复。
Suspended 和 Blocked 的区别:
- Blocked:由内核管理,条件满足后自动恢复。上面提到的任务阻塞方式都是Blocked。
- Suspended:必须手动调用
vTaskResume()才能继续
1 | vTaskSuspend(xTask1Handle); // 挂起任务1(永久阻塞) |
这里的 xTask1Handle 是任务创建时的任务句柄。





