官方文档参考
2.33 vTaskSuspend()
[[FreeRTOS_Reference_Manual_V10.0.0.pdf#page=151&selection=2,0,4,14|FreeRTOS_Reference_Manual_V10.0.0, page 151]]
任务的状态:
- 执行态
- 当任务实际执行时,被称为执行态,任务当前正在使用处理器。如果运行FreeRTOS的处理器只有一个内核,那么在任何给定时间内都只能有一个任务处于执行态。
- 就绪态
- 就绪态指那些任务能够执行(他们不处于阻塞态或挂起态),但是目前没有执行当前任务,因为同等或更高优先级的不同任务已经处于运行状态。
- 阻塞态
- 如果当前任务正在等待时间或者外部事件,则该任务被认为是阻塞态。例如,如果一个任务调用
vTaskDelay()
,它将被置于阻塞态,直到延迟结束。任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量事件。处于阻塞态的任务通常有一个”超时“周期,超时后任务将被超时,并被解除阻塞,即使该任务等待的事件没有发生。 - “阻塞”状态下的任务不使用处理器时间,不能被选择进入运行态。
- 如果当前任务正在等待时间或者外部事件,则该任务被认为是阻塞态。例如,如果一个任务调用
- 挂起态 [[6. 任务的挂起和恢复#^9b7449]]
- 与“阻塞”状态下的任务一样,“挂起”状态下的任务不能被选择进入运行态,但处于挂起状态的任务没有超时。相反,任务只有在分别通过
vTaskSuspend()
和xTaskResume()
API调用明确命令时,才会进入或退出挂起状态。
- 与“阻塞”状态下的任务一样,“挂起”状态下的任务不能被选择进入运行态,但处于挂起状态的任务没有超时。相反,任务只有在分别通过
挂起态
挂起操作
^9b7449
vTaskSuspend()
#include "FreeRTOS.h"
#include "task.h"
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
必须将
INCLUDE_vTaskSuspend
定义为1
才能使用此函数。
此函数的作用是暂停任意任务,无论优先级如何,任务被暂停后将永远无法获取任何微控制器处理时间。
对vTaskSuspend()
的调用不会累计次数,若多次使用vTaskSuspend()
将任务挂起,也仅需一次vTaskResume()
函数即可将任务恢复。
参数:
xTaskToSuspend:
被挂起的任务句柄。传递空句柄将导致调用任务被暂停。
在双核
ESP32
处理器中,需要勾选menuconfig
中的Component config -> FreeRTOS -> Kernel -> Run FreeRTOS only on first core
,设置只在第一个核心上运行FreeRTOS,才可以使挂起示例挂起成功。
示例代码
#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"
void myTask1(void *pvPara)
{
int delay = 5;
while (delay--) {
printf("Task1\n");
vTaskDelay(200 / portTICK_PERIOD_MS);
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void myTask2(void *pvPara)
{
int delay = 5;
while (delay--) {
printf("Task2\n");
vTaskDelay(200 / portTICK_PERIOD_MS);
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void app_main(void)
{
printf("Hello world! It's ESP32!\n");
TaskHandle_t myHandle1 = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask1, "myTask1", 2048, NULL, 1, &myHandle1);
xTaskCreate(myTask2, "myTask2", 2048, NULL, 2, &myHandle2);
vTaskSuspend(myHandle1); // 设置挂起 task1
}
示例输出
输出结果表明,task1在创建之后运行了一瞬间(即创建task2所用的时间),然后就被挂起,直到主函数返回。
恢复操作
vTaskResume()
#include "FreeRTOS.h"
#include "task.h"
void vTaskResume( TaskHandle_t xTaskToResume );
必须将
INCLUDE_vTaskSuspend
定义为1
才能使用此函数。
函数功能:恢复已挂起的任务。
由一次或多次调用vTaskSuspend()
而挂起的任务可以通过单词调用vTaskResume()
来恢复任务。
参数:
xTaskToResume:
要恢复的任务句柄。
示例代码
#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"
void myTask1(void *pvPara)
{
int delay = 5;
while (delay--) {
printf("Task1\n");
vTaskDelay(200 / portTICK_PERIOD_MS);
}
// vTaskDelay(5000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void myTask2(void *pvPara)
{
int delay = 5;
while (delay--) {
printf("Task2\n");
vTaskDelay(200 / portTICK_PERIOD_MS);
}
// vTaskDelay(5000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void app_main(void)
{
printf("Hello world! It's ESP32!\n");
TaskHandle_t myHandle1 = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask1, "myTask1", 2048, NULL, 1, &myHandle1);
xTaskCreate(myTask2, "myTask2", 2048, NULL, 2, &myHandle2);
vTaskSuspend(myHandle1); // 挂起 task1
printf("task1 suspended!\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskResume(myHandle1); // 恢复 task1
printf("task1 resumed!\n");
}
示例输出
可以看到task1被挂起后,等待1s的时间,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"
void myTask1(void *pvPara)
{
while (true) {
printf("Task1\n");
vTaskDelay(200 / portTICK_PERIOD_MS);
vTaskSuspend(NULL); // 将自己挂起
}
vTaskDelete(NULL);
}
void myTask2(void *pvPara)
{
while (true) {
printf("Task2\n");
vTaskDelay(200 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
void app_main(void)
{
printf("Hello world! It's ESP32!\n");
TaskHandle_t myHandle1 = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask1, "myTask1", 2048, NULL, 1, &myHandle1);
xTaskCreate(myTask2, "myTask2", 2048, NULL, 2, &myHandle2);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskResume(myHandle1); // 恢复 task1
printf("task1 resumed!\n");
}
示例输出
两个任务被创建后,task1被自己挂起后,被app_main()
恢复,然后又把自己挂起了。
由此可以知道,自我挂起,当外部将任务恢复后,只能执行一次,然后又被自己挂起
骚操作:
- 利用挂起自身的特点,可以控制某些程序只运行一次,同样也可以控制任务只挂起一次。
挂起所有 & 恢复所有任务
vTaskSuspendAll()
#include "FreeRTOS.h"
#include "task.h"
void vTaskSuspendAll( void );
函数作用:
- 挂起调度器,任务不进行切换,只运行当前任务。
特殊要求:
- 此函数被调用时,其他FreeRTOS的API函数不应该被调用。
- 注意看门狗,当一个任务执行太长时间,如果没有喂狗,则任务会被重启。
xTaskResumeAll()
#include "FreeRTOS.h"
#include "task.h"
void xTaskResumeAll( void );
函数作用:
- 恢复调度器,恢复执行所有挂起之前的任务。
vTaskSuspend()
和xTaskResumeAll()
使用注意事项:
- 两个函数需搭配使用,并且两个函数中间不能使用会引起上下文切换的函数,例如
printf
,因为在 ESP32 的 FreeRTOS 中,printf
内部会调用锁或其他 RTOS 服务,这在调度器暂停时是不允许的。- 在多核情况下,可以使用 FreeRTOS 提供的临界区。临界区只会屏蔽中断,不会完全停止调度器,因此可以安全地调用
printf
函数。
代码示例
#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"
void task1(void *pvPara)
{
while (true) {
printf("task1 begin!\n");
vTaskSuspendAll();
// 调度器暂停时,不允许调用影响 FreeRTOS 断言的函数,否则会断言失败
for (int i = 9999; i > 0; i--) {
for (int j = 3000; j > 0; j--) {
}
}
xTaskResumeAll();
printf("task1 end.\n");
}
}
void task2(void *pvPara)
{
while (true) {
printf("task2 is running...\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
xTaskCreate(task1, "task1", 2048, NULL, 1, NULL);
xTaskCreate(task2, "task2", 2048, NULL, 2, NULL);
}
示例输出
实际运行测试中,task1 begin
会先被打印,然后等待一会儿,task2 is running
和task1 end.
几乎一起被打印出来。
这是因为在代码中,xTaskResumeAll()
执行之后,task2
会立即执行,之后再执行printf("task1 end!\n");
。
以上,vTaskSuspendAll函数和xTaskResumeAll函数
测试成功。