调度器

调度器介绍
简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的
特性:调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而
使得任务被挂起)。调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。当前正在
执行的任务是运行态的任务。不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在
各个 RTOS 中区别不大,基本相同的架构,任务切换也是相似的。调度算法就有些区别了,比如咱要讲的
μCOS-III 和 FreeRTOS 在抢占式调度器算法上就是两种不同的调度方法,下面我们主要了解一下合作式
调度器,抢占式调度器和时间片调度器。

合作式调度器
合作式调度器实现起来比较简单,易学易用,但是要真正的运用好这种调度器,还是要用心研究下的,
要不就不存在那么多嵌入式领域的工程师做这方面的研究,比如咱们上期教程里面说的 Michael J. Pont,
他是这方面的资深专家,有自己的团队和公司,他们设计了一款处理器内嵌基于时间触发的合作式调度器,
重要的是已经商用。这么说来应该算是这个领域成功的典范,下面的截图是他们设计这款处理器的框图。

上面的 HW SCH 就是硬件调度器的意思。
如何理解合作式调度器呢,如果用过 FreeRTOS 的话,FreeRTOS 就支持合作式调度(和本期教程要
讲的调度方式稍有区别)。没有用过 FreeRTOS 的话,μCOS-II 里面的 Timer 组件也有类似的功能(μCOS-III
里面还保留着这个功能),其实这些东西都是相同的,而且在 Micrium 公司出的μCOS-II 或μCOS-III 配
套官方书籍中说的不可剥夺性内核(Non-Preemptive Kernel)和咱们这里要说的合作式调度器也是类似
的。简单的来说,合作式调度器就是根据用户的设置时刻(周期或者单次)来执行相应的任务,每个时刻
只有一个任务可以执行,这些任务间不支持被强占,直到该任务自愿放弃 CPU 的控制权。下面要说的合
作式调度器特性就是摘自时间触发嵌入式模式那本书。
合作式调度器
合作式调度器提供了一种单任务的的系统结构
操作:
在特定的时刻被调度运行(以周期性或者单次方式)
当任务需要运行的时候,被添加到等待队列。
当 CPU 空闲的时候,运行等待队列中的下一个(如果有的话)。
任务运行直到完成,然后由调度器来控制。
实现:
这种调度器很简单,用少量代码即可实现。
该调度器必须一次只为一个任务分配存储器。
该调度器通常完全由高级语言(比如“C”)实现。
该调度器不是一种独立的系统,它是开发人员代码的一部分。
性能:
设计阶段需要小心以快速响应外部事件。
可靠性和安全性:
合作式调度简单,可预测,可靠并且安全。

抢占式调度器
在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和
LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用前面说的合作式调度器或者后面要说的
时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。
如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。当一个运行着的任
务使一个比它优先级高的任务进入了就绪态,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那
个高优先级的任务立刻得到了 CPU 的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,
中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。
使用抢占式调度器,使得最高优先级的任务什么时候可以执行,可以得到 CPU 的控制权是可知的,
同时使得任务级响应时间得以最优化。
总的来说,学习抢占式调度掌握最关键的一点是:抢占式调度器会为每个任务都分配一个优先级,调
度器会激活就绪任务中优先级最高的任务。
上期教程提到的 embOS,FreeRTOS,μCOS-II,μCOS-III,RTX 都支持抢占式调度,可以说这种调
度算法在小型嵌入式 RTOS 中极为流行。抢占式调度器给任务带来快速响应的同时也使得任务间的同步和
通信机制显的很麻烦,而且源码中的很多地方都需要设置临界段(通过开关中断来实现)。

时间片调度器
在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-robin 调度算法。这种调度算法
可以用于抢占式或者合作式的多任务中,时间片调度适合用于不要求任务实时响应的情况下。
实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,
并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
目前 embOS,FreeRTOS,μCOS-III 和 RTX 都支持 Round-robin 调度算法。

