FreeRTOS 创建任务有动态和静态两种方式。动态创建时,任务控制块 TCB 和任务栈由 FreeRTOS 从堆中申请;静态创建时,这两块内存由用户提前定义,再交给 FreeRTOS 使用。
静态创建的核心价值是确定性:任务创建时不再依赖堆分配,也就避免了运行期因为堆不足导致创建失败的问题。它适合对内存位置、内存占用和可靠性要求更明确的场景。
一、静态创建任务 API
使用静态创建任务,需要在 FreeRTOSConfig.h 中打开:
#define configSUPPORT_STATIC_ALLOCATION 1
静态创建任务函数:
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer
);
参数重点如下:
| 参数 | 作用 |
|---|---|
pxTaskCode | 任务入口函数 |
pcName | 任务名称 |
ulStackDepth | 任务栈大小,单位是字,不是字节 |
pvParameters | 传给任务入口函数的参数 |
uxPriority | 任务优先级 |
puxStackBuffer | 用户提供的任务栈数组 |
pxTaskBuffer | 用户提供的任务控制块 |
和 xTaskCreate() 不同,xTaskCreateStatic() 返回值就是任务句柄。创建成功返回任务句柄,失败返回 NULL。
最小调用形式如下:
static StaticTask_t task1_tcb;
static StackType_t task1_stack[128];
task1_handle = xTaskCreateStatic(
task1,
"task1",
128,
NULL,
2,
task1_stack,
&task1_tcb
);
这段代码的重点是:task1_tcb 和 task1_stack 都由用户提供,而不是 FreeRTOS 从堆中申请。
二、静态创建的使用流程
静态创建可以按五步理解:
- 打开
configSUPPORT_STATIC_ALLOCATION。 - 为系统空闲任务提供 TCB 和任务栈。
- 如果启用了软件定时器,也要为定时器任务提供 TCB 和任务栈。
- 为用户任务定义 TCB、任务栈和入口函数。
- 调用
xTaskCreateStatic()创建任务。
静态创建比动态创建多出来的复杂度,主要就在“用户自己准备内存”这一步。
三、为什么要提供空闲任务内存
FreeRTOS 调度器启动后,系统至少会创建一个空闲任务。动态分配时,这个任务的 TCB 和栈可以由内核从堆中申请;静态分配时,这些内存也必须由用户提供。
因此需要实现:
void vApplicationGetIdleTaskMemory(
StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
configSTACK_DEPTH_TYPE *pulIdleTaskStackSize
);
如果工程启用了软件定时器:
#define configUSE_TIMERS 1
还需要实现:
void vApplicationGetTimerTaskMemory(
StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
configSTACK_DEPTH_TYPE *pulTimerTaskStackSize
);
这两个接口的作用不是创建任务逻辑,而是告诉 FreeRTOS:空闲任务和定时器任务可以使用哪一块 TCB、哪一块栈。
四、任务删除 API
静态创建的任务,删除时仍然使用同一个函数:
void vTaskDelete(TaskHandle_t xTaskToDelete);
使用前需要打开:
#define INCLUDE_vTaskDelete 1
示例:
vTaskDelete(task1_handle); /* 删除指定任务 */
vTaskDelete(NULL); /* 删除当前任务 */
删除任务后,FreeRTOS 会把该任务从就绪、阻塞、挂起、事件等列表中移除,使它不再参与调度。
不过,静态任务的 TCB 和任务栈是用户提供的静态内存,FreeRTOS 不会把它们归还给堆。任务删除后,这些数组和结构体仍然存在,后续如果需要重新创建任务,可以继续复用。
五、静态创建内部做了什么
xTaskCreateStatic() 内部大致流程如下:
- 获取用户传入的 TCB 地址。
- 获取用户传入的任务栈地址。
- 标记该任务使用静态分配方式。
- 初始化任务控制块 TCB。
- 初始化任务栈,为上下文切换准备现场。
- 将任务加入对应优先级的就绪列表。
- 返回任务句柄。
可以看到,静态创建和动态创建后半段是一样的,都会初始化 TCB、初始化任务栈、加入就绪列表。真正的区别在前半段:动态创建由 FreeRTOS 申请内存,静态创建由用户提供内存。
六、实验设计思路
与动态创建任务的实验现象一致。
本实验使用野火指南者开发板,主控芯片为 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_2
七、静态创建的优点
静态创建主要有三个优点:
- 不依赖运行期堆分配,任务创建更确定。
- 内存位置可控,可以把任务栈放到指定 RAM 区域。
- 内存占用更容易在编译期看清楚,适合可靠性要求高的工程。
它的代价也很明确:代码会更啰嗦,需要用户自己维护每个任务的 TCB 和任务栈。
八、常见注意点
StaticTask_t和StackType_t[]必须具有静态生命周期,不能定义成普通局部变量。- 栈大小单位是字,不是字节。
- 创建任务后要判断返回句柄是否为
NULL。 - 删除静态任务不会释放用户提供的 TCB 和栈数组。
- 任务内部自己申请的资源,删除前仍然要主动释放。
- 如果启用了软件定时器,不要忘记实现定时器任务内存接口。
九、动态创建和静态创建的区别
| 对比项 | 动态创建 | 静态创建 |
|---|---|---|
| 创建函数 | xTaskCreate() | xTaskCreateStatic() |
| TCB 来源 | FreeRTOS 从堆中申请 | 用户提供 StaticTask_t |
| 任务栈来源 | FreeRTOS 从堆中申请 | 用户提供 StackType_t[] |
| 创建失败原因 | 可能因为堆不足失败 | 不依赖堆,确定性更强 |
| 内存位置 | 由堆管理决定 | 用户可控 |
| 使用复杂度 | 更简单 | 更繁琐 |
| 常见场景 | 普通应用 | 内存确定性要求高的应用 |
十、小结
静态创建任务的本质,是把任务内存的分配权从 FreeRTOS 手里拿回来,由用户提前准备好 TCB 和任务栈。
记住四个点:
xTaskCreateStatic()用于静态创建任务。- 静态任务的 TCB 和任务栈必须由用户提供。
vTaskDelete()也能删除静态任务,但不会释放用户提供的静态内存。- 静态创建更确定,动态创建更简单。








