第三章 FreeRTOS 任务相关 API 函数

本章开始学习 FreeRTOS 任务相关的 API函数,重点学习函数的用法,先不深究函数内部实现原理,相关的原理性知识会在后面学习。

本章分为如下几部分:

3.1 FreeRTOS 创建和删除任务

3.2 FreeRTOS 获取和设置任务优先级

3.3 FreeRTOS 挂起和恢复任务

3.1 FreeRTOS 创建和删除任务

先以hello_word为模版创建一个基础工程,创建方法见第六章的6.1小节,创建好后,我们修改下文件目录名为01_FreeRTOS_Task,接下来就以该工程学习任务相关API函数。

FreeRTOS 中用于创建、删除任务及任务相关的 API 函数如下表所示:

函数 描述
xTaskCreate() 动态方式创建任务
xTaskCreateStatic() 静态方式创建任务
xTaskCreateRestricted() 动态方式创建使用 MPU 限制的任务
xTaskCreateRestrictedStatic() 静态方式创建使用 MPU 限制的任务
vTaskDelete() 删除任务
vTaskList() 获取任务列表
uxTaskGetStackHighWaterMark() 获取任务的任务栈历史剩余最小值

表 3.1.1 任务相关 API 函数

1. 函数 xTaskCreate()

此函数用于使用动态的方式创建任务, 任务的控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配, 若使用此函数, 需要在 FreeRTOSConfig.h(文件路径frameworks\esp-idf-v5.4.2\components\freertos\config\include\freertos\FreeRTOSConfig.h) 文件中将宏 configSUPPORT_DYNAMIC_ALLOCATION 配置为 1(默认已设置为1)。 此函数创建的任务会立刻进入就绪态, 由任务调度器调度运行。 函数原型如下所示:

复制代码
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,          // 指向任务函数的指针
                       const char * const pcName,          // 任务的可读名称(用于调试)
                       configSTACK_DEPTH_TYPE usStackDepth, // 任务堆栈大小(以字为单位)
                       void * const pvParameters,          // 传递给任务函数的参数指针
                       UBaseType_t uxPriority,             // 任务的优先级
                       TaskHandle_t * const pxCreatedTask  // 用于返回任务句柄的指针
);

下表详细说明了每个参数的含义和注意事项:

参数 类型 说明与要点
​​pvTaskCode​​ TaskFunction_t 指向任务具体执行逻辑的函数指针。该函数必须为 void vTaskFunction(void *pvParameters)形式,并且通常应包含一个​​无限循环​ ​,任务不能返回(即函数内部不能有 return语句)。
​​pcName​​ const char * const 任务的描述性名称,主要用于调试和追踪。其长度受 FreeRTOSConfig.h中的 configMAX_TASK_NAME_LEN宏定义限制,超出部分会被截断。
​​usStackDepth​​ configSTACK_DEPTH_TYPE 指定任务堆栈的深度,​​单位是字(Word)​​,而不是字节。例如,在32位架构中,1个字等于4字节。如果需要1KB(1024字节)的堆栈空间,则此参数应设置为256。堆栈大小需根据任务中局部变量、函数调用深度等因素合理设置,过小会导致堆栈溢出。
​​pvParameters​​ void * const 一个泛型指针,用于在任务创建时向任务函数传递参数。可以指向一个变量、结构体或其它数据。如果不需要传递参数,则设置为 NULL
​​uxPriority​​ UBaseType_t 定义任务的优先级。​​数值越大,优先级越高​ ​。优先级的取值范围为0(最低,通常为 tskIDLE_PRIORITY)到 (configMAX_PRIORITIES - 1)。FreeRTOS调度器默认采用抢占式调度,高优先级任务会抢占低优先级任务的CPU使用权。
​​pxCreatedTask​​ TaskHandle_t * const 用于输出新创建任务的​​句柄(Handle)​ ​。该句柄可用于后续对任务进行操作,例如删除任务 (vTaskDelete)、挂起/恢复任务或修改优先级等。如果不需要使用该句柄,可以传入 NULL

表 3.1.2 函数 xTaskCreate()形参相关描述

返回值与工作机制:

| 返回值 | 说明与要点 |
| ​​pdPASS​​ | 表示任务创建成功,并已放入就绪列表(Ready List),等待调度器调度。 |

pdFAIL ​(或 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 表示任务创建失败,通常是因系统堆内存不足,无法为任务的控制块(TCB)和堆栈分配所需空间。

表 3.1.3 函数 xTaskCreate()返回值描述

动态内存分配​ ​:xTaskCreate通过动态方式创建任务,它会自动从 FreeRTOS 的堆中申请任务控制块(TCB)和任务栈所需的内存。在项目的 FreeRTOSConfig.h配置文件中,必须将宏 configSUPPORT_DYNAMIC_ALLOCATION定义为 1。任务被删除 (vTaskDelete) 后,其占用的内存会被空闲任务(Idle Task)自动回收,但是注意。


**实验1:**我们分别调用xTaskCreate()函数创建3个任务,其中task1和task2有入参,task3无入参,并在任务中打印相关信息。
代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct TaskInfo
{
    TaskHandle_t xHandle;
    BaseType_t xResult;
} TaskInfo;

typedef struct PersonInfo
{
    char name[30];
    uint8_t age;
} PersonInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        printf("%s, In the task! data = %d\n", __FUNCTION__, *data);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *para)
{
    PersonInfo *personInfo = (PersonInfo *)para;
    while (1)
    {
        printf("%s, In the task! name = %s, age = %d\n", __FUNCTION__, personInfo->name, personInfo->age);
        vTaskDelay(pdMS_TO_TICKS(1500));
    }
}

