FreeRTOS(四)之任务管理(上):创建、删除与任务参数

前言

任务(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:使用任务参数)

五、删除任务:vTaskDelete

[示例 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. 两个任务优先级相同(都是 1);

  2. 同优先级时,后创建的任务先运行(新任务就绪后被放到就绪列表末尾,调度器按顺序轮转,先轮到 task2);

  3. 更深层的原因要看 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 轮流执行,各自打印自己的编号。

⚠️ 两个易错点:

  1. 参数指针指向的生命周期&num1 必须指向长期有效的内存(全局/静态变量),不能传函数内的局部变量地址(函数返回后变量就没了);

  2. 任务参数是 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* 传指针,注意指向的变量要长期有效;

  • 删除任务三种姿势:自杀 / 被杀 / 杀人

  • 被删任务的资源由空闲任务回收。

相关推荐
论迹复利2 天前
PendSV与RISC-V trap——上下文切换的两种设计哲学
内核·freertos·risc-v
捷瑞电子工坊5 天前
FreeRTOS中断管理:从原理到实践(标准库版)
嵌入式·freertos·实时操作系统·任务通知·临界区·中断管理·二值信号量
捷瑞电子工坊6 天前
FreeRTOS 内存管理详解:从 heap_1 到 heap_5 的选择与实践
freertos·内存管理·嵌入式开发·内存碎片·嵌入式实时操作系统·动态内存分配
qq_401700416 天前
FreeRTOS队列集--允许任务同时在多个队列或信号量上阻塞等待读取操作
freertos
hsjiasb7 天前
FreeRTOS学习(三十九)——最终总结
学习·学习笔记·freertos
捷瑞电子工坊7 天前
FreeRTOS事件组:多任务同步的利器
stm32·嵌入式·freertos·实时操作系统·标准外设库·事件组·任务同步
hsjiasb7 天前
FreeRTOS学习(三十八)——性能优化
学习·学习笔记·freertos
hsjiasb8 天前
FreeRTOS学习(三十七)——常见错误与工程规范
学习·学习笔记·freertos
捷瑞电子工坊9 天前
FreeRTOS任务管理:从入门到实践(标准库版)
stm32·嵌入式·freertos·实时操作系统·任务管理·标准外设库·任务挂起与恢复