前言
任务(Task)是 FreeRTOS 的基本执行单元。这一篇把任务创建、删除、参数传递讲透,附完整可运行示例。
目录
[2.1 函数原型](#2.1 函数原型)
[2.1.1 参数详解](#2.1.1 参数详解)
[2.1.2 返回值](#2.1.2 返回值)
[2.2 定义任务函数](#2.2 定义任务函数)
[2.3 定义任务句柄(推荐)](#2.3 定义任务句柄(推荐))
[三、示例 1:创建两个任务](#三、示例 1:创建两个任务)
[四、示例 2:使用任务参数](#四、示例 2:使用任务参数)
[示例 3:删除任务完整演示](#示例 3:删除任务完整演示)
一、任务的基本概念
任务是 FreeRTOS 的最小调度单位,本质就是一个永远不会返回的函数。多任务 = 多个这样的函数"同时"运行,由调度器分配 CPU。
任务和普通函数的区别:
| 维度 | 普通函数 | 任务 |
|---|---|---|
| 返回 | 执行完返回 | 永不返回(while(1)) |
| 执行 | 被调用才执行 | 由调度器调度执行 |
| 状态 | 无 | 有状态(就绪/运行/阻塞/挂起) |
| 栈 | 共用系统栈 | 每个任务独立栈 |
二、创建任务:三个步骤
2.1函数原型
cpp
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 函数指针,任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为 word
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask // 任务句柄,以后用它来操作这个任务
);
2.1.1 参数详解
| 参数 | 描述 |
|---|---|
| pxTaskCode | 函数指针,指向任务函数。任务本质上是一个 C 函数,但特殊之处在于:它要么永不退出(死循环),要么退出时必须调用 vTaskDelete(NULL) 自我删除。 |
| pcName | 任务的名字,FreeRTOS 内部不使用它,仅用于调试。长度受 configMAX_TASK_NAME_LEN 限制。 |
| usStackDepth | 每个任务都有自己的栈,这里指定栈大小。单位是 word (4 字节),比如传入 100,表示栈大小为 100 word = 400 字节。最大值受 uint16_t 限制。栈大小的确定往往靠估算,精确方法是通过看反汇编代码。 |
| pvParameters | 调用任务函数时传入的参数,会作为实参传递给 pxTaskCode 指向的函数:pxTaskCode(pvParameters)。 |
| uxPriority | 优先级范围:0 ~ (configMAX_PRIORITIES - 1)。数值越小优先级越低 ,如果传入过大的值,xTaskCreate 会自动将其调整为 configMAX_PRIORITIES - 1。 |
| pxCreatedTask | 用于保存任务句柄(task handle),以后如果要操作该任务(如修改优先级、删除任务),都需要这个句柄。如果不需要,可传入 NULL。 |
2.1.2 返回值
| 返回值 | 含义 |
|---|---|
| pdPASS | 任务创建成功。 |
| errCOULD_NOT_ALLOCATE_REQUEST_MEMORY | 任务创建失败,唯一原因是内存不足(堆空间不够分配 TCB 和任务栈)。 |
注意 :许多文档说失败时返回
pdFAIL(值为 0),但实际上xTaskCreate失败时返回的是errCOULD_NOT_ALLOCATE_REQUEST_MEMORY(值为 -1)。这在调试内存分配问题时需要留意。
2.2 定义任务函数
任务函数必须遵循特定原型:
cpp
void vTaskFunction(void *pvParameters);
-
函数名可自定义,建议用
vTaskXxx格式(遵守命名规范); -
pvParameters是通用指针,接收传入参数,不需要就填 NULL; -
任务函数通常是一个无限循环------任务创建后会一直运行,直到被删除或系统停止。如果函数体不是循环,任务跑完就自动"死"了。
2.3 定义任务句柄(推荐)
任务句柄是任务的"身份证",通过它可以删除、挂起、恢复任务:
cpp
TaskHandle_t xTaskHandle;
三、示例 1:创建两个任务
cpp
void vTask1(void *pvParameters)
{
while (1)
{
printf("Task1 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延时 500ms
}
}
void vTask2(void *pvParameters)
{
while (1)
{
printf("Task2 running\n");
vTaskDelay(pdMS_TO_TICKS(500));
}
}
int main(void)
{
xTaskCreate(vTask1, "task1", 128, NULL, 1, NULL);
xTaskCreate(vTask2, "task2", 128, NULL, 1, NULL);
vTaskStartScheduler(); // 启动调度器(正常不会返回)
return 0; // 永远不会执行到这里
}
运行结果:任务 1 和任务 2 轮流执行。
⚠️ 注意一个现象:task2 先运行! 原因:
-
两个任务优先级相同(都是 1);
-
同优先级时,后创建的任务先运行(新任务就绪后被放到就绪列表末尾,调度器按顺序轮转,先轮到 task2);
-
更深层的原因要看
xTaskCreate的源码实现。
新手很容易在这里栽跟头,以为"先创建的先跑"------恰恰相反。
四、示例 2:使用任务参数
多个任务可以共用一个函数,差别靠参数 和独立栈体现:
cpp
void vPrintTask(void *pvParameters)
{
int num = *(int *)pvParameters; // 取出参数
while (1)
{
printf("Task %d running\n", num);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
int main(void)
{
static int num1 = 1, num2 = 2; // 注意:要用静态变量或全局变量
xTaskCreate(vPrintTask, "task1", 128, &num1, 1, NULL);
xTaskCreate(vPrintTask, "task2", 128, &num2, 1, NULL);
vTaskStartScheduler();
}
运行结果:同一个函数、不同任务、不同参数,任务 1 和任务 2 轮流执行,各自打印自己的编号。
⚠️ 两个易错点:
-
参数指针指向的生命周期 :
&num1必须指向长期有效的内存(全局/静态变量),不能传函数内的局部变量地址(函数返回后变量就没了); -
任务参数是
void*,传入后要自己强转类型。
五、删除任务:vTaskDelete
cpp
void vTaskDelete(TaskHandle_t xTaskToDelete);
-
传入任务句柄 → 删除指定任务;
-
传 NULL → 删除自己。
三种删除场景:
| 场景 | 代码 | 说明 |
|---|---|---|
| 自杀 | vTaskDelete(NULL) |
任务删除自己 |
| 被杀 | vTaskDelete(xTaskSelfHandle) |
别的任务删除我 |
| 杀人 | vTaskDelete(xOtherHandle) |
我删除别的任务 |
示例 3:删除任务完整演示
任务 1:循环打印 1;任务 2:循环打印 2,1 秒后删除任务 1 ,再过 1 秒自杀:
cpp
TaskHandle_t xTask1Handle; // 任务 1 的句柄(全局,供任务 2 使用)
void vTask1(void *pvParameters)
{
while (1)
{
printf("1\n");
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void vTask2(void *pvParameters)
{
vTaskDelay(pdMS_TO_TICKS(1000)); // 等 1 秒
vTaskDelete(xTask1Handle); // 删除任务 1
vTaskDelay(pdMS_TO_TICKS(1000)); // 再等 1 秒
vTaskDelete(NULL); // 自杀
}
int main(void)
{
xTaskCreate(vTask1, "task1", 128, NULL, 1, &xTask1Handle);
xTaskCreate(vTask2, "task2", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
运行结果:
1
2
1
2
1
2
1
2
1 ← 第 1 秒,任务 2 删除任务 1
2
2 ← 第 2 秒,任务 2 自杀,系统只剩空闲任务
注意:被删除的任务内存会由空闲任务回收,所以"删除"不是立即释放资源,而是等空闲任务来处理。
六、总结
-
任务三步走:定义函数 → 定义句柄 → xTaskCreate;
-
任务函数必须
while(1),否则跑完就"死"了; -
同优先级后创建的先运行 ;不同优先级高的先运行;
-
任务参数用
void*传指针,注意指向的变量要长期有效; -
删除任务三种姿势:自杀 / 被杀 / 杀人;
-
被删任务的资源由空闲任务回收。