FreeRTOS 中创建任务,本质上就是创建一个任务控制块 TCB 和一块任务栈,然后把这个任务加入就绪列表,交给调度器运行。
动态创建任务时,TCB 和任务栈都由 FreeRTOS 从它管理的堆中申请。对使用者来说,这种方式最简单,也最常见;只要堆空间足够,调用 xTaskCreate() 就可以完成任务创建。
一、动态创建任务 API
使用动态创建任务,需要在 FreeRTOSConfig.h 中打开:
#define configSUPPORT_DYNAMIC_ALLOCATION 1
动态创建任务函数:
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask
);
参数重点如下:
| 参数 | 作用 |
|---|---|
pxTaskCode | 任务入口函数 |
pcName | 任务名称,方便调试 |
usStackDepth | 任务栈大小,单位是字,不是字节 |
pvParameters | 传给任务入口函数的参数 |
uxPriority | 任务优先级,数值越大优先级越高 |
pxCreatedTask | 任务句柄,用于后续删除、挂起、恢复任务 |
返回值:
| 返回值 | 含义 |
|---|---|
pdPASS | 创建成功 |
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY | 创建失败,通常是堆内存不足 |
最小调用形式如下:
xTaskCreate(task1, "task1", 128, NULL, 2, &task1_handle);
任务创建成功后,会立即进入就绪态。至于什么时候真正运行,要看当前调度器状态以及任务优先级。
二、动态创建的使用流程
动态创建任务的使用过程可以压缩成三步:
- 打开
configSUPPORT_DYNAMIC_ALLOCATION。 - 编写任务入口函数。
- 调用
xTaskCreate()创建任务。
任务入口函数一般写成死循环:
void task1(void *pvParameters)
{
while (1)
{
/* 任务逻辑 */
vTaskDelay(pdMS_TO_TICKS(500));
}
}
如果任务函数执行完就直接返回,这在 FreeRTOS 中通常不是一个好习惯。任务要结束时,应主动调用 vTaskDelete(NULL) 删除自己。
三、任务删除 API
使用任务删除函数,需要打开:
#define INCLUDE_vTaskDelete 1
函数原型:
void vTaskDelete(TaskHandle_t xTaskToDelete);
参数含义很直接:
| 参数 | 含义 |
|---|---|
| 具体任务句柄 | 删除指定任务 |
NULL | 删除当前正在运行的任务 |
示例:
vTaskDelete(task1_handle); /* 删除 task1 */
vTaskDelete(NULL); /* 删除当前任务 */
任务被删除后,会从就绪、阻塞、挂起、事件等列表中移除。若删除的是当前任务,FreeRTOS 会先把它放入等待删除列表,之后由空闲任务负责回收系统分配的 TCB 和任务栈。
这里有一个重要边界:空闲任务只负责释放 FreeRTOS 为任务分配的内存。如果任务运行过程中自己申请了内存、打开了外设资源或持有互斥量,这些资源必须在删除任务前由用户主动处理。
四、动态创建内部做了什么
从内核角度看,xTaskCreate() 大致做了这些事:
- 从 FreeRTOS 堆中申请任务栈。
- 从 FreeRTOS 堆中申请任务控制块 TCB。
- 初始化 TCB 中的任务名、优先级、栈地址等成员。
- 初始化任务栈,为后续上下文切换准备寄存器现场。
- 把任务加入对应优先级的就绪列表。
- 如果调度器已经运行,并且新任务优先级更高,则触发一次任务切换。
也就是说,xTaskCreate() 不只是“注册一个函数”,而是把一个任务运行所需的调度信息、栈空间和上下文环境都准备好。
五、TCB 是什么
TCB,全称 Task Control Block,即任务控制块。每个任务都有一个 TCB,可以理解为任务在内核里的身份档案。
常见关键成员包括:
| 成员 | 作用 |
|---|---|
pxTopOfStack | 当前任务栈顶,任务切换时保存和恢复上下文 |
xStateListItem | 任务状态列表项,用于挂到就绪、阻塞、挂起等列表 |
xEventListItem | 事件列表项,用于事件等待机制 |
uxPriority | 任务优先级 |
pxStack | 任务栈起始地址 |
pcTaskName | 任务名称 |
任务调度时,FreeRTOS 不是直接“调度函数”,而是通过 TCB 找到任务栈、优先级、状态列表节点等信息,再完成上下文切换。
六、实验设计思路
本实验使用野火指南者开发板,主控芯片为 STM32F103VET6,平台使用STM32CubeIDE,故不是FreeRTOS 原生接口,而是由CMSIS-RTOS v2封装的FreeRTOS API 。
实验说明:板载 LED 是一个 RGB 灯,不适合用多个独立 LED 分别表示多个任务,因此这里用不同颜色来区分任务现象:不同任务按照不同周期控制 RGB 灯显示不同颜色。
这个实验只围绕两个问题展开:
- 观察任务是如何被动态创建出来的。
- 观察指定任务被删除后,它对应的现象是否停止。
任务分工如下:
| 任务 | 作用 |
|---|---|
start_task | 周期打印串口信息,作为系统仍在运行的心跳 |
task1 | 每隔 1s 控制 RGB LED 显示蓝色 |
task2 | 每隔 1.2s 控制 RGB LED 显示绿色,作为被删除的目标任务 |
按键触发前,start_task 会持续打印串口信息,说明调度器仍在正常运行;task1 和 task2 也会按照各自周期运行。由于两个任务的周期分别是 1s 和 1.2s,蓝色和绿色的切换节奏会不断错开,过一段时间后又重新接近重合。
按键触发后,中断服务函数不直接删除任务,而是只设置一个 volatile 标志位。随后在任务上下文中检测这个标志位,并删除 task2。删除成功后,绿色不再出现,RGB LED 只保留 task1 对应的蓝色变化;同时串口心跳仍然继续打印,说明删除的是指定任务,而不是整个系统停止运行。
这个实验的观察重点不是 RGB 灯本身,而是通过 RGB 灯和串口现象确认两件事:任务删除后,对应任务的行为会停止;其他未被删除的任务仍然可以继续被调度。
GitHub:Hui404/FreeRTOS_1
七、常见注意点
usStackDepth的单位是字,不是字节。- 动态创建可能失败,要检查
xTaskCreate()的返回值。 - 删除任务后,任务句柄变量不会自动变成
NULL,建议手动清空。 - 被删除任务自己申请的资源,必须在删除前释放。
- 动态创建依赖 FreeRTOS 堆,需要合理配置
configTOTAL_HEAP_SIZE。 - 如果创建多个任务时不希望中途被调度打断,可以用短临界区保护创建过程。
八、小结
动态创建任务适合大多数普通应用场景。它的优势是简单灵活,内存分配由 FreeRTOS 负责;代价是创建过程依赖堆空间,存在申请失败的可能。
记住三个点就够了:
xTaskCreate()动态创建任务,成功后任务进入就绪态。vTaskDelete()删除任务,传入NULL表示删除自身。- FreeRTOS 只回收它分配的任务内存,用户自己申请的资源要自己释放。








