1 前言
RTOS通过任务(task)来组织应用层程序框架(framework),支持任务的并发和同步执行(concurrent and asynchronous execution of tasks),并通过调度器(scheduler)来管理任务的执行,OSEK也概莫能外。
OSEK将任务划分为基础任务(basic tasks)和拓展任务(extended tasks)两类。对于基础任务来说,它们会一直占有CPU,直至其主动终止(terminate)任务运行,或被高优先级的任务/中断抢占,才会释放CPU资源;而拓展任务的特殊性在于多了一个可阻塞的等待状态,即任务阻塞在那里等待一个事件的触发(WaitEvent,如等待一个队列的入队操作等),这一状态被称为等待状态(waiting state)。
拓展任务的等待状态的好处显而易见,即可以让出CPU资源给优先级较低的任务,但从OS的角度来看,相对于基础任务,其消耗的资源必然也更大,毕竟进入阻塞的等待状态前,需要对该拓展任务进行现场保护,这存在着时间和空间上的开销。
2 任务状态机模型
2.1 状态模型
如图1所示,基础任务和拓展任务的区别主要在于waiting状态(等待态/阻塞态),而running(运行态),suspended(挂起态)和ready状态(就绪态)的定义完全相同。显然,由于基础任务没有等待状态,因此只包含任务开始和结束时这两个同步点(synchronisation point);对于有内部同步点的作业,还是拓展任务更能胜任。
图1(a) 状态模型示意图
图1(b) 状态模型示意图
此外,为了降低系统复杂度,OSEK规定只有任务本身可以终止自己(self-termination),如果一个任务调用ChainTask来链式激活自己,则会将自己加到对应优先级的就绪态队列尾部,等待调度执行;同时,没有从挂起态到等待态的直接转换路径,在挂起状态下,任务处于被动状态,即可以被激活。
调度器(scheduler)根据调度策略(scheduling policy)来决定各任务的状态,选择当前得以运行的任务(使之从ready状态进入running状态),并为其统筹所有系统资源。从某种意义上,调度器本身也可以看作一种资源,在单核环境下,在同一时刻最多只能被一个任务占有,其本质是对应着CPU的计算资源。
2.2 任务优先级
OSEK完全摒弃了动态设置任务优先级的方式,采用静态方式配置任务优先级,即不可再运行时更改任务优先级。0是最低任务优先级,数值越大,则代表优先级越高,这点与freeRTOS相同,与ARM中断优先级定义方式相反。
此外,在BCC2或ECC2下,支持多个任务配置相同优先级。
2.3 调度策略
2.3.1 完全抢占式调度(Full preemptive scheduling)
简单来说,完全抢占式调度只有一个规则,就是完全按照优先级来进行调度,谁的优先级高,谁就执行。正在运行的任务必须做好随时被拉下神坛的觉悟,一旦有优先级更高的任务出现,则必须退位让贤(保存上下文,退回就绪态,让出CPU)。这种随时随地可能发生的重调度也就意味着,任务间的共享数据必须做好同步,否则必有惊喜出现。
总的来说,重调度点(触发系统调度)主要包括以下几种情形:
① 通过系统服务TerminateTask成功终止一个任务时,或通过系统服务ChainTask成功终止当前任务并激活下一个任务时;
② 任务级别的任务激活(Activating a task at task level),包括通过系统服务ActivateTask
激活任务、通过消息通知机制(message notification mechanism)激活任务、或定时器溢出(alarm expiration)来激活任务等;
③ 在拓展任务中调用系统服务WaitEvent时,任务进入等待状态;
④ 设置一个事件(event)以唤醒等待该事件的任务,事件的设置可以通过系统服务SetEvent或通过消息通知机制或在定时器溢出处理中来实现;
⑤ 任务级别的资源释放(通过系统服务ReleaseResource);
⑥ 从中断返回任务时(Return from interrupt level to task level);
⑦ 显式调用系统服务函数Schedule时,触发调度。
2.3.2 非抢占式调度
该调度策略完全依赖于基于系统服务的显示调用来触发调度。在此策略下,任务的优先级显然也没多大意义了,高优先级的任务无法抢占低优先级的任务,只能等待低优先级任务主动让出CPU,这对于实时系统来说,显然时不利的。
非抢占式调度的重调度点主要包括以下几种情形:
① 通过系统服务TerminateTask成功终止一个任务时,或通过系统服务ChainTask成功终止当前任务并激活下一个任务时;
② 显式调用系统服务函数Schedule时,触发调度;
③ 任务陷入等待状态,通常都是等待一个事件导致的(WaitEvent),其本质是任务被迫让出CPU的使用权。
2.3.3 任务组(Groups of tasks)
OSEK支持对任务进行分组,以分别执行抢占式调度策略和非抢占式调度策略。具体来说,对于那些优先级小于等于组内最高优先级(the highest priority within a group)的任务,执行非抢占式调度策略(non preemptable tasks);对于那些优先级大于组内最高优先级的任务,执行抢占式调度策略。
这么做有什么好处呢?一个典型的例子,可以将共享同一资源的任务分在一组内,而当任何组内占有这一共享资源时,都会通过优先级提升将任务优先级临时设置为资源的上限优先级(天花板优先级,ceiling priority of the resource),这会避免不必要的任务抢占和调度,毕竟共享资源本事就是临界资源。另一方面,对于那些优先级高于上限优先级且不需要该资源的任务,仍以抢占式调度策略来执行。
2.3.4 混合式抢占调度策略(Mixed preemptive scheduling)
当系统上同时存在可抢占和不可抢占任务时,就需要应用混合式抢占调度策略。在这种情况下,会为任务添加上是否可抢占的属性标签,并在运行时执行对应的调度策略。这也就意味着,系统中的任务存在两种不同调度类型,即可抢占和不可抢占(preemptable or non preemptable),这与任务类型(task type,基础任务和拓展任务)是相互独立的、不同维度的概念。
在完全抢占式调度策略里混合点不可抢占的刺头的意义在哪呢?首先,如果一个任务的执行时间很短,短到同切换上下文(任务切换)的耗时接近,甚至更短,那就没必要抢占这个任务,让它执行结束好了;其次,不抢占意味着不用做上下文切换,也就省去了现场保护的内存开销,这对于RAM的节约相当友好;最后,也有可能存在一些这样那样的任务,人家本身就是不希望被抢占的。