浅析FreeRTOS任务调度器的三种调度算法和应用

描述

 

FreeRTOS在MCU领域应用非常广泛,今天就给大家讲解一下FreeRTOS调度器中的三种调度算法,以及在瑞萨RZ/T2L MPU中的应用。

1. 任务状态和事件的概述

RTOS,即实时操作系统,是一种能够在规定时间内响应外部事件或数据,并控制生产过程或对处理系统做出快速响应的操作系统。RTOS通过调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行。RTOS的主要特点包括提供及时响应和高可靠性。

在RTOS中,任务状态通常包括以下几种:

就绪态(Ready):任务已经准备好执行,所有必要资源都已经准备就绪,只等待RTOS调度器选中并分配CPU时间。新创建的任务通常立即进入就绪状态,等待调度器的调度。

运行态(Running):任务正在执行,占据了CPU资源。当任务被RTOS调度器选中后,其上下文会被加载到CPU寄存器中,开始执行任务代码。

阻塞态(Blocked):任务暂时不能执行,通常是因为等待某些外部事件,如I/O操作完成、定时器到期、信号量或互斥量被释放等。一旦这些事件发生,任务会重新进入就绪状态,等待调度器的调度。

挂起态(Suspended):任务被主动挂起,不参与调度,需要手动恢复才能回到就绪态。挂起状态的任务不会执行,直到被其他任务或中断调用特定的API函数恢复。

RTOS中的事件是指能够触发任务状态改变或任务执行的某些外部或内部发生的情况。事件可以是来自硬件的中断、定时器到期、消息队列中的消息到达等。RTOS通过事件来同步和协调任务的执行,确保它们能够按照预期的方式和时间顺序运行。

2.FreeRTOS调度算法

FreeRTOS它支持多种任务调度算法,可通过配置来满足不同应用的需求。

通过配置 configUSE_PREEMPTION 和 configUSE_TIME_SLICING 来更改算法。这两个常量都在FreeRTOSConfig.h中定义。

还有个配置常数configUSE_TICKLESS_IDLE也会影响调度算法,因为使用它会导致tick中断在很长一段时间内被完全关闭。

configUSE_TICKLESS_IDLE是一个高级选项,专门用于必须最小化功耗的应用程序。configUSE_TICKLESS_IDLE在之后解说。

对于相同优先级的任务,FreeRTOS调度器依次选中相同优先级的任务进入运行态。这种轮流策略被称为‘Round Robin Scheduling’

‘Round Robin Scheduling’算法并不能保证同等优先级的任务之间运行相同的时间,只能保证同等优先级的“就绪”任务会依次进入“运行”态。

2.1

基于时间片的抢占式调度

如果按照如下配置:

configUSE_PREEMPTION 1

configUSE_TIME_SLICING 1

FreeRTOS调度器使用一种称为“基于时间片的固定优先级抢占式调度”的调度算法,这是大多数小型RTOS应用程序使用的调度算法。

固定优先级

被描述为“固定优先级”的调度算法不会改变分配给被调度任务的优先级,但也不会阻止任务本身改变自己的优先级或其他任务的优先级。

抢占式调度

在优先级高于运行态任务的任务进入就绪态时,调度器立即让这个高优先级的任务“抢占”运行态任务。被抢占意味着任务移出运行态并进入就绪态,并不是因为任务自己主动让出或者阻塞。

时间片

时间片用于在具有相同优先级的任务之间共享处理时间,即使任务没有显式地让步或进入阻塞状态。使用“时间片”的调度算法将在每个时间片结束时选择一个新任务进入运行状态,如果有其他与运行任务具有相同优先级的就绪状态任务。一个时间片等于两个RTOS tick中断之间的时间。

调度算法

上图演示了使用“基于时间片的固定优先级抢占式调度”算法抢占调度任务的调度过程。

task1是最高优先级的事件驱动任务,task2是中等优先级的周期性任务,task3是最低优先级的事件驱动任务,Idle task是空闲任务。

task1周期性运行,阻塞时,空闲任务就会运行,task3的事件到达就会抢占空闲任务,task3运行期间,如果task2的周期到了,因为task2优先级高就会抢占task3,task2运行完了再接着运行task2,task2运行期间,由于task1优先级高,一旦task1等待的事件到了就会抢占task2。

有相同优先级任务的情况:

调度算法

task1是优先级最高的事件驱动任务,task2是和Idle task优先级相同的持续处理型任务。

task2和空闲任务就会轮流运行,而task1则可以抢占task2和空闲任务。

假如空闲任务里其实没做什么事情,我们想让和空闲任务相同优先级的Task2有更多的运行时间就可以配置configIDLE_SHOULD_YIELD。

如果configIDLE_SHOULD_YIELD设置为0,那么空闲任务将在整个时间片中保持运行状态,除非它被更高优先级的任务抢占。

如果configIDLE_SHOULD_YIELD设置为1,如果有其他空闲优先级任务处于就绪状态,那么空闲任务将主动让出运行时间。

调度算法

2.2

不带时间片的抢占式调度

配置如下时,调度算法就会变成不带时间片的抢占式调度

configUSE_PREEMPTION  1

configUSE_TIME_SLICING 0

和前面的唯一区别就是相同优先级的任务之间不会随时间自动切换。

如果不使用时间片,那么调度程序只会在发生这两种情况时,进行任务切换:

1

优先级更高的任务进入“就绪”态。

2

运行态任务变为阻塞态或被挂起。

很显然,不使用时间片时,任务切换的情况会变少,所以关闭时间切片可以减少调度器的处理开销。但是关闭时间切片也可能导致具有相同优先级的任务获得的处理时间相差很大。因此,一定要慎重使用。

调度算法

task1为最高优先级的事件驱动任务,task2和Idle task有相同的优先级。但是由于关闭了时间片,任务切换只会在空闲任务阻塞或者挂起或者task1抢占后发生,所以空闲任务和task2虽然优先级相同,但是明显空闲任务占有的时间长很多。

2.3

协同调度

configUSE_PREEMPTION 0

configUSE_TIME_SLICING x

当使用协同调度时,只有当运行态任务进入阻塞态,或者运行态任务通过调用taskYIELD()主动让出,才会发生任务切换。

任务永远不会被抢占,不能使用时间片,时间片配置的值随便,无所谓。

调度算法

task1、2、3优先级依次变低,刚开始task3运行,虽然task1和2优先级高,并且没有阻塞,但是也无法抢占,task3调用taskYIELD(),主动让出,因为task1优先级比task2高,task1就运行了,task1运行够了进入阻塞态,由于task2比task3优先级高,task2就运行了。

在RZ/T2L上做的实验

如果两个参数设置为:configUSE_PREEMPTION =0,configUSE_TIME_SLICING= 0或者1(随便) 称之为协同调度,当使用协同调度时,只有当运行态任务进入阻塞态,或者运行态任务通过调用taskYIELD()主动让出,才会发生任务切换。

任务永远不会被抢占,不能使用时间片,时间片配置的值随便。我做了一个实验截图如下:

调度算法

调度算法

上面的实验说明 “只有当运行态任务进入阻塞态,或者运行态任务通过调用taskYIELD()主动让出,才会发生任务切换”,此时与时间片1 ms已经没有什么关系了。

如果两个参数设置为 configUSE_PREEMPTION =1,configUSE_TIME_SLICING=1, 此时只有两个任务并且优先级相同,每个任务都持续做一件事就会出现时间征为1ms的轮转情况:

调度算法

调度算法

通过上面的两个实验可以说明,时间片存的意义是相同优先级并且持续时间较长的处理任务,需要平等分享CPU使用权。但是如果一旦有一方主动放弃CPU或者阻塞或者更高优先级任务来了,调度器是不会等待一个时间片完全结束,再去调度就绪列表中的任务的,而是直接调用就绪列表里的第一个任务。这样的设计我认为是合理的,如果一定要等待时间片结束,是存在严重的资源浪费的。



审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分