void myTask3()
{
    while (1)
    {
        printf("%s, In the task!\n", __FUNCTION__);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

void app_main(void)
{
    uint16_t testCnt = 1;

    TaskInfo taskInfo1 = {0};
    TaskInfo taskInfo2 = {0};
    TaskInfo taskInfo3 = {0};
    PersonInfo personInfo = {.name = "I am task2", .age = 18};

    taskInfo1.xResult = xTaskCreate(myTask1,
                                    "myTask1",
                                    2048,
                                    &testCnt,
                                    1,
                                    &taskInfo1.xHandle);

    taskInfo2.xResult = xTaskCreate(myTask2,
                                    "myTask2",
                                    2048,
                                    &personInfo,
                                    1,
                                    &taskInfo2.xHandle);

    taskInfo3.xResult = xTaskCreate(myTask3,
                                    "myTask3",
                                    2048,
                                    NULL,
                                    2,
                                    NULL);

    if ((pdPASS != taskInfo1.xResult) || (pdPASS != taskInfo2.xResult) || (pdPASS != taskInfo3.xResult))
    {
        printf("Create task fail!\n");
    }

    while (1)
    {
        testCnt++;
        printf("%s, In the task, testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

实验输出结果:

图 3.1.1 xTaskCreate()函数实验结果

2. 函数 xTaskCreateStatic()

此函数用于使用静态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,

需要由用户分配提供,若使用此函数,需要在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION(默认为1)配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

复制代码
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,       // 任务函数指针
                                const char * const pcName,       // 任务名称(用于调试)
                                uint32_t ulStackDepth,          // 任务栈深度
                                void * pvParameters,            // 传递给任务的参数
                                UBaseType_t uxPriority,         // 任务优先级
                                StackType_t * puxStackBuffer,  // 任务栈缓冲区指针
                                StaticTask_t * pxTaskBuffer ); // 任务控制块(TCB)缓冲区指针

函数 xTaskCreateStatic()的形参描述, 如下表所示:

形参 描述
pxTaskCode 指向任务函数的指针
pcName 任务名, 最大长度为 configMAX_TASK_NAME_LEN
ulStackDepth 任务堆栈大小(栈深度), 其单位是 StackType_t类型的元素数量。在 32 位架构(如 ESP32)中,StackType_t通常为 4 字节。因此,若需要 1KB 的栈空间,此参数应设置为 256(1024 字节 / 4 字节)。
pvParameters 传递给任务函数的参数
uxPriority 任务优先级, 最大值为(configMAX_PRIORITIES-1)
puxStackBuffer 任务栈指针(栈缓冲区),指向用户预先分配的栈内存的指针。这通常是一个 StackType_t类型的大数组。
pxTaskBuffer 任务控制块指针(TCB缓冲区), 指向用户预先分配的任务控制块(TCB)内存的指针。这是一个 StaticTask_t类型的变量。TCB 用于存储任务的状态、优先级、栈指针等信息,操作系统通过它来管理任务。每个任务都有一个对应的 TCB,它包含了任务的所有相关信息,就像任务的"身份证"一样。

表 3.1.4 函数 xTaskCreateStatic()形参相关描述

函数 xTaskCreateStatic()的返回值, 如下表所示:

返回值 描述
NULL 用户没有提供相应的内存, 任务创建失败。
其他值 任务句柄, 成功创建任务时,函数返回一个 TaskHandle_t类型的任务句柄,可用于后续管理任务(如删除、挂起)。

表 3.1.5 函数 xTaskCreateStatic()返回值相关描述

重要注意事项:

  • 内存生命周期管理​​: 静态分配的内存(栈和TCB)在任务被删除后不会被自动释放。您需要自行管理这些内存的复用或生命周期。这与动态创建不同,动态创建的任务删除后,其内存由空闲任务自动回收;
  • 确保内存独占性​​: 传递给 xTaskCreateStatic的栈和 TCB 缓冲区必须​​专属于该任务,在该任务存活期间不能被其他任务或代码覆盖;
  • **栈大小估算​​:**需要更精确地估算任务所需栈空间。建议使用 FreeRTOS 提供的 uxTaskGetStackHighWaterMark()函数来监控任务运行过程中的历史最小剩余栈空间,帮助优化栈大小设置,避免浪费或溢出。

**实验2:**我们分别调用xTaskCreateStatic()函数创建3个任务,其中task1和task2有入参,task3无入参,并在任务中打印相关信息。
代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct PersonInfo
{
    char name[30];
    uint8_t age;
} PersonInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        printf("%s, In the task! data = %d\n", __FUNCTION__, *data);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *para)
{
    PersonInfo *personInfo = (PersonInfo *)para;
    while (1)
    {
        printf("%s, In the task! name = %s, age = %d\n", __FUNCTION__, personInfo->name, personInfo->age);
        vTaskDelay(pdMS_TO_TICKS(1500));
    }
}

void myTask3()
{
    while (1)
    {
        printf("%s, In the task!\n", __FUNCTION__);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

#define MY_TASK_STACK_SIZE 2048 // 栈深度,单位为字(Word)
static StackType_t xMyTaskStack[3][MY_TASK_STACK_SIZE];
static StaticTask_t xMyTaskTCB[3];

void app_main(void)
{
    uint16_t testCnt = 1;
    TaskHandle_t xMyTaskHandle[3] = {0};
    PersonInfo personInfo = {.name = "I am task2", .age = 18};

    xMyTaskHandle[0] = xTaskCreateStatic(
        myTask1,            // 任务函数
        "myTask1",          // 任务名
        MY_TASK_STACK_SIZE, // 栈深度
        &testCnt,           // 任务参数
        1,                  // 优先级(数值越大优先级越高)
        xMyTaskStack[0],    // 栈缓冲区指针
        &xMyTaskTCB[0]      // TCB 缓冲区指针
    );

    xMyTaskHandle[1] = xTaskCreateStatic(
        myTask2,            // 任务函数
        "myTask2",          // 任务名
        MY_TASK_STACK_SIZE, // 栈深度
        &personInfo,        // 任务参数
        3,                  // 优先级(数值越大优先级越高)
        xMyTaskStack[1],    // 栈缓冲区指针
        &xMyTaskTCB[1]      // TCB 缓冲区指针
    );

    xMyTaskHandle[2] = xTaskCreateStatic(
        myTask3,            // 任务函数
        "myTask3",          // 任务名
        MY_TASK_STACK_SIZE, // 栈深度
        NULL,               // 任务参数
        2,                  // 优先级(数值越大优先级越高)
        xMyTaskStack[2],    // 栈缓冲区指针
        &xMyTaskTCB[2]      // TCB 缓冲区指针
    );

    if ((NULL == xMyTaskHandle[0]) || (NULL == xMyTaskHandle[1]) || (NULL == xMyTaskHandle[2]))
    {
        printf("Create task fail!\n");
    }

    while (1)
    {
        testCnt++;
        printf("%s, In the task, testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

实验输出结果:

图 3.1.2 实验xTaskCreateStatic()函数实验结果

3. 函数 xTaskCreateRestricted()

此函数用于创建受 MPU(内存保护单元)保护的任务,能有效提升系统的稳定性和安全性。当任务试图访问未授权的内存区域时,MPU 会触发异常,防止系统崩溃或数据损坏。

任务控制块以及任务的栈空间所需的内存, 均由 FreeRTOS 从 FreeRTOS 管理的堆中分配, 若使用此函数, 需要将宏configSUPPORT_DYNAMIC_ALLOCATION 和宏 portUSING_MPU_WRAPPERS(在IDF下FreeRTOSConfig.h配置文件中未找到) 同时配置为 1。此函数创建的任务会立刻进入就绪态, 由任务调度器调度运行。 函数原型如下所示:

复制代码
BaseType_t xTaskCreateRestricted( const TaskParameters_t *pxTaskDefinition,
                                  TaskHandle_t *pxCreatedTask );
参数 类型 说明
​​pxTaskDefinition​​ const TaskParameters_t * 指向 TaskParameters_t结构体的指针,该结构体包含了任务的所有配置参数。
​​pxCreatedTask​​ TaskHandle_t * 用于返回新创建任务的句柄,后续可通过此句柄操作任务。

表 3.1.6 函数 xTaskCreateRestricted()形参相关描述

TaskParameters_t结构体​ ​(定义在 task.h中)是关键,它整合了创建任务所需的全部信息,通常包括:

| 成员 | 说明 |
| ​ pvTaskCode​ | 任务函数指针 |
| ​ pcName​ | 任务名称 |
| usStackDepth | 堆栈深度 |
| pvParameters | 传递给任务的参数 |
| uxPriority | 任务优先级 |
| puxStackBuffer | 指向堆栈缓冲区的指针(静态分配时) |

xRegions MPU 内存区域配置(核心,定义任务可访问的内存区域)

表 3.1.7 函数TaskParameters_t结构体​​描述

| 返回值 | 描述 |
| pdPASS | 任务创建成功。 |

​​其他值​​(如 pdFAIL 任务创建失败,通常是由于 FreeRTOS 的堆内存不足或 MPU 配置无效。

表 3.1.8 函数xTaskCreateRestricted()返回值描述

4. 函数 xTaskCreateRestrictedStatic()

此函数用于使用静态的方式创建受 MPU 保护的任务, 此函数创建的任务的任务控制块以及任务的栈空间所需的内存, 需要由用户自行分配提供, 若使用此函数, 需要将宏configSUPPORT_STATIC_ALLOCATION 和宏 portUSING_MPU_WRAPPERS (在IDF下FreeRTOSConfig.h配置文件中未找到)同时配置为 1。此函数创建的任务会立刻进入就绪态, 由任务调度器调度运行。 函数原型如下所示:

复制代码
BaseType_t xTaskCreateRestrictedStatic( const TaskParameters_t * const pxTaskDefinition,
                                        TaskHandle_t * pxCreatedTask );

函数 xTaskCreateRestrictedStatic()的形参描述, 如下表所示:

形参 描述
pxTaskDefinition 指向任务参数结构体的指针, 建结构体中包含任务函数、 任 务名、 任务优先级等任务参数
pxCreadedTask 任务句柄, 任务成功创建后, 会返回任务句柄。 任务句柄就 是任务的任务控制块

表 3.1.9 函数 xTaskCreateRestrictedStatic()形参相关描述

函数 xTaskCreateRestrictedStatic()的返回值, 如下表所示:

返回值 描述
pdPASS 任务创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 没有提供相应的内存, 任务创建失败

表 3.1.10 函数 xTaskCreateRestrictedStatic()返回值相关描述

重要注意事项:

  • **MPU 区域配置​​:**xRegions成员的配置是使用该函数最复杂也是最关键的部分。您需要根据任务实际需要访问的内存地址范围(如代码段、数据段、外设寄存器等)来正确设置每个区域的起始地址、大小和访问权限(如只读、读写、不可执行等)。错误的配置可能导致任务无法正常运行;
  • **任务协作​​:**受 MPU 保护的任务必须设计为协作式。任务函数内部必须包含能让出 CPU 的调用,如 vTaskDelay(), xQueueReceive()等。严禁使用空循环(忙等待),否则会阻塞低优先级的空闲任务,导致系统资源无法回收,最终可能引发看门狗复位;
  • **​​内存管理责任​​:**由于采用静态分配,当任务被 vTaskDelete()删除后,其 TCB 和栈所占用的内存(即您预先分配的数组)不会被自动释放。您需要自行管理这些内存的复用。
5、任务创建相关函数比较
特性 xTaskCreate xTaskCreateStatic xTaskCreateRestricted xTaskCreateRestrictedStatic
​​内存分配​​ 动态(系统自动) 静态(用户提供) 动态(系统自动) ​​静态(用户提供)​​
​​MPU 保护​​ 不适用 不适用 ​​支持​​ ​​支持​​
​​确定性/可靠性​​ 高(无堆碎片风险) ​​极高(无堆碎片风险+内存保护)​​
​​使用复杂度​​ 简单 中等 复杂 ​​最复杂​​
​​适用场景​​ 通用应用,快速开发 资源紧张,需高确定性 需MPU保护但内存管理偏好动态 ​​安全关键,对确定性和可靠性要求极高的系统​

表 3.1.11 任务创建函数比较

6. 函数 vTaskList()

此函数用于以"表格"的形式获取系统中任务的信息,若使用此函数,在SDK配置编辑器(menuconfig)中设置配置项configUSE_TRACE_FACILITY、configUSE_STATS_FORMATTING_FUNCTIONS(默认没有勾选,否则即使包了头文件也会报未定义)和在FreeRTOSConfig.h文件中同时配置configUSE_TRACE_FACILITY、configUSE_STATS_FORMATTING_FUNCTIONS为1(默认为1),此函数的函数原型如下所示:

复制代码
voidvTaskList(char*pcWriteBuffer);

函数vTaskList()的形参描述,如下表所示:

形参 描述
pcWriteBuffer 接收任务信息的缓存指针,该函数会遍历当前系统中的所有任务(包括空闲任务和软件定时器服务任务等系统任务),将每个任务的关键信息格式化为一个字符串表格,存入提供的缓冲区中。

表 3.1.12 函数 vTaskList()形参相关描述

函数 vTaskList()无返回值。
使用注意事项:

  • **实时性影响​​:**调用 vTaskList()时,FreeRTOS 会挂起调度器以确保数据一致性。因此,在函数执行期间所有任务都会被暂停,可能会影响系统的实时性,所以建议仅在调试时使用;
  • **​​仅在任务上下文中调用​​:**vTaskList()设计为在任务代码中调用,不要在中断服务程序(ISR)中调用它。

实验2:我们调用vTaskList()函数打印出创建的3个任务信息。

代码如下(只在main函数中修改,其他部分修改见上面xTaskCreate()实验代码):

复制代码
static char taskList[512] = {0};
    while (1)
    {
        testCnt++;
        printf("%s, In the task, testCnt = %d\n", __FUNCTION__, testCnt);

        vTaskList(taskList);
        printf("-----------------Task List----------------\n");
        printf("Name          State Priority   Stack   Num\n");
        printf("%s\n", taskList);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }

实验输出结果:

图 3.1.3 vTaskList()函数实验结果

打印"表格"说明:

列名 说明
​​Name​​ 任务名称(创建任务时指定)。
​​State​​ 任务当前状态,用单个字母表示: - ​​R (Running)​ ​: 运行态 - ​​B (Blocked)​ ​: 阻塞态 - ​​S (Suspended)​ ​: 挂起态 - ​​D (Deleted)​ ​: 删除态(等待清理) - ​​R (Ready)​​: 就绪态
​​Priority​​ 任务当前的优先级。数字越大,优先级越高。
​​Stack​​ 任务的​​堆栈高水位线​ ​(High Water Mark),即自任务运行以来堆栈剩余空间的最小值。​​这个值是诊断堆栈是否溢出的关键指标​​。单位通常是字节。
​​Num​​ 由 FreeRTOS 内核分配的唯一任务编号。当多个任务同名时,可通过此编号区分。

表 3.1.13 函数 vTaskList()打印结果说明

7. 函数vTaskDelete()

此函数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为1(默认为1)。函数原型如下所示:

复制代码
voidvTaskDelete(TaskHandle_txTaskToDelete);

函数vTaskDelete()的形参描述,如下表所示:

| 形参 | 描述 |

TaskHandle_t 指定要删除的任务。 * 如果传入一个​有效的任务句柄​,则删除该句柄对应的任务; * 如果传入 NULL ,则表示删除​当前正在调用该函数的任务自身​(即"任务自删")。

表 3.1.13 函数 vTaskDelete()形参相关描述

vTaskDelete内部机制与资源释放:

理解 vTaskDelete()的内部机制有助于避免常见的资源管理错误,其核心流程和内存释放责任可以概括为下图:

从上图可以看出,FreeRTOS 采用了一种"惰性删除"策略来平衡实时性和资源管理 。最关键的一点是:​vTaskDelete()函数本身不会立即释放任务的内存​ ​。对于自删的任务,其任务控制块(TCB)和堆栈内存的释放工作是由​​空闲任务(Idle Task)​ ​ 在后台完成的 。这意味着你必须确保空闲任务能够获得 CPU 时间运行,否则会导致内存泄漏 。

此外,需要特别注意的是,如果任务在运行期间使用 pvPortMalloc()等函数​​手动分配了内存​ ​,或者在运行中获取了信号量、互斥锁等资源,这些资源​​不会被自动释放​ ​。程序员必须在调用 vTaskDelete()之前自行清理这些资源,否则会造成资源泄漏 。

重要注意事项:

  • **​​禁止在中断中使用​​:**vTaskDelete()不是中断安全函数,绝对不能在中断服务程序(ISR)中调用。
  • **静态任务的内存管理​​:**对于使用 xTaskCreateStatic()创建的静态任务,其 TCB 和堆栈内存是由用户静态分配的。调用 vTaskDelete()后,任务会被从调度器中移除,但这些静态内存不会被释放,需要用户自行管理。
  • **句柄管理​​:**任务被删除后,其任务句柄就失效了。建议在删除任务后,将对应的句柄变量设置为 NULL,以防止后续误用成为"野指针"。
  • **确保空闲任务运行​​:**由于空闲任务负责清理资源,因此不能让它长时间阻塞或"饿死"。这意味着自删的任务必须设计为协作式,在其循环中应包含 vTaskDelay()、等待队列或信号量等能让出 CPU 的函数,而不能是纯粹的忙等待(while(1);)。

**实验3:**我们分别调用vTaskDelete()函数创建3个任务,在主任务中删除task2,task3任务中删除自己,观察任务中打印相关信息,同时我们通过vTaskList观察任务信息。
代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct TaskInfo
{
    TaskHandle_t xHandle;
    BaseType_t xResult;
} TaskInfo;

typedef struct PersonInfo
{
    char name[30];
    uint8_t age;
} PersonInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        printf("%s, In the task! data = %d\n", __FUNCTION__, *data);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

void myTask2(void *para)
{
    PersonInfo *personInfo = (PersonInfo *)para;
    while (1)
    {
        printf("%s, In the task! name = %s, age = %d\n", __FUNCTION__, personInfo->name, personInfo->age);
        vTaskDelay(pdMS_TO_TICKS(1500));
    }
}

void myTask3()
{
    printf("%s, In the task!\n", __FUNCTION__);
    vTaskDelay(pdMS_TO_TICKS(3000));
    vTaskDelete(NULL);
}

void app_main(void)
{
    uint16_t testCnt = 1;
    static char taskList[512] = {0};
    TaskInfo taskInfo1 = {0};
    TaskInfo taskInfo2 = {0};
    TaskInfo taskInfo3 = {0};
    PersonInfo personInfo = {.name = "I am task2", .age = 18};

    taskInfo1.xResult = xTaskCreate(myTask1,
                                    "myTask1",
                                    2048,
                                    &testCnt,
                                    1,
                                    &taskInfo1.xHandle);

    taskInfo2.xResult = xTaskCreate(myTask2,
                                    "myTask2",
                                    2048,
                                    &personInfo,
                                    1,
                                    &taskInfo2.xHandle);

    taskInfo3.xResult = xTaskCreate(myTask3,
                                    "myTask3",
                                    2048,
                                    NULL,
                                    2,
                                    NULL);

    if ((pdPASS != taskInfo1.xResult) || (pdPASS != taskInfo2.xResult) || (pdPASS != taskInfo3.xResult))
    {
        printf("Create task fail!\n");
    }


    while (1)
    {
        testCnt++;
        vTaskList(taskList);
        printf("-----------------Task List----------------\n");
        printf("Name          State Priority   Stack   Num\n");
        printf("%s\n", taskList);

        printf("%s, In the task, testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(3000));
        if (NULL != taskInfo1.xHandle)
        {
            vTaskDelete(taskInfo1.xHandle);
            taskInfo1.xHandle = NULL;
        }
    }
}

实验结果如下:

图 3.1.4 vTaskDelete()函数实验结果

8. 函数uxTaskGetStackHighWaterMark()

该函数用于查询指定任务自创建以来,其堆栈空间的​​历史最小剩余量​ ​,这个值被称为"高水位线"(High Water Mark),返回值越小,表示任务曾经消耗的堆栈空间越多,堆栈溢出的风险也就越大​​。若使用此函数,需在FreeRTOSConfig.h文件中设置配置项INCLUDE_uxTaskGetStackHighWaterMark为1(默认为1),此函数的函数原型如下所示:

复制代码
UBaseType_tuxTaskGetStackHighWaterMark(TaskHandle_txTask);

函数uxTaskGetStackHighWaterMark()的形参描述,如下表所示:

形参 描述
xTask 要查询的​​任务句柄​ ​(TaskHandle_t)。如果传入 NULL,则表示查询​​当前任务​​(即调用该函数的任务自身)的堆栈高水位线。

表 3.1.14 函数 uxTaskGetStackHighWaterMark()形参相关描述

函数 uxTaskGetStackHighWaterMark()的返回值, 如下表所示:

返回值 描述
整数 返回值的单位是​​字(Word)​​。在 ESP32(32 位架构)等平台上,1 字等于 4 字节。 * 返回值的意义​:它表示从任务开始运行到调用该函数时,堆栈剩余空间的最小值。例如,返回值为 100,表示在最坏的情况下,堆栈还有 100 字(400 字节)未被使用过; * 危险信号​ :如果返回值接近甚至等于 ​0​ ,则极有可能已经发生了​堆栈溢出​

表 3.1.15 函数 uxTaskGetStackHighWaterMark()返回值相关描述

重要注意事项:

  • 主要用于调试​​: 此函数会消耗一些 CPU 时间,建议主要在调试阶段使用,以帮助合理设置任务的堆栈大小(任务栈设置太小会导致程序崩溃,系统重启)。在产品发布版本中可考虑移除相关调用;
  • **​​估算堆栈需求​​:**通过高水位线,您可以估算出任务的最大堆栈使用量:最大使用量 ≈ 任务的总堆栈大小(字) - 高水位线(字);
  • **确保任务已创建​​:**在查询其他任务的高水位线时,请确保该任务句柄有效且任务已被创建;
  • **​​理解"历史最小值"​​:**该函数返回的是自任务运行以来的历史最小值。即使某次查询到的值较大,只要历史最小值很小,就依然存在风险。

**实验4:**我们分别创建2个任务task1和task2,在task1和task2任务中打印相关信息,然后调用uxTaskGetStackHighWaterMark()函数获取task1和task2,并打印堆栈空间的历史最小剩余量。

代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct TaskInfo
{
    TaskHandle_t xHandle;
    BaseType_t xResult;
} TaskInfo;

typedef struct PersonInfo
{
    char name[30];
    uint8_t age;
} PersonInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        printf("%s, In the task! test uxTaskGetStackHighWaterMark1\n", __FUNCTION__);
        printf("%s, In the task! test uxTaskGetStackHighWaterMark2\n", __FUNCTION__);
        printf("%s, In the task! test uxTaskGetStackHighWaterMark3\n", __FUNCTION__);
        printf("%s, In the task! data = %d\n", __FUNCTION__, *data);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *para)
{
    PersonInfo *personInfo = (PersonInfo *)para;
    while (1)
    {
        printf("%s, In the task! name = %s, age = %d\n", __FUNCTION__, personInfo->name, personInfo->age);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    uint16_t testCnt = 1;
    TaskInfo taskInfo1 = {0};
    TaskInfo taskInfo2 = {0};
    PersonInfo personInfo = {.name = "I am task2", .age = 18};

    taskInfo1.xResult = xTaskCreate(myTask1,
                                    "myTask1",
                                    2048,
                                    &testCnt,
                                    2,
                                    &taskInfo1.xHandle);

    taskInfo2.xResult = xTaskCreate(myTask2,
                                    "myTask2",
                                    2048,
                                    &personInfo,
                                    1,
                                    &taskInfo2.xHandle);

    if ((pdPASS != taskInfo1.xResult) || (pdPASS != taskInfo2.xResult))
    {
        printf("Create task fail!\n");
    }

    while (1)
    {
        testCnt++;
        UBaseType_t waterMarkSize = uxTaskGetStackHighWaterMark(taskInfo1.xHandle);
        printf("%s, task1 waterMarkSize = %d\n", __FUNCTION__, waterMarkSize);
        waterMarkSize = uxTaskGetStackHighWaterMark(taskInfo2.xHandle);
        printf("%s, task2 waterMarkSize = %d\n", __FUNCTION__, waterMarkSize);

        printf("%s, In the task! testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

实验结果如下:

图 3.1.5 uxTaskGetStackHighWaterMark()函数实验结果

3.2 FreeRTOS 获取和设置任务优先级

下面介绍2个函数,获取任务优先级及设置任务优先级。

函数 描述
uxTaskPriorityGet() 获取任务优先级
vTaskPrioritySet() 设置任务优先级

表 3.2.1 获取任务优先级及设置任务优先级函数

1. 函数 uxTaskPriorityGet()

此函数用于获取指定任务的任务优先级, 若使用此函数, 需在 FreeRTOSConfig.h 文件中设

置配置项 INCLUDE_uxTaskPriorityGet 为 1(默认设置为1), 此函数的函数原型如下所示:

复制代码
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);

函数 uxTaskPriorityGet()的形参描述, 如下表所示:

形参 参数类型 描述
xTask TaskHandle_t 任务句柄: * 如果传入一个​有效的任务句柄​,则查询该句柄对应任务的优先级; * 如果传入 NULL ,则表示查询​当前正在调用该函数的任务自身​的优先级 。

表 3.2.2 函数 uxTaskPriorityGet()形参相关描述

函数 uxTaskPriorityGet()的返回值, 如下表所示:

返回值 参数类型 描述
整数 UBaseType_t 返回的是任务的当前优先级数值: 在 FreeRTOS 中,​​数值越大,表示优先级越高​ ​,范围通常从 0(最低优先级,通常是空闲任务)到 configMAX_PRIORITIES - 1

表 3.2.3 函数 uxTaskPriorityGet()返回值相关描述

2. 函数 vTaskPrioritySet()

此函数用于设置指定任务的优先级, 若使用此函数, 需在 FreeRTOSConfig.h 文件中设置配

置项 INCLUDE_vTaskPrioritySet 为 1(默认为1), 此函数的函数原型如下所示:

复制代码
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority)

函数 vTaskPrioritySet()的形参描述, 如下表所示:

形参 描述
xTask 要修改优先级的任务的​​句柄​ ​。如果传入 NULL,则表示修改​​当前正在调用此函数的任务自身​​的优先级 。
uxNewPriority 目标任务设置的​​新优先级​ ​。该值会自动限制在 0(最低优先级,通常是空闲任务)到 configMAX_PRIORITIES - 1(最高优先级)的范围内。如果设置的值超过上限,系统会自动将其设置为 configMAX_PRIORITIES - 1

表 3.2.4 函数 vTaskPrioritySet()形参相关描述

函数 vTaskPrioritySet()无返回值。

重要注意事项:

  • **优先级数值与任务调度​​:**在 FreeRTOS 中,数值越大表示优先级越高。调度器总是保证处于就绪态的最高优先级任务运行;
  • **​​配置优先级范围​​:**最大可用优先级由 FreeRTOSConfig.h中的 configMAX_PRIORITIES定义。建议此值不要超过32,以节省内存和提高调度效率;
  • **中断服务程序(ISR)中不可使用​​:**vTaskPrioritySet()不能在中断服务程序中调用。若需要在ISR中修改任务优先级,应使用中断安全的 xTaskPrioritySetFromISR()函数;
  • **优先级继承机制​​:**在使用互斥信号量(Mutex)时,FreeRTOS 可能自动临时提升低优先级任务的优先级(优先级继承),以解决优先级反转问题。手动设置的优先级在互斥锁释放后会恢复。

**实验5:**我们分别创建2个任务task1和task2,task1优先级高于task2,在任务中打印相关信息,获取任务优先级并打印;然后调整task2的优先级高于task1。预期结果:未调整task2任务优先级之前,task1中的打印优先于task2,中的打印优先打印出来;调整task2任务优先级后,task2中的打印优先于task1。

**需要注意:**代码中使用xPortGetCoreID()获取当前任务运行核号,这是为了确保两个任务在同一个核上运行,保证实验可靠性。
代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct TaskInfo
{
    TaskHandle_t xHandle;
    BaseType_t xResult;
} TaskInfo;

typedef struct PersonInfo
{
    char name[30];
    uint8_t age;
} PersonInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        uint16_t coreId = xPortGetCoreID();
        printf("%s, In the task! coreId = %d, data = %d\n", __FUNCTION__, coreId, *data);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *para)
{
    PersonInfo *personInfo = (PersonInfo *)para;
    while (1)
    {
        uint16_t coreId = xPortGetCoreID();
        printf("%s, In the task! coreId = %d,  name = %s, age = %d\n", __FUNCTION__, coreId, personInfo->name, personInfo->age);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    uint16_t testCnt = 1;
    TaskInfo taskInfo1 = {0};
    TaskInfo taskInfo2 = {0};
    PersonInfo personInfo = {.name = "I am task2", .age = 18};

    taskInfo1.xResult = xTaskCreate(myTask1,
                                    "myTask1",
                                    2048,
                                    &testCnt,
                                    2,
                                    &taskInfo1.xHandle);

    taskInfo2.xResult = xTaskCreate(myTask2,
                                    "myTask2",
                                    2048,
                                    &personInfo,
                                    1,
                                    &taskInfo2.xHandle);

    if ((pdPASS != taskInfo1.xResult) || (pdPASS != taskInfo2.xResult))
    {
        printf("Create task fail!\n");
    }

    vTaskDelay(pdMS_TO_TICKS(5000));  //延时先让task1和task2按照设置优先级打印

    UBaseType_t task1Priority = uxTaskPriorityGet(taskInfo1.xHandle);
    printf("task1 priority = %d\n", task1Priority);
    UBaseType_t task2Priority = uxTaskPriorityGet(taskInfo2.xHandle);
    printf("task2 priority = %d\n", task2Priority);

    vTaskPrioritySet(taskInfo2.xHandle, 3);  //设置task2的任务优先级为3,高于task1

    task1Priority = uxTaskPriorityGet(taskInfo1.xHandle);
    printf("task1 priority = %d\n", task1Priority);
    task2Priority = uxTaskPriorityGet(taskInfo2.xHandle);
    printf("task2 priority = %d\n", task2Priority);

    while (1)
    {
        testCnt++;
        printf("%s, In the task! testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

实验结果如下:

图 3.2.1 获取及调整任务优先级函数实验结果

3.3 FreeRTOS 挂起和恢复任务

下面介绍5个关于任务挂起与恢复的函数。

| 函数 | 描述 |
| vTaskSuspend() | 挂起任务 |
| vTaskResume() | 恢复被挂起的任务 |
| vTaskSuspendAll() | 挂起任务调度器 |
| vTaskResumeAll() | 恢复任务调度器 |

xTaskResumeFromISR() 在中断中恢复被挂起的任务
1. 函数vTaskSuspend()

此函数用于挂起任务,若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskSuspend配置为1。无论优先级如何,被挂起的任务都将不再被执行,直到任

务被恢复。此函数并不支持嵌套,不论使用此函数重复挂起任务多少次,只需调用一次恢复任

务的函数,那么任务就不再被挂起。函数原型如下所示:

复制代码
void vTaskSuspend(TaskHandle_t xTaskToSuspend)

函数 vTaskSuspend()的形参描述, 如下表所示:

形参 描述
xTaskToSuspend 要挂起的任务的​​句柄​ ​(TaskHandle_t)。如果传入 NULL,则表示挂起​​当前正在调用该函数的任务自身​​。

表 3.3.1 函数 vTaskSuspend()形参相关描述

函数 vTaskSuspend()无返回值。

当调用 vTaskSuspend()后,FreeRTOS 内核会执行以下操作:

  • **从调度列表中移除​​:**将目标任务从就绪列表、阻塞列表或事件列表中移除;
  • **添加到挂起列表​​:**把任务加入到挂起任务列表(xSuspendedTaskList)中;
  • **触发调度​​:**如果被挂起的是当前正在运行的任务,则会强制进行一次任务切换(Context Switch)。

被挂起的任务将​​不再参与调度​​,无法获得CPU时间,但其所有的状态和局部变量都被保留在内存中。退出挂起状态的唯一方法是另一个任务(或中断)调用 vTaskResume()或xTaskResumeFromISR()来恢复它。

重要注意事项:

事项 说明
​​配置宏​​ 使用前必须在 FreeRTOSConfig.h中将 INCLUDE_vTaskSuspend定义为 1,否则该函数不可用。
​​挂起次数​​ 对同一任务多次调用 vTaskSuspend()挂起,只需调用一次 vTaskResume()即可恢复。
​​空闲任务​​ ​不能挂起空闲任务(Idle Task)​​,否则可能导致已删除任务的内存无法被回收,引起系统死锁或内存泄漏。
​​资源死锁​​ 若任务在挂起前持有关键资源(如互斥锁),可能导致其他需要该资源的任务无限期等待,设计时需谨慎。
​​与阻塞的区别​​ ​挂起(Suspend)​ ​是主动让任务停止调度,不依赖任何事件;​​阻塞(Block)​​是任务等待特定事件(如信号量、延时)时被动进入的状态。

表 3.3.2 函数 vTaskSuspend()使用注意事项

2. 函数 vTaskResume()

此函数用于在任务中恢复被挂起的任务, 若使用此函数, 需要在 FreeRTOSConfig.h 文件中

将宏 INCLUDE_vTaskSuspend 配置为 1。 不论一个任务被函数 vTaskSuspend()挂起多少次, 只

需要使用函数 vTakResume()恢复一次, 就可以继续运行。 函数原型如下所示:

复制代码
void vTaskResume(TaskHandle_t xTaskToResume)

函数 vTaskResume()的形参描述, 如下表所示:

形参 描述
xTaskToResume 要恢复的任务的​​句柄​ ​(TaskHandle_t)。这个句柄通常在任务创建时通过 xTaskCreate()函数的最后一个参数获取

表 3.3.3 函数 vTaskResume()形参相关描述

函数 vTaskResume()无返回值。

重要注意事项:

  • **专用于恢复挂起任务​​:**vTaskResume()只能恢复通过 vTaskSuspend()函数挂起的任务;它不能恢复因等待信号量、队列、延时等事件而阻塞的任务;
  • **非嵌套性​​:**无论调用了多少次 vTaskSuspend()来挂起一个任务,只需要调用一次vTaskResume()即可将其恢复;
  • **不能恢复当前任务​​:**逻辑上不能恢复当前正在运行的任务(即调用 vTaskResume()的任务自身),代码中通过 configASSERT( pxTaskToResume )进行校验;
  • **中断服务程序(ISR)中禁用​​:**绝对不要在中断服务程序(ISR)中调用 vTaskResume();如果需要在中断中恢复任务,必须使用其专用版本 xTaskResumeFromISR();
  • **​​与调度器挂起的区别​​:**vTaskResume()和 vTaskSuspend()用于操作单个任务的状态;而 vTaskSuspendAll()和 xTaskResumeAll()用于挂起和恢复整个调度器,会影响所有任务的调度;
  • **确保句柄有效性​​:**在恢复任务前,务必确保传入的任务句柄是有效的。

**实验6:**我们分别创建2个任务task1和task2,在任务中打印相关信息,在task2中通过传入task1的handle来挂起task1一段时间(挂起器件task1中无打印信息),然后恢复。
代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct TaskInfo
{
    TaskHandle_t xHandle;
    BaseType_t xResult;
} TaskInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        printf("%s, In the task! data = %d\n", __FUNCTION__, *data);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *para)
{
    TaskHandle_t* xHandle = (TaskHandle_t *)para;

    while (1)
    {
        printf("%s, In the task!\n", __FUNCTION__);
        vTaskDelay(pdMS_TO_TICKS(3000));
        printf("挂起myTask1...\n");
        vTaskSuspend(*xHandle);
        
        vTaskDelay(pdMS_TO_TICKS(5000));

        printf("恢复myTask1...\n");
        vTaskResume(*xHandle);
    }
}

void app_main(void)
{
    uint16_t testCnt = 1;
    TaskInfo taskInfo1 = {0};
    TaskInfo taskInfo2 = {0};

    taskInfo1.xResult = xTaskCreate(myTask1,
                                    "myTask1",
                                    2048,
                                    &testCnt,
                                    2,
                                    &taskInfo1.xHandle);

    taskInfo2.xResult = xTaskCreate(myTask2,
                                    "myTask2",
                                    2048,
                                    &taskInfo1.xHandle,
                                    1,
                                    &taskInfo2.xHandle);

    if ((pdPASS != taskInfo1.xResult) || (pdPASS != taskInfo2.xResult))
    {
        printf("Create task fail!\n");
    }

    while (1)
    {
        testCnt++;
        printf("%s, In the task! testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

实验结果如下:

图 3.2.2 获取及调整任务优先级函数实验结果

3. 函数vTaskSuspendAll()

vTaskSuspendAll()是一个用于​​挂起任务调度器​​的函数。它的核心作用是临时禁止任务切换,让当前任务能够独占 CPU 运行,而不会被打断。函数原型如下:

复制代码
void vTaskSuspendAll( void );

函数无入参和返回值。

调用 vTaskSuspendAll()后,FreeRTOS 的调度器会暂停工作。这意味着:

  • **任务切换被禁止​​:**即使有更高优先级的任务进入就绪状态,系统也不会立即进行任务切换。当前正在运行的任务将继续独占 CPU 执行权,直到调用 xTaskResumeAll()恢复调度器;
  • 中断不受影响​​: 这是一个关键特点。硬件中断仍然可以正常产生并得到响应​​,中断服务程序(ISR)会照常执行。这保证了系统对紧急事件的实时响应能力。

适用场景与优势:

vTaskSuspendAll()非常适合用于保护那些​​执行时间稍长、需要保证连续性,但又不能关闭中断​​的操作场景。

  • **​​保护共享资源(无中断访问)​​:**当多个任务需要复杂地操作同一全局数据结构(如链表、缓冲区)时,使用 vTaskSuspendAll()可以确保整个操作序列的原子性,避免在操作过程中被其他任务打断导致数据不一致。与完全关中断的临界区相比,它的开销更小,且不影响中断响应;
  • **​​执行原子性操作​​:**例如,需要连续向多个队列发送消息,或者进行复杂的硬件初始化流程,希望这一系列操作完成后才允许任务调度。

重要注意事项:

  • **严禁在中断中调用​​:**vTaskSuspendAll()和 xTaskResumeAll()只能用于任务(Task)中,绝对不能在中断服务程序(ISR)中调用;
  • 避免长时间挂起:挂起调度器会阻塞所有高优先级任务,破坏系统的实时性。因此,受保护的代码段应尽可能短小,理想情况下在微秒级完成;
  • 禁止在挂起期间调用阻塞API:在调度器挂起期间,绝对不能调用如vTaskDelay()。xQueueReceive()等可能引起任务阻塞的函数。因为调度器已停止,没有其他任务可以运行,会导致系统死锁;
  • 确保嵌套匹配:务必保证 vTaskSuspendAll()和 xTaskResumeAll()的调用次数严格匹配。如果挂起次数多于恢复次数,调度器将无法恢复,导致系统"冻僵"。

为了更清晰地理解 vTaskSuspendAll()的定位,下表将其与 FreeRTOS 中其他两种常用的保护机制进行对比。

特性 vTaskSuspendAll()(调度器挂起) vTaskSuspend()(任务挂起) taskENTER_CRITICAL()(临界区)
​作用对象​ 整个系统的任务调度器 单个指定的任务 CPU 中断
​主要影响​ 禁止任务切换 将指定任务置于挂起态 屏蔽中断(或部分中断)
​中断响应​ ✅ ​​不受影响​​,可正常响应 ✅ 不受影响 ❌ ​​被屏蔽​​,实时性受影响
​适用场景​ 保护任务间共享资源(操作时间稍长) 暂停某个特定任务的执行 保护极短的关键代码段(如操作变量)
​推荐保护时长​ 中等(微秒到毫秒级) 可长可短 极短(几个指令周期)
4. 函数vTaskResumeAll()

该函数用于​​恢复被挂起的调度器​ ​的关键函数。它与 vTaskSuspendAll()配对使用,共同控制整个系统的任务调度。函数原型如下:

复制代码
BaseType_t xTaskResumeAll( void );

vTaskResumeAll返回值 , 如下表所示:

| 返回值 | 描述 |
| pdTRUE | 表示恢复调度器后,有一个​​更高优先级的任务就绪​​,需要立即进行任务切换(上下文切换)。 |

pdFALSE 表示无需立即进行任务切换 。

表 3.3.4 函数 vTaskResumeAll()返回值描述

重要注意事项:

  • **​​嵌套调用​​:**vTaskSuspendAll()和 xTaskResumeAll()支持嵌套调用。系统内部维护一个计数器,必须保证调用多少次 vTaskSuspendAll(),就要对应调用多少次 xTaskResumeAll(),调度器才会真正恢复;
  • **禁止在中断中使用​​:**xTaskResumeAll()不能在中断服务程序(ISR)中调用;
  • **与临界区的区别​​:**挂起调度器 (vTaskSuspendAll/xTaskResumeAll) 只是禁止了任务切换,中断仍然有效。而进入临界区 (taskENTER_CRITICAL/taskEXIT_CRITICAL) 会屏蔽中断,适用于更短、更紧急的关键段保护;
  • **避免在挂起期间调用阻塞API​​:**在调度器挂起期间,严禁调用如 vTaskDelay()、xQueueSend()等可能导致任务阻塞的函数,否则可能引起系统死锁。

**实验6:**我们分别创建2个任务task1和task2,在任务中打印相关信息,在task2中调用vTaskSuspendAll(),然后延时(正常此处执行受保护的代码,如修改共享数据灯),然后调用xTaskResumeAll()恢复。
代码如下:

复制代码
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"

typedef struct TaskInfo
{
    TaskHandle_t xHandle;
    BaseType_t xResult;
} TaskInfo;

void myTask1(void *para)
{
    uint16_t *data = (uint16_t *)para;
    while (1)
    {
        printf("%s, In the task! data = %d\n", __FUNCTION__, *data);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void myTask2(void *para)
{
    uint32_t cnt = 0;
    while (1)
    {
        printf("%s, In the task!\n", __FUNCTION__);
        printf("挂起整个系统的任务调度器...\n");
        vTaskSuspendAll();

        //执行需要保护的、非常快速的代码

        printf("恢复整个系统的任务调度器...\n");
        xTaskResumeAll();
    }
}

void app_main(void)
{
    uint16_t testCnt = 1;
    TaskInfo taskInfo1 = {0};
    TaskInfo taskInfo2 = {0};

    taskInfo1.xResult = xTaskCreate(myTask1,
                                    "myTask1",
                                    2048,
                                    &testCnt,
                                    2,
                                    &taskInfo1.xHandle);

    taskInfo2.xResult = xTaskCreate(myTask2,
                                    "myTask2",
                                    2048,
                                    NULL,
                                    1,
                                    &taskInfo2.xHandle);

    if ((pdPASS != taskInfo1.xResult) || (pdPASS != taskInfo2.xResult))
    {
        printf("Create task fail!\n");
    }

    while (1)
    {
        testCnt++;
        printf("%s, In the task! testCnt = %d\n", __FUNCTION__, testCnt);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
5. 函数 xTaskResumeFromISR()

此函数用于在中断服务程序中恢复一个之前被 vTaskSuspend()函数挂起的任务,使其重新进入就绪状态,能够被调度器调度运行。若使用此函数, 需要在 FreeRTOSConfig.h 文件中

将宏 INCLUDE_xTaskResumeFromISR 配置为 1。 不论一个任务被函数 vTaskSuspend()挂起多次, 只需要使用函数 vTakResumeFromISR()恢复一次, 就可以继续运行。 函数原型如下所示:

复制代码
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

函数 xTaskResumeFromISR()的形参描述, 如下表所示:

形参 描述
xTaskToResume 指定要恢复的​​任务句柄​​(TaskHandle_t)。这个句柄在任务创建时获取。

表 3.3.5 函数 xTaskResumeFromISR()形参相关描述

函数 xTaskResumeFromISR()的返回值, 如下表所示:

返回值 描述
pdTRUE 表示被恢复的任务优先级​​等于或高于​ ​被中断打断的任务优先级。这意味着在退出中断后​​需要进行一次任务切换​​,以便调度器能够立即运行更高优先级的任务。
pdFALSE 表示被恢复的任务优先级​​低于​ ​当前被中断的任务优先级。这意味着在退出中断后​​不需要立即进行任务切换​​。

表 3.3.6 函数 xTaskResumeFromISR()返回值相关描述

相关推荐
.NET修仙日记3 小时前
C#/.NET 微服务架构:从入门到精通的完整学习路线
微服务·c#·.net·.net core·分布式架构·技术进阶
友友马4 小时前
『 QT 』QT控件属性全解析 (二)
开发语言·数据库·qt
逐步前行8 小时前
C标准库--C99--布尔型<stdbool.h>
c语言·开发语言
QX_hao8 小时前
【Go】--闭包
开发语言·golang
wanglong37139 小时前
STM32单片机PWM驱动无源蜂鸣器模块C语言程序
stm32·单片机·1024程序员节
塔能物联运维11 小时前
物联网固件安全更新中的动态密钥绑定与验证机制
物联网
清风66666611 小时前
基于单片机的故障检测自动保护智能防夹自动门设计及LCD状态显示系统
单片机·毕业设计·课程设计·1024程序员节·期末大作业
林月明11 小时前
【VBA】自动设置excel目标列的左邻列格式
开发语言·excel·vba·格式
喜欢吃燃面12 小时前
数据结构算法题:list
开发语言·c++·学习·算法·1024程序员节