抢占式调度器
抢占式调度器提供了一种多任务的系统结构
操作:
任务在特定的时刻被调度运行(以周期性或者单次方式)
当任务需要运行的时候,被添加到等待队列。
等待的任务(如果有的话)运行一段固定的时间,如果没有完成,将被放回到等待队列。然后
下一个等待任务将会运行一段固定的时间,以此类推。
实现:
这种调度器相对复杂,访问共享资源时,要防止冲突。
该调度器必须为强占任务的所有中间状态分配存储器。
该调度器通常将(至少部分)由汇编语言编写。
该调度器通常作为一个独立的系统被创建。
性能:
对外部事件的响应速度快
可靠性和安全性:
与合作式任务相比,通常认为更不可预测,并且可靠性低。
使用抢占式调度器最大的好处是对外部事件的快速响应,提高系统的实时性。带来好处的同时也是要付出
代价的,下面就举一个例子,这个例子比较典型。
假设系统中的一个任务要从 ADC 端口读取模拟信号,这时发生了上下文切换,另一个任务也要访问
这个端口,在这种情况下如果不采取措施阻止这种情况,数据将可能丢失或者破坏。这种问题往往出现在
多任务平台中所谓的“临界段”,这些代码段一旦开始就必须不中断地运行,直到跑完这段代码。临界段
还有很多,比如:
读取或者修改变量(特别是任务间通信的全局变量)的代码,一般来说这时最常见的关键代码。
如果读过μCOS-II 或μCOS-III 源代码的话,会发现里面很多地方都做了开关中断的处理,就是为了
防止多任务造成错误。
调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。
解决这种情况最常用的的办法就是在临界段做开关中断处理,这种情况是最影响实时性的,如果关中断期
间发生了外部中断事件,那么这个中断事件只有临界段执行完之后才能得到相应。还有一种解决办法就是
使用调度锁,这样在一定程度上能够提供任务的实时性,在发生外部中断的时候会得到相应,但是不会发
生任务切换,必须等到调度锁被解开。
关于抢占式调度器和合作式调度器到底谁好,在嵌入式领域没有一个定论,要根据自己的时间应用选
择,很多嵌入式领域的专家都发表过这方面的文章,有兴趣的可以找找深入研究下,下面是 Michael J. Pont
的观点,原话如下:
Finally, it should be noted that the reasons why pre-emptive schedulers have been more
widely discussed and used may not be for technical reasons at all: in fact, the use of pre-emptive
environments can be seen to have clear commercial advantages for some companies. For
example, a co-operative scheduler may be easily constructed, entirely in a high-level
programming language, in around 300 lines of ‘C’ code, as we demonstrate in Chapter 9. The
code is highly portable, easy to understand and to use and is, in effect, freely available. By
contrast, the increased complexity of a preemptive operating environment results in a much
larger code framework (some ten times the size, even in a simple implementation: Labrosse,
1998). The size and complexity of this code makes it unsuitable for ‘in-house’ construction in
most situations and therefore provides the basis for a commercial ‘RTOS’ products to be sold,
generally at high prices and often with expensive run-time royalties to be paid. The continued
promotion and sale of such environments has, in turn, prompted further academic interest in this
area.
上面这段话的意思是说,抢占式调度器被广泛的讨论和使用的原因可能并不完全是技术上的问题,实
际上对于一些公司来说,使用抢占式平台有明显的商业上的好处,这个该怎么理解呢?比如我们做了一款
产品,产品的系统是用的μCOS-II。我们肯定会在说明书中宣称我们使用的是μCOS-II 系统,因为这个系
统已经得到了业界的认可,而不会使用我们自己的设计的系统,用户在购买产品的时候肯定会首选用
μCOS-II 设计的系统。如果使用合作式调度器,只需要 300 行左右的 C 代码就可以实现一个合作式调度器,
这些代码的可移植性非常好,易于理解和使用,并且实际上是免费使用的。相比而言,抢占式运行环境增
加了复杂性,从而导致更复杂的代码结构。在大多数情况下,这些代码的复杂性和长度使其不适合于普通
开发人员自己构建,因此提供了商业实时操作系统的产品的销售基础。抢占式调度器通常价格比较贵,而
且还需占用很大的实时开销。这些平台持续不断的宣称和销售反过来又促进了这个领域的更进一步的学术
研究。
对于合作式调度,安富莱第二章给了一个自己实现的参考,但是正如Michael J. Pont 所说,比如我们做了一款
产品,产品的系统是用的μCOS-II。我们肯定会在说明书中宣称我们使用的是μCOS-II 系统,因为这个系
统已经得到了业界的认可,而不会使用我们自己的设计的系统,用户在购买产品的时候肯定会首选用 
μCOS-II 设计的系统。

混合式调度器介绍
合作式调度器为各种嵌入式系统提供了一个可预测的平台。在一些场合可能需要在合作式调度器结构
中加入抢占式调度器的一些特性,并小心的加以控制,混合式调度器应运而生。
混合式调度器综合了合作式调度器和抢占式调度器的特性,而且这种方式始终是受控的,不依赖复杂
的上下文切换程序,以及任务间复杂的通信机制。下面是混合式调度器的特性:

混合式调度器
混合式调度器提供了有限的多任务处理能力。
操作:
支持多个合作式调度的任务。
支持一个抢占式任务(可以中断合作式任务)。
实现:
这种调度器很简单,用少量代码即可实现。
该调度器必须同时为两个任务分配存储器。
该调度器通常完全由高级语言(比如”C”实现)。
该调度器不是一种独立的系统,它成为开发人员的代码的一部分。
性能:
对外部事件的响应速度快
可靠性和安全性:
只要小心设计可以和纯碎的合作式调度器一样可靠。
这里所描述的混合式调度器和合作式调度器是有区别的,区别如下:
不再要求所有任务都在时标间隔之间完成任务。一个(或者更多)的合作式任务的运行时间可以大
于时标间隔。
与前面讨论的合作式调度器一样,可以调度任意个合作式任务。然而,还可以同时调度一个抢占式
任务。
抢占式任务可以抢先(中断)合作式任务。
一旦抢占式任务开始运行,将一直运行到完成。
同时注意:
与完全的抢占式解决方案相比,只有一个抢占式任务,而且该任务连续运行直到完成,这将及大地
简化系统的结构。尤其是不需要实现上下文切换的机制。这意味着:1. 该结构仍然十分简单。 2. 运
行环境可以全部由 C 实现。
和完全的抢占式调度器相比,简化了任务间的通信。
应该只有一个短任务(最长的运行时间大于为时标间隔的%50,尽可能的短)可以强占运行,否则
将削弱系统的整体性能。
下面详细讲一下混合式调度器的设计,这样大家会有一个更好的认识。
在使用混合式调度器的时
候,大家要特别注意这点。
混合式调度器主要由以下几部分组成:
调度器数据结构
初始化函数
嘀嗒定时器中断,用来以一定的时间间隔刷新调度器
向调度器增加任务函数
使任务在应当运行的时候被执行的调度函数
从调度器删除任务的函数(此功能未做)
具体实现参考安富莱第三章。

原文地址:https://www.cnblogs.com/yangguang-it/p/7267165.html