第三章 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()返回值相关描述

相关推荐
专业开发者5 小时前
物联网传感器:破解设备维护难题
物联网
半桶水专家6 小时前
go语言中的结构体嵌入详解
开发语言·后端·golang
广州灵眸科技有限公司6 小时前
瑞芯微(EASY EAI)RV1126B CAN使用
linux·网络·单片机·嵌入式硬件
SmartRadio6 小时前
CH584M vs nRF52840 vs 主流BLE SoC全面对比
单片机·嵌入式硬件·mcu·物联网·开源·硬件工程
在屏幕前出油6 小时前
二、Python面向对象编程基础——理解self
开发语言·python
阿方索7 小时前
python文件与数据格式化
开发语言·python
cici158747 小时前
C#实现三菱PLC通信
java·网络·c#
专业开发者8 小时前
经 Nordic 实测:蓝牙长距离传输
网络·物联网
weixin_440730508 小时前
java结构语句学习
java·开发语言·学习
JIngJaneIL8 小时前
基于java+ vue医院管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot