RTOS 中的任务调度与三种任务模型

RTOS 中的任务调度与三种任务模型

概述

任务的执行通过任务调度器来完成。通俗地讲,任务调度器就像一个导演,这位大导演在系统启动时就自动开始工作了,他的主要工作就是定时看下剧本,决定让哪个任务获取 CPU 进行表演:
在这里插入图片描述

实际使用中,任务调度器是一个较高优先级的中断,每一个 SysTick 时间到达或系统主动触发了任务调度器工作时,就检查就绪的任务中谁的优先级高,然后允许优先级高的任务使用 CPU, 直到下次触发任务调度器再次工作,重新决定哪个 Task 获取 CPU 得到执行权。

上图显示了任务的调度有两个策略:

1)任务被创建后将进入就绪状态,当两个任务的优先级不一样时,优先级高的任务先获取 CPU 的使用权得到运行。上图中,就绪的 Task0、Task1、Task2中,因 Task0 的优先级最高,因此获取了 SysTick1-SysTick2 之间的 CPU 使用权。

2)当两个任务的优先级一样时,若它们的优先级最高,则它们执行时间片调度,在 FreeRTOS 中时间片调度即循环地获取每一个 SysTick 时间内的 CPU 使用权。如上图中,Task1、Task2 的优先级相同,因此他们共享 SysTick2-SysTick4 之间的 CPU 使用权,在这两个 Tick 内,每个任务都获取其中的 1个 Tick 内的 CPU 使用权(其他的一些 RTOS 可以定义一个时间片的大小为几个 SysTick,FreeRTOS 的时间片大小是 1个 SysTick)。

需求及功能解析

本小节主要引出任务调度器的概念,在此基础上,介绍了任务函数的三种策略:

1)单次运行的任务 task3:创建一次,运行一次:

static void task3_process(void *arg)
{
    static const char *TASK3_TAG = "TASK3";
    ESP_LOGI(TASK3_TAG, "task3_flag = %d, arg3 = %s", task3_flag, (char *)arg);
    task3_flag++;
    vTaskDelete(NULL); // 删除任务
}

采用创建任务的方式来启动任务,可以省去用通信手段触发任务的麻烦,在不需要该任务时,还可以节省内存资源。此外,可以通过创建任务时的参数 *para,使得每次启动任务时可以让任务以不同的方式来执行。通常可以对 设备升级任务采取这种设计模式。当然,这种方式也有不好的地方,创建任务比较耗费时间,因此其仅适用对实时性要求不高的事情,如控制键盘启动任务。

2)周期执行的任务 task2:创建一次,周期运行:

static void task2_process(void *arg)
{
    static const char *TASK2_TAG = "TASK2";
    while (1) {
        ESP_LOGI(TASK2_TAG, "task2_flag = %d, arg2 = %s", task2_flag, (char *)arg);
        task2_flag++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

具备周期性的需求,往往有多种方案,我们将在后续深入讨论这些知识。

3)事件触发执行的任务 task1:创建后,等到某个事件的触发,相关事件触发后,自动运行一次:

static void task1_process(void *arg)
{
    static const char *TASK1_TAG = "TASK1";
    while (1) {
        if(wait_event()) { // 等待事件
            process_event(); // 处理事件
            notify_other_tasks();
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

根据 RTOS 的任务调度机制,事件触发的编程模型能最大限度提高CPU的使用效率(不必立即创建新任务,也不涉及延时),保证程序的实时性。

另外,周期性和事件触发执行的任务,其 TaskCode 都包含 while(1) 循环。他们的 TaskCode 的组成主要是:进行准备工作的代码、循环代码、异常处理代码。我们将在后续的学习中进一步熟悉这点。

示例解析

示例输出:

This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, Minimum free heap size: 294424 bytes
Wait event!
Process event!
Notify other tasks!
I (336) TASK2: task2_flag = 0, arg2 = 2
I (336) TASK3: task3_flag = 0, arg3 = 3

如上所述,单次运行的任务只运行一次(打印)就结束了;周期运行的任务一直打印;事件触发运行的任务也在循环打印,我们这里先阐述事件驱动任务编程的思路,后续将深入研究这种任务机制。

讨论

  1. 可以改变函数的优先级探究任务调度器的使用。

  2. 典型的任务调度机制有哪些?

1)抢占式调度( (Preemptive Scheduling) ),即总是期望优先级最高的任务获取 CPU 资源得到运行。每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到延时或者等待。

2)时间片调度(Time Slice),即每个任务都拥有相同的优先级,任务会运行固定的时间片个数(除非任务主动延时或者进入等待状态)。

3)协助式调度(Cooperative Scheduling,也称为合作式调度),只要一个任务不主动 yield 交出 CPU 使用权,它就会一直执行下去。

FreeRTOS 支持三种调度方式的,但最后一种调度方式几乎很少用了,读者只需了解它即可,本课程的内容仅涉及前两种调度方式。

  1. 触发任务调度(任务切换)的场景主要有哪些?

1)系统 SysTick 时间到达时触发任务调度器检查是否需要执行任务切换。

2)程序中主动调用系统调用函数。常见的系统调用函数如 taskYIELD()、portYIELD()、portYIELD_FROM_ISR() 在程序中被调用时也会触发任务调度器检查是否需要执行任务切换。

总结

1)任务调度器是 FreeRTOS 系统已经实现的任务管理组件,用于分配 CPU 的使用权,获取到 CPU 使用权的任务将得到执行。

2)任务调度器有两个调度策略:高优先级在 SysTick 到达时可抢占 CPU 使用权,同优先级可以共享一断时间内的 CPU 使用权(称为时间片轮转)。

3)任务根据其运行情况,可以分为单次运行、周期运行、事件驱动运行三种模型,其中事件驱动模型可以最大限度提高 CPU 的使用效率,并且实时性好。

4)更多优先级的设计,以及调度情况我们将在后续的章节中深入讨论。

资源链接

1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)
3) 下一篇:RTOS 暂停任务-任务挂起与恢复

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网老王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值