06-任务挂起与恢复.md 7.5 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第5章 任务挂起与恢复" author: "尚硅谷研究院" date: 2026-07-17

created: 2026-07-17

任务挂起与恢复

用生活理解:挂起任务就像把员工暂时调离岗位(保留工位和编制),不需要时让他歇着;恢复就像把他叫回来上班。而挂起调度器则像是整个公司暂停运转,所有员工原地待命。

Concept

Basic concept

挂起(Suspend) 将任务置于挂起态(Suspended State):

  • 任务暂停运行,调度器不再分配 CPU 时间给该任务
  • 任务在挂起态不会就绪,需要通过 vTaskResume() 手动恢复
  • 与阻塞态不同,挂起不存在超时机制,只能由外部显式恢复

Working principle

FreeRTOS 提供两套挂起/恢复机制:

1. 单任务挂起/恢复

API 描述
vTaskSuspend(TaskHandle_t xTaskToSuspend) 挂起指定任务
vTaskResume(TaskHandle_t xTaskToResume) 恢复指定任务
xTaskResumeFromISR(TaskHandle_t xTaskToResume) 从中断中恢复任务(中断安全版本)

2. 调度器级挂起/恢复

API 描述
vTaskSuspendAll() 挂起整个调度器,所有任务停止切换
xTaskResumeAll() 恢复调度器,返回 pdTRUEpdFALSE

三种状态对比

操作 挂起 阻塞 删除
任务存在
可自动恢复 ✅(超时后)
资源释放
恢复方式 手动 resume 超时/事件触发 需重新创建

Experiment

Requirements

  • 创建 3 个任务,task1/task2 各自翻转到 LED,task3 检测按键:
    • KEY1 → vTaskSuspend(task1) 挂起 task1
    • KEY2 → vTaskResume(task1) 恢复 task1
    • KEY3 → vTaskSuspendAll() 挂起调度器
    • KEY4 → xTaskResumeAll() 恢复调度器
    • 每次按键后调用 vTaskList() 打印所有任务状态

Code Implementation

File: 03_任务挂起与恢复/Core/Src/freertos_demo.c

#include "freertos_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"

/* 起始任务配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/* 任务3配置 */
#define TASK3_STACK 128
#define TASK3_PRIORITY 4
TaskHandle_t task3_handle;
void task3(void *pvParameters);

void freertos_start(void)
{
    /* 动态创建起始任务 */
    xTaskCreate((TaskFunction_t)start_task,
                (char *)"start_task",
                (configSTACK_DEPTH_TYPE)START_TASK_STACK,
                (void *)NULL,
                (UBaseType_t)START_TASK_PRIORITY,
                (TaskHandle_t *)&start_task_handle);

    /* 启动调度器 */
    vTaskStartScheduler();
}

void start_task(void *pvParameters)
{
    /* 进入临界区,防止创建过程被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1, (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK, (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2, (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK, (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handle);
    xTaskCreate((TaskFunction_t)task3, (char *)"task3",
                (configSTACK_DEPTH_TYPE)TASK3_STACK, (void *)NULL,
                (UBaseType_t)TASK3_PRIORITY, (TaskHandle_t *)&task3_handle);

    /* 删除起始任务自身 */
    vTaskDelete(NULL);

    taskEXIT_CRITICAL();
}

void task1(void *pvParameters)
{
    while (1)
    {
        printf("task1正在运行...\r\n");
        LED_Toggle(LED1_Pin);
        vTaskDelay(500);     // 阻塞延时500ms,让出CPU
    }
}

void task2(void *pvParameters)
{
    while (1)
    {
        printf("task2正在运行...\r\n");
        LED_Toggle(LED2_Pin);
        vTaskDelay(500);
    }
}

char task_info[500];  // 用于保存vTaskList输出的任务信息字符串

/**
 * @description: 任务3——按键控制挂起/恢复,并打印任务状态
 *               KEY1=挂起task1  KEY2=恢复task1
 *               KEY3=挂起调度器  KEY4=恢复调度器
 */
void task3(void *pvParameters)
{
    uint8_t key = 0;
    while (1)
    {
        printf("task3正在运行...\r\n");
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* KEY1按下:挂起task1——task1将停止运行 */
            printf(">>>>挂起task1....\r\n");
            vTaskSuspend(task1_handle);
        }
        else if (key == KEY2_PRESS)
        {
            /* KEY2按下:恢复task1——task1重新进入就绪态 */
            printf(">>>>恢复task1....\r\n");
            vTaskResume(task1_handle);
        }
        else if (key == KEY3_PRESS)
        {
            /* KEY3按下:挂起调度器——所有任务停止切换 */
            printf("<<<<挂起调度器....\r\n");
            vTaskSuspendAll();
        }
        else if (key == KEY4_PRESS)
        {
            /* KEY4按下:恢复调度器——任务切换恢复 */
            printf("<<<<恢复调度器....\r\n");
            xTaskResumeAll();
        }

        /* 打印当前所有任务的状态信息 */
        vTaskList(task_info);
        printf("%s\r\n", task_info);

        vTaskDelay(500);
    }
}

Key Config

FreeRTOSConfig.h(启用任务状态跟踪功能):

// 任务挂起/恢复API必须包含
#define INCLUDE_vTaskSuspend         1
#define INCLUDE_xResumeFromISR      1

// 任务列表查询支持
#define configUSE_TRACE_FACILITY               1
#define configUSE_STATS_FORMATTING_FUNCTIONS   1

Core API Reference Table

Function Parameters Return Description
vTaskSuspend xTaskToSuspend void 挂起指定任务,使其进入挂起态
vTaskResume xTaskToResume void 恢复指定任务,使任务就绪
xTaskResumeFromISR xTaskToResume BaseType_t 从中断中恢复任务,返回 pdTRUE 表示需要任务切换
vTaskSuspendAll void 挂起调度器,禁止任务切换
xTaskResumeAll BaseType_t 恢复调度器,返回 pdTRUE 表示有挂起的切换
vTaskList pcWriteBuffer void 将任务状态写入字符串缓冲区

Common Issues & Pitfalls

  1. 挂起调度器时不得调用 FreeRTOS APIvTaskSuspendAll() 之后调用 vTaskDelay()xQueueSend() 等会导致断言失败。只在临界代码段中使用。
  2. 挂起任务与调度器挂起的区别vTaskSuspend() 只针对单个任务,其余任务正常运行;vTaskSuspendAll() 影响所有任务,常用于保护短代码段。
  3. 恢复已挂起的任务无效:多次调用 vTaskSuspend() 挂起同一个任务,只需一次 vTaskResume() 即可恢复。
  4. vTaskList() 需要缓冲区足够大:每个任务约占用 40 字节,根据任务数量计算缓冲区大小。
  5. 从中断中恢复任务:必须在中断中使用 xTaskResumeFromISR() 而非 vTaskResume(),并根据返回值判断是否需要 portYIELD_FROM_ISR()