FreeRTOS任务创建与删除
TaskHandle_t
TaskHandle_t是 FreeRTOS 中用于标识任务的句柄类型,它是任务管理和任务间通信的核心。
在 FreeRTOS 中,TaskHandle_t定义为指向任务控制块(Task Control Block, TCB)的指针:
typedef struct tskTaskControlBlock * TaskHandle_t;
TaskHandle_t指向的任务控制块包含任务的所有信息
是任务控制块的指针,唯一标识一个任务
用于任务管理(挂起、恢复、删除、优先级控制等)
是任务间通信(特别是任务通知)的关键
可以获取任务状态和运行时信息
支持高级功能如任务通知数组和任务标签
xTaskCreate
xTaskCreate()是 FreeRTOS 中用于创建一项新任务并将其添加到准备运行的任务列表中的任务创建函数,它负责分配任务所需的内存资源并初始化任务控制块(TCB)。
注意:configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或处于未定义状态(默认为 1), 才可使用此 RTOS API 函数。
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称字符串
const configSTACK_DEPTH_TYPE usStackDepth, // 任务栈深度(以字为单位)
void *pvParameters, // 传递给任务函数的参数
UBaseType_t uxPriority, // 任务优先级
TaskHandle_t *pxCreatedTask // 返回的任务句柄指针
);
参数详解
1. pvTaskCode - 任务函数指针
-
类型:
TaskFunction_t(定义为void (*)(void *)) -
作用:指向任务入口函数的指针
-
要求:
-
函数必须为无限循环结构
-
函数不能返回或退出但是,任务可以 自行删除
-
函数原型:
void TaskFunction(void *pvParameters)
-
2. pcName - 任务名称
-
类型:
const char * const -
作用:描述性任务名称,主要用于调试目的也可用于 获取任务句柄
-
限制:
-
最大长度由
configMAX_TASK_NAME_LEN定义
-
-
建议:使用有意义的名称,如 "LED_Task"、"KEY_Task"
3. usStackDepth - 栈深度
-
类型:
configSTACK_DEPTH_TYPE(通常为uint16_t或uint32_t) -
单位:字(word),32位系统为4字节
-
计算建议:
-
最小栈:
configMINIMAL_STACK_SIZE -
典型栈大小:
-
简单任务:128-256字
-
中等任务:256-512字
-
复杂任务:512-1024字
-
-
-
调试工具:
-
uxTaskGetStackHighWaterMark()检查栈使用情况 (INCLUDE_uxTaskGetStackHighWaterMark)需在 FreeRTOSConfig.h 中设置为 1
-
4. pvParameters - 任务参数
-
类型:
void * -
作用:传递给任务函数的参数
-
注意:如果 pvParameters 为某变量的地址, 则在创建的任务执行时,该变量必须仍然存在, 因此,不能传递堆栈变量的地址。
5. uxPriority - 任务优先级
6. pxCreatedTask - 任务句柄指针
-
范围:0(最低)到
configMAX_PRIORITIES-1(最高) -
建议:
-
空闲任务优先级最低:0
-
普通任务:1-3
-
高优先级任务:4-(configMAX_PRIORITIES-2)
-
定时器服务任务:通常最高优先级
-
-
空闲任务的专属优先级:
tskIDLE_PRIORITY(0)
FreeRTOS 在启动调度器(vTaskStartScheduler())时,会自动创建一个空闲任务,该任务的优先级固定为tskIDLE_PRIORITY(0)。
空闲任务的主要功能包括:
1.当系统中没有其他更高优先级(≥1)的任务就绪时,空闲任务运行,避免 CPU “空转”;
若启用动态内存分配(configSUPPORT_DYNAMIC_ALLOCATION = 1),空闲任务会负责释放被删除任务的内存(通过内部调用vPortFree());
2.执行用户定义的 “空闲钩子函数”(vApplicationIdleHook()),若configUSE_IDLE_HOOK = 1(可用于低功耗处理、系统监控等)。 -
优先级特性与调度逻辑
FreeRTOS 的优先级遵循 “数值越小,优先级越低” 的规则tskIDLE_PRIORITY(0)是最低优先级,仅当所有更高优先级(≥1)的任务都处于阻塞、挂起或完成状态时,空闲任务才会被调度运行;
若用户创建了优先级为 0 的任务(与空闲任务同优先级),则这些任务会与空闲任务按时间片(若启用configUSE_TIME_SLICING)共享 CPU(轮流运行)。
优先级使用注意事项:
不建议用户任务使用 0 优先级:若用户任务优先级为 0,会与空闲任务竞争 CPU 时间,可能导致空闲任务无法及时释放内存(被删除任务的内存)或执行钩子函数,影响系统稳定性;
仅在特殊场景下使用:例如需要 “最低优先级后台任务” 且不影响空闲任务核心功能时(需确保内存释放逻辑可靠)。 -
类型:
TaskHandle_t * -
作用:接收创建的任务句柄
-
用途:
-
后续任务管理(挂起、恢复、删除等)
-
任务间通信
-
-
可为NULL:如果不需要引用任务,可设为NULL
返回值
-
pdPASS:任务创建成功
-
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:任务创建失败(堆内存不足)
内存管理
xTaskCreate()使用 FreeRTOS 堆分配:
-
任务控制块(TCB)
-
任务栈空间
-
任务名称字符串
所需任务总内存 ≈ sizeof(TCB) + (usStackDepth * sizeof(StackType_t)) + strlen(pcName)
使用示例
//动态任务创建
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t task1_handle;//任务1句柄
TaskHandle_t task2_handle;//任务2句柄
void task1(void *pvParam)//任务1
{
const TickType_t xDelay = pdMS_TO_TICKS(1000); // 1秒延时
while(1) {
GPIOC->ODR ^= GPIO_ODR_ODR13; // 取反PC13
vTaskDelay(xDelay); // 使用RTOS延时函数
}
}
void task2(void *pvParam)//任务2
{
const TickType_t xDelay = pdMS_TO_TICKS(500); // 0.5秒延时
while(1) {
GPIOA->ODR ^= GPIO_ODR_ODR5; // 取反PA5
vTaskDelay(xDelay); // 使用RTOS延时函数
}
}
int main(void)
{
// 1. 启用时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPAEN;
// 2. 配置PC13 (LED) - 高8位引脚
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13);
GPIOC->CRH |= GPIO_CRH_MODE13_1 | GPIO_CRH_MODE13_0; // 推挽输出,最大速度50MHz
// 3. 配置PA5 - 低8位引脚,使用CRL寄存器
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5);
GPIOA->CRL |= GPIO_CRL_MODE5_1 | GPIO_CRL_MODE5_0; // 推挽输出,最大速度50MHz
// 4. 创建任务 - 使用不同优先级
xTaskCreate(task1, "task1", 128, NULL, 2, &task1_handle);
xTaskCreate(task2, "task2", 128, NULL, 3, &task2_handle);
// 5. 启动调度器
vTaskStartScheduler();
// 6. 如果调度器启动失败,将停留在此循环
while(1) {
// 此处代码不会执行,除非调度器启动失败
}
}
常见问题
任务创建失败
-
原因:堆空间不足
-
解决方案:
-
增加总堆大小
configTOTAL_HEAP_SIZE -
优化栈大小
-
使用静态创建
-
栈溢出
-
检测:启用
configCHECK_FOR_STACK_OVERFLOW -
现象:系统不稳定、随机复位
-
解决:增加栈大小或优化代码
优先级反转
-
场景:高优先级任务等待低优先级任务释放资源
-
解决方案:使用互斥锁的优先级继承机制
总结
xTaskCreate()是FreeRTOS多任务系统的核心函数,使用需要注意:
-
合理设置栈大小
-
精心规划任务优先级
-
有效管理任务参数
-
妥善处理任务句柄
-
实施健壮的错误处理
通过合理使用 xTaskCreate(),可以构建高效、可靠且易于维护的嵌入式实时系统程序。
xTaskCreateStatic
xTaskCreateStatic()是 FreeRTOS 中用于创建任务的静态内存分配版本,它允许开发者完全控制任务内存分配,避免使用动态内存分配。
注意:configSUPPORT_STATIC_ALLOCATION 必须在 FreeRTOSConfig.h中设置为 1,才可使用此 RTOS API 函数。
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 // 用户提供的任务控制块缓冲区
);
参数详解
1. pxTaskCode
-
任务入口函数
-
类型:
void (*)(void *) -
必须为无限循环结构
2. pcName
-
任务描述名称
-
用于调试和识别
-
最大长度由
configMAX_TASK_NAME_LEN定义
3. ulStackDepth
-
栈深度(以字为单位)
-
32位系统:1字 = 4字节
-
必须足够容纳任务最大栈使用
4. pvParameters
-
传递给任务函数的参数
-
可以是任意类型的数据指针
5. uxPriority
-
任务优先级 (0 到 configMAX_PRIORITIES-1)
-
数值越大优先级越高
6. puxStackBuffer
-
用户提供的栈缓冲区
-
类型:
StackType_t * -
大小必须至少为
ulStackDepth * sizeof(StackType_t) -
通常定义为数组:
static StackType_t xTaskStack[STACK_SIZE];
7. pxTaskBuffer
-
用户提供的任务控制块缓冲区
-
类型:
StaticTask_t * -
大小由
sizeof(StaticTask_t)决定 -
通常定义为:
static StaticTask_t xTaskTCB;
返回值
-
成功:任务句柄 (TaskHandle_t)
-
失败:NULL(通常因无效参数或缓冲区不足)
使用示例
//静态任务创建
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
// 任务句柄定义
TaskHandle_t task1_handle; // 控制PC13
TaskHandle_t task2_handle; // 控制PA5
TaskHandle_t task3_handle; // 控制PB1
TaskHandle_t task4_handle; // 控制PA7
// 1. 静态任务资源定义:为每个任务分配栈和TCB(必须全局或static)
// 任务栈(大小128,单位:StackType_t,通常为4字节)
static StackType_t task1_stack[128];
static StackType_t task2_stack[128];
static StackType_t task3_stack[128];
static StackType_t task4_stack[128];
// 任务控制块(TCB,FreeRTOS用于管理任务的结构体)
static StaticTask_t task1_tcb;
static StaticTask_t task2_tcb;
static StaticTask_t task3_tcb;
static StaticTask_t task4_tcb;
// 任务1:PC13闪烁(1秒周期)
void task1(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(1000);
while(1) {
GPIOC->ODR ^= GPIO_ODR_ODR13; // 取反PC13
vTaskDelay(xDelay);
}
}
// 任务2:PA5闪烁(0.5秒周期)
void task2(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(500);
while(1) {
GPIOA->ODR ^= GPIO_ODR_ODR5; // 取反PA5
vTaskDelay(xDelay);
}
}
// 任务3:PB1闪烁(0.2秒周期)
void task3(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(200);
while(1) {
GPIOB->ODR ^= GPIO_ODR_ODR1; // 取反PB1
vTaskDelay(xDelay);
}
}
// 任务4:PA7闪烁(0.3秒周期)
void task4(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(300);
while(1) {
GPIOA->ODR ^= GPIO_ODR_ODR7; // 取反PA7
vTaskDelay(xDelay);
}
}
// 为空闲任务分配静态栈和TCB
static StackType_t idle_task_stack[configMINIMAL_STACK_SIZE]; // 空闲任务栈(大小由configMINIMAL_STACK_SIZE定义)
static StaticTask_t idle_task_tcb; // 空闲任务TCB
/**
* @brief 静态内存模式下,FreeRTOS会调用此函数获取空闲任务的内存
* @param ppxIdleTaskTCBBuffer: 用于返回空闲任务TCB的地址
* @param ppxIdleTaskStackBuffer: 用于返回空闲任务栈的地址
* @param pulIdleTaskStackSize: 用于返回空闲任务栈的大小
*/
void vApplicationGetIdleTaskMemory(
StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize
)
{
*ppxIdleTaskTCBBuffer = &idle_task_tcb; // 传递空闲任务TCB的地址
*ppxIdleTaskStackBuffer = idle_task_stack; // 传递空闲任务栈的地址
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; // 传递栈大小(与定义的数组长度一致)
}
#if configUSE_TIMERS == 1
// 为定时器任务分配静态栈和TCB
static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; // 定时器任务栈(大小由configTIMER_TASK_STACK_DEPTH定义)
static StaticTask_t timer_task_tcb; // 定时器任务TCB
/**
* @brief 静态内存模式下,FreeRTOS会调用此函数获取定时器任务的内存
* @param ppxTimerTaskTCBBuffer: 用于返回定时器任务TCB的地址
* @param ppxTimerTaskStackBuffer: 用于返回定时器任务栈的地址
* @param pulTimerTaskStackSize: 用于返回定时器任务栈的大小
*/
void vApplicationGetTimerTaskMemory(
StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize
)
{
*ppxTimerTaskTCBBuffer = &timer_task_tcb; // 传递定时器任务TCB的地址
*ppxTimerTaskStackBuffer = timer_task_stack; // 传递定时器任务栈的地址
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; // 传递栈大小(与定义的数组长度一致)
}
#endif
int main(void)
{
// 1. 启用时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
// 2. 配置PC13
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13);
GPIOC->CRH |= GPIO_CRH_MODE13_1 | GPIO_CRH_MODE13_0; // 推挽输出,50MHz
// 3. 配置PA5
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5);
GPIOA->CRL |= GPIO_CRL_MODE5_1 | GPIO_CRL_MODE5_0; // 推挽输出,50MHz
// 4. 配置PB1
GPIOB->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1);
GPIOB->CRL |= GPIO_CRL_MODE1_1 | GPIO_CRL_MODE1_0; // 推挽输出,50MHz
// 5. 配置PA7
GPIOA->CRL &= ~(GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
GPIOA->CRL |= GPIO_CRL_MODE7_1 | GPIO_CRL_MODE7_0; // 推挽输出,50MHz
// 6. 静态创建任务(使用xTaskCreateStatic,需传入栈和TCB地址)
task1_handle = xTaskCreateStatic(
task1, // 任务函数
"task1", // 任务名(调试用)
128, // 栈大小(与栈数组长度一致)
NULL, // 任务参数(无)
2, // 优先级
task1_stack, // 静态栈地址
&task1_tcb // 静态TCB地址
);
task2_handle = xTaskCreateStatic(
task2,
"task2",
128,
NULL,
3,
task2_stack,
&task2_tcb
);
task3_handle = xTaskCreateStatic(
task3,
"task3",
128,
NULL,
1,
task3_stack,
&task3_tcb
);
task4_handle = xTaskCreateStatic(
task4,
"task4",
128,
NULL,
4,
task4_stack,
&task4_tcb
);
// 7. 启动调度器
vTaskStartScheduler();
// 8. 调度器启动失败时的循环
while(1) {
// 此处代码不会执行,除非调度器启动失败
}
}
当使用静态任务创建(configSUPPORT_STATIC_ALLOCATION = 1)时,FreeRTOS 要求必须手动提供空闲任务(Idle Task)的静态内存(栈和 TCB)
1. 确保FreeRTOSConfig.h中开启静态分配支持
首先检查FreeRTOSConfig.h是否有以下配置(如果没有则添加):
#define configSUPPORT_STATIC_ALLOCATION 1 // 启用静态内存分配
2. 实现空闲任务的内存分配回调函数
FreeRTOS 在静态模式下,会通过vApplicationGetIdleTaskMemory回调函数获取空闲任务的栈和 TCB(任务控制块)。需要在代码中添加以下代码:
// 为空闲任务分配静态栈和TCB
static StackType_t idle_task_stack[configMINIMAL_STACK_SIZE]; // 空闲任务栈(大小由configMINIMAL_STACK_SIZE定义)
static StaticTask_t idle_task_tcb; // 空闲任务TCB
/**
* @brief 静态内存模式下,FreeRTOS会调用此函数获取空闲任务的内存
* @param ppxIdleTaskTCBBuffer: 用于返回空闲任务TCB的地址
* @param ppxIdleTaskStackBuffer: 用于返回空闲任务栈的地址
* @param pulIdleTaskStackSize: 用于返回空闲任务栈的大小
*/
void vApplicationGetIdleTaskMemory(
StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize
)
{
*ppxIdleTaskTCBBuffer = &idle_task_tcb; // 传递空闲任务TCB的地址
*ppxIdleTaskStackBuffer = idle_task_stack; // 传递空闲任务栈的地址
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; // 传递栈大小(与定义的数组长度一致)
}
3. (可选)若启用了软件定时器,还需实现定时器任务的回调
如果你的FreeRTOSConfig.h中开启了软件定时器(configUSE_TIMERS = 1),还需要添加定时器任务的内存分配回调,否则会出现类似的vApplicationGetTimerTaskMemory未定义错误:
#if configUSE_TIMERS == 1
// 为定时器任务分配静态栈和TCB
static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; // 定时器任务栈(大小由configTIMER_TASK_STACK_DEPTH定义)
static StaticTask_t timer_task_tcb; // 定时器任务TCB
/**
* @brief 静态内存模式下,FreeRTOS会调用此函数获取定时器任务的内存
* @param ppxTimerTaskTCBBuffer: 用于返回定时器任务TCB的地址
* @param ppxTimerTaskStackBuffer: 用于返回定时器任务栈的地址
* @param pulTimerTaskStackSize: 用于返回定时器任务栈的大小
*/
void vApplicationGetTimerTaskMemory(
StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize
)
{
*ppxTimerTaskTCBBuffer = &timer_task_tcb; // 传递定时器任务TCB的地址
*ppxTimerTaskStackBuffer = timer_task_stack; // 传递定时器任务栈的地址
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; // 传递栈大小(与定义的数组长度一致)
}
#endif
原理说明:
- FreeRTOS 启动时会自动创建空闲任务(Idle Task)(优先级最低,用于系统空闲时运行)。
- 当使用动态内存分配(
configSUPPORT_STATIC_ALLOCATION = 0)时,空闲任务的内存由 FreeRTOS 从堆中分配,无需用户干预。 - 当使用静态内存分配(
configSUPPORT_STATIC_ALLOCATION = 1)时,FreeRTOS 不再使用堆,因此需要用户手动提供空闲任务(及定时器任务,若启用)的栈和 TCB 内存,通过上述回调函数告知内核。
与 xTaskCreate 的区别
|
特性 |
xTaskCreate |
xTaskCreateStatic |
|---|---|---|
|
内存分配 |
动态分配堆内存 |
静态分配用户提供内存 |
|
内存来源 |
FreeRTOS 堆 |
用户定义缓冲区 |
|
适用场景 |
通用应用 |
无动态内存系统、安全关键系统 |
|
内存管理 |
自动管理 |
完全手动管理 |
|
资源开销 |
有堆管理开销 |
无额外开销 |
|
确定性 |
分配时间不确定 |
分配时间确定 |
|
配置要求 |
需要配置堆大小 |
需要 configSUPPORT_STATIC_ALLOCATION=1 |
vTaskDelete
vTaskDelete()是 FreeRTOS 中用于删除任务的核心函数,它允许动态管理任务生命周期,释放任务占用的资源。INCLUDE_vTaskDelete 必须定义为 1,才可使用此函数。
从 RTOS 内核管理中移除任务。要删除的任务将从所有就绪、 阻塞、挂起和事件列表中移除。
注意:空闲任务负责释放由 RTOS 内核分配给已删除任务的 内存。因此,如果应用程序调用了
vTaskDelete(),请务必确保空闲任务获得足够的微控制器处理时间。任务代码分配的内存不会自动释放, 应在任务删除之前手动释放。
void vTaskDelete( TaskHandle_t xTask ); //要删除的任务句柄
特殊值:NULL会删除调用任务表示删除当前任务
功能特点
-
立即终止任务:
-
任务从所有状态列表(就绪、阻塞、挂起)中移除
-
不再参与调度
-
-
资源回收:
-
动态分配的任务:自动回收TCB和栈内存
-
静态分配的任务:标记资源可用(需手动回收)
-
-
安全删除:
-
可删除任何状态的任务(运行、就绪、阻塞、挂起)
-
可删除自身(
vTaskDelete(NULL))
-
-
内存回收时机:
-
动态内存:在空闲任务中回收
-
需要确保空闲任务能运行
-
使用场景
1. 删除其他任务
TaskHandle_t xSensorTaskHandle;
void vControlTask(void *pvParameters) {
// 创建传感器任务
xTaskCreate(vSensorTask, "Sensor", 256, NULL, 2, &xSensorTaskHandle);
// 运行一段时间后删除传感器任务
vTaskDelay(pdMS_TO_TICKS(10000));
vTaskDelete(xSensorTaskHandle);
xSensorTaskHandle = NULL; // 防止重复删除
}
2. 任务自删除
void vOneShotTask(void *pvParameters)
{
// 执行一次性操作
read_sensors();
process_data();
send_report();
// 任务完成后删除自身
vTaskDelete(NULL);
}
3. 错误处理中删除
void vCommunicationTask(void *pvParameters)
{
if(init_comms() != SUCCESS)
{
// 初始化失败,删除任务
vTaskDelete(NULL);
}
while(1)
{
// 正常通信处理
}
}
回收过程:
-
任务被标记为已删除
-
TCB和栈添加到待删除列表
-
空闲任务调用
prvDeleteTCB()回收内存
注意事项
1. 资源泄漏风险
任务删除前必须释放:
-
动态分配的内存
-
持有的信号量/互斥锁
-
打开的文件/外设
-
注册的回调函数
2. 删除运行中的任务
当任务被删除时:
-
当前执行点立即停止
-
后续代码不会执行
3. 空闲任务依赖
动态内存回收需要空闲任务运行:
-
确保空闲任务有执行的时间
-
避免长时间禁用中断
4. 任务通知处理
删除任务时:
| 操作场景 | 未处理的通知是否丢失 | 等待通知的任务行为 | 返回值 |
|---|---|---|---|
| 删除接收通知的任务(未处理通知在其 TCB) | 是(随 TCB 回收丢失) | 若任务正等待通知:强制解除阻塞 | pdFAIL |
| 删除发送通知的任务(接收方未处理通知) | 否(通知仍在接收方 TCB) | 接收方继续阻塞(直到超时或收到其他通知) | 超时返回 pdFALSE,收到通知返回 pdTRUE |
| 接收方等待通知时,主动发送通知 | 否(正常处理) | 解除阻塞,处理通知 | pdTRUE |
//批量删除任务
void vDeleteAllTasks(void) {
TaskHandle_t xHandle;
// 获取所有任务句柄
UBaseType_t uxCount = uxTaskGetNumberOfTasks();
TaskHandle_t *pxHandles = pvPortMalloc(uxCount * sizeof(TaskHandle_t));
if(pxHandles) {
uxTaskGetSystemState(pxHandles, uxCount);
// 删除除空闲任务外的所有任务
for(UBaseType_t ux = 0; ux < uxCount; ux++) {
if(strcmp(pcTaskGetName(pxHandles[ux]), "IDLE") != 0) {
vTaskDelete(pxHandles[ux]);
}
}
vPortFree(pxHandles);
}
}
常见问题解决
1. 删除后系统崩溃
-
原因:任务持有资源未释放
-
解决:实现删除钩子或清理协议
2. 内存未回收
-
原因:空闲任务未运行
-
解决:
// 删除后主动让出CPU vTaskDelete(xTask); taskYIELD();
3. 重复删除
-
现象:未定义行为
-
解决:
if(xHandle != NULL) { vTaskDelete(xHandle);// 删除任务 xHandle = NULL; // 句柄置空,避免后续重复删除 }
4. 删除关键任务
-
风险:删除调度器任务导致系统崩溃
在 FreeRTOS 中,“调度器任务” 通常指 内核依赖的核心系统任务—— 主要是 空闲任务(Idle Task) 和 软件定时器任务(Timer Task)。这两个任务是 FreeRTOS 调度器正常运行的 “基础设施”,绝对禁止手动删除,一旦删除会直接破坏系统调度机制,导致崩溃(如硬 fault、系统卡死、断言失败等)。
一、先明确:哪些是 “调度器依赖的核心任务”?
FreeRTOS 启动后会自动创建以下核心任务,用户无需手动创建,也不能删除:
| 核心任务 | 创建时机 | 核心作用 |
|---|---|---|
| 空闲任务(Idle Task) | 调用 vTaskStartScheduler() 时自动创建 |
1. 当所有用户任务都处于阻塞态时,运行空闲任务(避免系统 “无任务可调度”);2. 回收动态任务删除后的堆内存(如 vTaskDelete() 后,由空闲任务释放栈 / TCB);3. 执行空闲任务钩子函数(vApplicationIdleHook())。 |
| 定时器任务(Timer Task) | 启用 configUSE_TIMERS = 1 时自动创建 |
管理软件定时器(xTimerCreate() 创建的定时器),检测定时器超时并触发回调函数。 |
二、删除核心任务导致崩溃的底层原因
为什么删除这两个任务会崩溃?本质是破坏了 FreeRTOS 的 “调度逻辑闭环” 和 “资源管理机制”:
1. 删除「空闲任务」:系统失去 “兜底” 和 “资源回收” 能力
-
无任务可调度,触发硬 fault:FreeRTOS 要求 “就绪列表中必须至少有一个可运行任务”。若删除空闲任务,且所有用户任务都进入阻塞态(如调用
vTaskDelay()、xQueueReceive()),系统会因 “无任务可调度” 陷入死循环,或触发内核断言(configASSERT()),最终导致硬 fault。 -
动态任务内存泄漏,引发内存溢出:动态创建的任务(
xTaskCreate())被删除后,其栈和 TCB 内存的回收依赖空闲任务(空闲任务会定期检查 “待回收任务链表”,释放内存)。若没有空闲任务,被删除的任务内存无法回收,会持续占用堆空间,最终导致堆内存溢出,后续任务创建失败或触发内存访问错误。 -
示例崩溃场景:调用
vTaskDelete(xIdleTaskHandle)(假设获取到空闲任务句柄)后,若用户任务task1调用vTaskDelay(1000)进入阻塞态,系统会立即因 “无就绪任务” 崩溃。
2. 删除「定时器任务」:软件定时器机制失效,引发资源死锁
-
定时器队列无人处理,触发死锁:软件定时器的超时事件会通过 “定时器队列”(
xTimerQueue)传递给定时器任务。若删除定时器任务,队列中的超时事件会堆积,无人处理,最终导致队列满溢,或后续调用xTimerStart()等 API 时阻塞超时,引发系统逻辑死锁。 -
断言失败(Debug 模式下):FreeRTOS 调试版本中,若检测到定时器任务被删除,会触发
configASSERT()(如port.c或timers.c中的断言),直接暂停系统并提示错误。 -
示例崩溃场景:启用
configUSE_TIMERS = 1后,创建一个软件定时器xTimer,然后删除定时器任务。当定时器超时后,超时事件无法被处理,后续调用xTimerReset()会因队列阻塞超时,导致任务逻辑卡死。
三、如何避免 “误删核心任务”?
核心原则:不获取、不操作核心任务的句柄,不调用 vTaskDelete() 删除它们。具体措施:
1. 禁止手动获取核心任务的句柄
FreeRTOS 不提供公开 API 获取空闲任务 / 定时器任务的句柄(部分非官方方法如通过任务名称查找,本质是 “hack”,不推荐)。若通过非法方式获取到句柄(如 xTaskGetHandle("IDLE")),绝对禁止传入 vTaskDelete()。
2. 确认核心任务的 “不可删除” 属性
- 空闲任务的优先级是 最低优先级(
tskIDLE_PRIORITY,默认 0),且内核会保护其不被意外删除; - 定时器任务的优先级由
configTIMER_TASK_PRIORITY配置(通常设为高优先级),同样属于系统级任务,禁止删除。
3. 检查配置,确保核心任务正常创建
在 FreeRTOSConfig.h 中,确保以下配置正确(默认已开启,无需修改):
#define configUSE_IDLE_HOOK 0 // 空闲钩子可选,但空闲任务必须存在
#define configUSE_TIMERS 0 // 若不使用软件定时器,可设为0(不创建定时器任务)
#define configTIMER_TASK_PRIORITY 3 // 若启用定时器任务,优先级需合理配置(低于configMAX_PRIORITIES)
四、崩溃后的排查方向
若系统因 “疑似删除核心任务” 崩溃,可通过以下步骤确认:
-
查看任务列表,确认核心任务是否存在使用 FreeRTOS 提供的任务列表 API(如
vTaskList()),在系统启动后打印所有任务:char task_list[1024]; vTaskList(task_list); printf("任务列表:\r\n%s", task_list);- 正常情况下,会显示
IDLE任务(空闲任务); - 若启用软件定时器,会显示
Tmr Svc任务(定时器任务)。若这两个任务缺失,说明已被删除或未正常创建。
- 正常情况下,会显示
-
检查
vTaskDelete()的调用位置全局搜索代码中vTaskDelete()的调用,确认是否有传入非法句柄(如通过xTaskGetHandle()获取的IDLE或Tmr Svc任务句柄)。 -
Debug 模式下查看断言信息若使用 Debug 版本的 FreeRTOS(启用
configASSERT()),崩溃时会触发断言,通过调试器可查看断言位置(如timers.c中检查定时器任务是否存在的断言),快速定位是否因删除核心任务导致。
五、总结
FreeRTOS 的 空闲任务和定时器任务 是调度器的 “基础设施”,而非用户可管理的普通任务:
- 空闲任务:负责 “兜底调度” 和 “动态内存回收”,删除后系统无任务可调度或内存泄漏;
- 定时器任务:负责软件定时器管理,删除后定时器机制失效并引发死锁。
禁忌:永远不要尝试获取或删除这两个核心任务的句柄,用户只需关注 “自己创建的普通任务” 的生命周期管理(如删除用户任务 task1/task2),可避免此类崩溃。
使用示例
//任务删除
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
// 任务句柄定义
TaskHandle_t task1_handle; // 控制PC13
TaskHandle_t task2_handle; // 控制PA5
TaskHandle_t task3_handle; // 控制PB1
TaskHandle_t task4_handle; // 控制PA7
TaskHandle_t task5_handle; // 控制任务:负责删除其他任务
// 任务1:PC13闪烁(1秒周期)
void task1(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(1000);
while(1) {
GPIOC->ODR ^= GPIO_ODR_ODR13; // 取反PC13
vTaskDelay(xDelay);
}
}
// 任务2:PA5闪烁(0.5秒周期)
void task2(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(500);
while(1) {
GPIOA->ODR ^= GPIO_ODR_ODR5; // 取反PA5
vTaskDelay(xDelay);
}
}
// 任务3:PB1闪烁(0.2秒周期)
void task3(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(200);
while(1) {
GPIOB->ODR ^= GPIO_ODR_ODR1; // 取反PB1
vTaskDelay(xDelay);
}
}
// 任务4:PA7闪烁(0.3秒周期)
void task4(void *pvParam)
{
const TickType_t xDelay = pdMS_TO_TICKS(300);
while(1) {
GPIOA->ODR ^= GPIO_ODR_ODR7; // 取反PA7
vTaskDelay(xDelay);
}
}
// 任务5:控制任务,负责删除task1、task2,最后尝试删除空闲任务(错误操作)
void task5(void *pvParam)
{
// 1. 延时5秒,让所有任务先运行一段时间
vTaskDelay(pdMS_TO_TICKS(5000));
// 2. 删除task1
if (task1_handle != NULL ) {
vTaskDelete(task1_handle);
task1_handle = NULL;
// PC13停止闪烁,说明task1已删除
}
// 3. 延时2秒,观察现象
vTaskDelay(pdMS_TO_TICKS(2000));
// 4. 删除task2
if (task2_handle != NULL ) {
vTaskDelete(task2_handle);
task2_handle = NULL;
// PA5停止闪烁,说明task2已删除
}
// 5. 延时2秒,准备执行错误操作
vTaskDelay(pdMS_TO_TICKS(2000));
// 6. 尝试删除空闲任务(错误操作!仅作演示,实际会导致系统崩溃)
TaskHandle_t xIdleHandle = xTaskGetHandle("IDLE"); // 获取空闲任务句柄(非官方方法,不推荐)
if (xIdleHandle != NULL) {
vTaskDelete(xIdleHandle); // 执行此句后系统会立即崩溃
}
// 7. 任务5自我删除(实际不会执行,因上一步已崩溃)
vTaskDelete(NULL);
}
int main(void)
{
// 1. 启用时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
// 2. 配置PC13
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13);
GPIOC->CRH |= GPIO_CRH_MODE13_1 | GPIO_CRH_MODE13_0; // 推挽输出,50MHz
// 3. 配置PA5
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5);
GPIOA->CRL |= GPIO_CRL_MODE5_1 | GPIO_CRL_MODE5_0; // 推挽输出,50MHz
// 4. 配置PB1
GPIOB->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1);
GPIOB->CRL |= GPIO_CRL_MODE1_1 | GPIO_CRL_MODE1_0; // 推挽输出,50MHz
// 5. 配置PA7
GPIOA->CRL &= ~(GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
GPIOA->CRL |= GPIO_CRL_MODE7_1 | GPIO_CRL_MODE7_0; // 推挽输出,50MHz
// 6. 创建任务
xTaskCreate(task1, "task1", 128, NULL, 2, &task1_handle);
xTaskCreate(task2, "task2", 128, NULL, 3, &task2_handle);
xTaskCreate(task3, "task3", 128, NULL, 1, &task3_handle);
xTaskCreate(task4, "task4", 128, NULL, 4, &task4_handle);
xTaskCreate(task5, "task5", 128, NULL, 5, &task5_handle);
// 控制任务(最高优先级或关闭调度,若其他任务比控制任务优先级高则会导致其他任务可能没法启动)
// 7. 启动调度器
vTaskStartScheduler();
// 8. 调度器启动失败时的循环
while(1) {}
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)