--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第5章 任务挂起与恢复" author: "尚硅谷研究院" date: 2026-07-17 created: 2026-07-17 --- # 任务挂起与恢复 > **用生活理解**:挂起任务就像把员工暂时调离岗位(保留工位和编制),不需要时让他歇着;恢复就像把他叫回来上班。而挂起调度器则像是整个公司暂停运转,所有员工原地待命。 ## 概念介绍 ### 基本概念 **挂起(Suspend)** 将任务置于挂起态(Suspended State): - 任务暂停运行,调度器不再分配 CPU 时间给该任务 - 任务在挂起态不会就绪,需要通过 `vTaskResume()` 手动恢复 - 与阻塞态不同,挂起不存在超时机制,只能由外部显式恢复 ### 工作原理 FreeRTOS 提供两套挂起/恢复机制: **1. 单任务挂起/恢复** | API | 描述 | |-----|------| | `vTaskSuspend(TaskHandle_t xTaskToSuspend)` | 挂起指定任务 | | `vTaskResume(TaskHandle_t xTaskToResume)` | 恢复指定任务 | | `xTaskResumeFromISR(TaskHandle_t xTaskToResume)` | 从中断中恢复任务(中断安全版本) | **2. 调度器级挂起/恢复** | API | 描述 | |-----|------| | `vTaskSuspendAll()` | 挂起整个调度器,所有任务停止切换 | | `xTaskResumeAll()` | 恢复调度器,返回 `pdTRUE` 或 `pdFALSE` | ### 三种状态对比 | 操作 | 挂起 | 阻塞 | 删除 | |------|------|------|------| | 任务存在 | ✅ | ✅ | ❌ | | 可自动恢复 | ❌ | ✅(超时后) | ❌ | | 资源释放 | ❌ | ❌ | ✅ | | 恢复方式 | 手动 resume | 超时/事件触发 | 需重新创建 | ## 实验 ### 需求描述 - 创建 3 个任务,task1/task2 各自翻转到 LED,task3 检测按键: - KEY1 → `vTaskSuspend(task1)` 挂起 task1 - KEY2 → `vTaskResume(task1)` 恢复 task1 - KEY3 → `vTaskSuspendAll()` 挂起调度器 - KEY4 → `xTaskResumeAll()` 恢复调度器 - 每次按键后调用 `vTaskList()` 打印所有任务状态 ### 代码实现 **代码来源:**03_代码/03_任务挂起与恢复/Core/Src/freertos_demo.c ```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); } } ``` ### 关键配置 **FreeRTOSConfig.h**(启用任务状态跟踪功能): ```c // 任务挂起/恢复API必须包含 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_xResumeFromISR 1 // 任务列表查询支持 #define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 ``` ## 核心函数速查表 | 函数 | 参数 | 返回值 | 功能简述 | |----------|-----------|--------|-------------| | `vTaskSuspend` | xTaskToSuspend | void | 挂起指定任务,使其进入挂起态 | | `vTaskResume` | xTaskToResume | void | 恢复指定任务,使任务就绪 | | `xTaskResumeFromISR` | xTaskToResume | BaseType_t | 从中断中恢复任务,返回 pdTRUE 表示需要任务切换 | | `vTaskSuspendAll` | 无 | void | 挂起调度器,禁止任务切换 | | `xTaskResumeAll` | 无 | BaseType_t | 恢复调度器,返回 pdTRUE 表示有挂起的切换 | | `vTaskList` | pcWriteBuffer | void | 将任务状态写入字符串缓冲区 | ## 常见问题与避坑 1. **挂起调度器时不得调用 FreeRTOS API**:`vTaskSuspendAll()` 之后调用 `vTaskDelay()`、`xQueueSend()` 等会导致断言失败。只在临界代码段中使用。 2. **挂起任务与调度器挂起的区别**:`vTaskSuspend()` 只针对单个任务,其余任务正常运行;`vTaskSuspendAll()` 影响所有任务,常用于保护短代码段。 3. **恢复已挂起的任务无效**:多次调用 `vTaskSuspend()` 挂起同一个任务,只需一次 `vTaskResume()` 即可恢复。 4. **vTaskList() 需要缓冲区足够大**:每个任务约占用 40 字节,根据任务数量计算缓冲区大小。 5. **从中断中恢复任务**:必须在中断中使用 `xTaskResumeFromISR()` 而非 `vTaskResume()`,并根据返回值判断是否需要 `portYIELD_FROM_ISR()`。