在 FreeRTOS 中,任务挂起(Suspend)与解挂(Resume) 是一种手动控制任务是否参与调度的机制。它可以临时“冻结”某个任务,使其不再被调度器运行,直到被恢复。
任务挂起与解挂的核心 API
函数 | 说明 |
---|---|
vTaskSuspend() |
挂起指定任务 |
vTaskResume() |
解挂(恢复)指定任务 |
vTaskResumeFromISR() |
从中断服务程序(ISR)中恢复任务 |
vTaskSuspend()
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
//作用:将指定任务挂起,使其不再参与调度。
//参数:
//`xTaskToSuspend`:任务句柄。若为 `NULL`,则挂起当前任务。
//注意:挂起任务后,直到调用 `vTaskResume()` 才会恢复运行。
vTaskResume()
void vTaskResume(TaskHandle_t xTaskToResume);
//作用:恢复被挂起的任务,使其重新参与调度。
//参数:
//xTaskToResume:要解挂的任务句柄。
//注意:若任务没有被挂起,调用此函数无效。
vTaskResumeFromISR()
BaseType_t vTaskResumeFromISR(TaskHandle_t xTaskToResume);
//作用:从 中断服务函数(ISR) 中恢复任务。
//返回值:若恢复的任务优先级更高,返回 pdTRUE,需在中断末尾调用 portYIELD_FROM_ISR()。
挂起与解挂的运行机制
首先,我们必须搞清楚在FreeRTOS中,任务状态是一个重要的概念,它决定了每个任务当前所处的状态以及调度器如何管理任务的执行。
FreeRTOS定义了多种任务状态,每个任务在任何时间点都处于以下状态之一:
1. 运行中 (Running):
任务正在CPU上执行。系统中同一时刻只有一个任务可以处于运行状态(在单核系统中)。
2. 就绪 (Ready):
任务已经准备好运行,等待调度器分配CPU时间。就绪状态的任务不处于任何阻塞状态并且满足优先级条件。
3. 阻塞 (Blocked):
任务因为等待某个事件而无法继续执行。常见的阻塞原因包括等待信号量、队列消息、事件组或超时事件。
阻塞任务在事件发生或超时到达时会被转移到就绪状态。
4. 挂起 (Suspended):
任务被挂起,除非被显式唤醒(通过调用 vTaskResume() 或 vTaskResumeFromISR()),否则不会被调度执行。
挂起状态与阻塞状态的区别在于挂起状态任务不会自动因为事件发生而恢复。
5. 删除 (Deleted):
任务已被删除,资源等待被系统回收。被删除的任务不再被调度执行。
调用 vTaskDelete()
会将任务置于删除状态。
说明:
- 挂起任务后,它将被移出就绪列表。
- 被挂起的任务不会运行,即使它的优先级最高。
- 解挂后,任务会重新加入就绪列表,调度器判断是否立即切换。
挂起任务只是暂停运行,不会释放堆栈或删除任务:❌ 不会释放资源。
状态转换的过程:
[就绪态]
↑
│
vTaskResume()
│
┌───────┘
[挂起态] ←───── vTaskSuspend()
实际代码示例
示例一:任务的挂起与解挂:
代码说明:这段代码是基于 STM32F10x 标准外设库 和 FreeRTOS 的一个多任务模版,从FreeRTOS的创建任务、任务挂起/恢复、以及对 GPIO(LED 和按键)的控制。
函数说明:
✅ 系统初始化 初始化串口、GPIO、按键等外设
✅ 创建任务 创建 start_task 用于初始化系统任务
✅ 动态创建任务 start_task 中动态创建 led0_task 和 led1_task
✅ 任务控制 led0_task 通过按键挂起/恢复 led1_task
✅ LED 控制 led1_task 控制 GPIOC.7 周期性闪烁
/**
******************************************************************************
* @file Project/STM32F10x_StdPeriph_Template/main.c
* @author MCD Application Team
* @version V3.5.0
* @date 08-April-2011
* @brief Main program body
******************************************************************************
* @attention
*
* THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
* WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
* TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
* DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
* FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
* CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* <h2><center>© COPYRIGHT 2011 STMicroelectronics</center></h2>
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdio.h>
#include <stdbool.h>
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
#include "gpio.h"
//------------------------- start_task --------------------------------------------//
void start_task(void *pvParameters); //任务函数入口
TaskHandle_t StartTask_Handler; //任务句柄 _任务身份证_每个任务都有独立的任务
#define START_STK_SIZE 64 //任务堆栈大小
#define START_TASK_PRO 1 //任务优先级
//-----------------------------------------------------------------------
void led0_task(void *pvParameters); //任务函数入口
TaskHandle_t LED0_Task_Handler; //任务句柄
//-----------------------------------------------------------------------
void led1_task(void *pvParameters); //任务函数入口
TaskHandle_t LED1_Task_Handler; //任务句柄
int main(void)
{
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); //RTOS需要将中断优先级分组分配到第四组
usart_init(115200); // 初始化串口
MX_GPIO_Init(); // 初始化 GPIO
MX_Key_Init(); // 初始化按键
printf("Create Task! \r\n");
//------------------------- start_task --------------------------------------------//
// 创建启动任务
xTaskCreate((TaskFunction_t) start_task, //任务函数入口
(const char * ) "start_task", //任务函数名称
(uint16_t ) START_STK_SIZE, //任务堆栈大小
(void * ) NULL, //任务参数入口
(UBaseType_t ) START_TASK_PRO, //任务优先级
(TaskHandle_t *) &StartTask_Handler ); //任务句柄
vTaskStartScheduler(); // 启动调度器(永不返回)
while(1) // 不会执行到这里
{
}
}
// -------------------------- 启动任务 start_task --------------------------
void start_task(void *pvParameters) //任务函数入口
{
printf("start Task Run! \r\n");
taskENTER_CRITICAL(); //进入临界区,因为创建的任务不能被打断,所以开启临界区之后,中断会被屏蔽
//------------------------- 创建 LED0 任务 --------------------------------------------//
xTaskCreate((TaskFunction_t) led0_task, //任务函数入口
(const char * ) "led0_task", //任务函数名称
(uint16_t ) 128, //任务堆栈大小
(void * ) NULL, //任务参数入口
(UBaseType_t ) 2, //任务优先级
(TaskHandle_t *) &LED0_Task_Handler ); //任务句柄
//------------------------- 创建 LED1 任务 --------------------------------------------//
xTaskCreate((TaskFunction_t) led1_task, //任务函数入口
(const char * ) "led1_task", //任务函数名称
(uint16_t ) 128, //任务堆栈大小
(void * ) NULL, //任务参数入口
(UBaseType_t ) 3, //任务优先级
(TaskHandle_t *) &LED1_Task_Handler ); //任务句柄
vTaskDelete(StartTask_Handler); //删除start_task 任务句柄
taskEXIT_CRITICAL(); //退出临界区
//临界区保护:防止在任务创建期间被调度打断。
//任务自删除:start_task 完成任务后自我销毁,节省资源。
}
void led0_task(void *pvParameters)
{
printf("led0_task Run! \r\n");
while(1)
{
//按键PA0按下
if(GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_0 ) == Bit_RESET)
{
vTaskSuspend(LED1_Task_Handler); //将 LED1_Task 挂起
printf("led1_task is Suspend! \r\n");
}
if(GPIO_ReadInputDataBit( GPIOC, GPIO_Pin_1 ) == Bit_RESET)
{
vTaskResume(LED1_Task_Handler); //将 LED1_Task 解挂
printf("led1_task is Resume! \r\n");
}
vTaskDelay(10); // 延迟 10 tick,避免按键抖动
}
}
void led1_task(void *pvParameters)
{
printf("led1_task Run! \r\n");
while(1)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_RESET); // 开灯
vTaskDelay(500);
GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_SET); // 关灯
vTaskDelay(500);
vTaskDelay(10);
}
//控制 LED(GPIOC.7)每 0.5 秒闪烁一次
//被 led0_task 动态挂起与恢复
}
/*
//这部分注释是潜在的 led_blue_task:说明你可以在任务运行时继续扩展更多功能,如蓝灯控制等等,
while(1) //锁死当前线程,但并不影响其他线程的运行
{
for(int i = 0;i<5;i++)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_RESET); //绿灯开启
vTaskDelay(500);
GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_SET); //绿灯关闭
vTaskDelay(500);
printf("start_task Run Count; %d \r\n", i);
}
vTaskDelay(10);
taskENTER_CRITICAL(); //进入临界区,因为创建的任务不能被打断,所以开启临界区之后,中断会被屏蔽
//------------------------- led_B_task --------------------------------------------//
//初始化函数
xTaskCreate((TaskFunction_t) led_blue_task, //任务函数入口
(const char * ) "led_blue_task", //任务函数名称
(uint16_t ) 32, //任务堆栈大小
(void * ) NULL, //任务参数入口
(UBaseType_t ) 2, //任务优先级_值越大优先级越高
(TaskHandle_t *) LED_BLUE_TASK_Handler ); //蓝灯任务句柄
vTaskDelete(StartTask_Handler); //删除start_task 任务句柄
taskEXIT_CRITICAL(); //退出临界区
}
*/
/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
示例二:挂起和恢复其他任务:
TaskHandle_t xTaskHandle;
void vControlledTask(void *pvParameters)
{
for (;;)
{
printf("任务运行中...\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vControllerTask(void *pvParameters)
{
// 挂起任务
vTaskSuspend(xTaskHandle);
vTaskDelay(pdMS_TO_TICKS(5000));
// 解挂任务
vTaskResume(xTaskHandle);
// 控制任务自身结束
vTaskDelete(NULL);
}
void main_app()
{
xTaskCreate(vControlledTask, "TaskA", 128, NULL, 2, &xTaskHandle);
xTaskCreate(vControllerTask, "Controller", 128, NULL, 3, NULL);
vTaskStartScheduler();
}
示例三:任务自挂起
⚠️ 注意:如果任务挂起自己,必须由其他任务或中断来恢复它!自己是无法为自己解挂的。
void vSelfSuspendingTask(void *pvParameters)
{
for (;;)
{
printf("执行一次后挂起...\n");
// 挂起自己
vTaskSuspend(NULL);
}
}
示例四:中断中恢复任务
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskResumeFromISR(xTaskHandle); // 恢复高优先级任务
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 若有必要切换上下文
}
FreeRTOS任务的挂起与解挂可用于控制执行时机,比如等待外部事件、同步信号等;当被挂起的任务即使有更高优先级也不会抢占;这可以与中断配合使用, 能够在中断中恢复任务(vTaskResumeFromISR()
)。
vTaskSuspend()
可将任务挂起,暂停其调度执行,vTaskResume()
或 vTaskResumeFromISR()
可将其恢复。这种机制适合对任务执行时机进行精确控制,但在多数场景下使用信号量/事件组更优雅。
以上,便是 FreeRTOS任务的挂起与解挂。
以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!