任务的挂起和恢复

任务的挂起和恢复

官方文档参考 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]] 任务的状态: 执行

官方文档参考

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]]


任务的状态:

  1. 执行态
    • 当任务实际执行时,被称为执行态,任务当前正在使用处理器。如果运行FreeRTOS的处理器只有一个内核,那么在任何给定时间内都只能有一个任务处于执行态。
  2. 就绪态
    • 就绪态指那些任务能够执行(他们不处于阻塞态或挂起态),但是目前没有执行当前任务,因为同等或更高优先级的不同任务已经处于运行状态。
  3. 阻塞态
    • 如果当前任务正在等待时间或者外部事件,则该任务被认为是阻塞态。例如,如果一个任务调用vTaskDelay(),它将被置于阻塞态,直到延迟结束。任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量事件。处于阻塞态的任务通常有一个”超时“周期,超时后任务将被超时,并被解除阻塞,即使该任务等待的事件没有发生
    • “阻塞”状态下的任务不使用处理器时间,不能被选择进入运行态
  4. 挂起态 [[6. 任务的挂起和恢复#^9b7449]]
    • 与“阻塞”状态下的任务一样,“挂起”状态下的任务不能被选择进入运行态,但处于挂起状态的任务没有超时。相反,任务只有在分别通过vTaskSuspend()xTaskResume()API调用明确命令时,才会进入或退出挂起状态。
      6. 任务的挂起和恢复-20240612185053698.webp

挂起态

挂起操作

^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
}

示例输出

6. 任务的挂起和恢复-20240612191200590.webp

输出结果表明,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");
}

示例输出

6. 任务的挂起和恢复-20240612192109058.webp

可以看到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");
}

示例输出

6. 任务的挂起和恢复-20240612193022814.webp

两个任务被创建后,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()使用注意事项:

  1. 两个函数需搭配使用,并且两个函数中间不能使用会引起上下文切换的函数,例如printf,因为在 ESP32 的 FreeRTOS 中,printf内部会调用锁或其他 RTOS 服务,这在调度器暂停时是不允许的。
  2. 在多核情况下,可以使用 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);
}

示例输出

6. 任务的挂起和恢复-20240612233709453.webp

实际运行测试中,task1 begin会先被打印,然后等待一会儿,task2 is runningtask1 end.几乎一起被打印出来。
这是因为在代码中,xTaskResumeAll()执行之后,task2会立即执行,之后再执行printf("task1 end!\n");
以上,vTaskSuspendAll函数和xTaskResumeAll函数测试成功。

Comment