OSEKOS task管理
2 task管理
2.1 task概念
task为函数们的执行提供了框架。操作系统提供task的并发和异步执行。scheduler组织task执行的顺序。操作系统提供了一种task切换机制(参见scheduler),包括一种在没有其他系统或应用程序功能处于活动状态时处于活动状态的机制。这种机制称为空闲机制。
操作系统提供了两种不同的task概念:
- 基本task;
- 扩展task。
2.2 task状态模型
12.2.1 General
一个task应该能够在多个状态之间变化,因为处理器在任何时候只能执行一个task的一条指令,而多个task可能在同一时间竞争处理器。操作系统负责在必要时连同task状态转换一起保存和恢复task上下文。
2.2.2 基本task

基本task由以下task状态组成:
- 运行(Running): 在运行状态下,task被分配给CPU,使其指令可以执行。在任何时间点,只有一个task处于该状态,而其他所有状态可以被多个task同时采用。
- 就绪(Ready): 转换到运行状态的所有功能先决条件都已经存在,task只等待处理器的分配。scheduler(调度器)决定接下来执行哪个Ready的task进入Running。
- 挂起(Suspended): task为被动状态,可被激活,只有被激活到Ready,才能到Running。
只有当task自己终止(“self- terminate”)时,才有可能终止task。这个限制降低了操作系统的复杂性。所以每次task的最后,都需要调用接口来让出CPU资源。
基本task只有在以下情况下才会释放处理器:
- 他们自己终止;
- 操作系统切换到更高优先级的task;
- 或一个中断发生,导致处理器切换到一个中断服务程序(ISR)。
可以如下定义一个基础task:
其他触发源:触发了激活task,有以下两种方式:
{
ActivateTask(Base_demo1);
或者
ChainTask(Base_demo1);
}
task(Base_demo1)
{
code_1; // 每次从ready到running状态时,都是从头code_1开始往下运行
code_2;
// 此处被中断或者更高优先级的task抢占,待他们运行完成之后,继续执行code_3
code_3;
code_4;
TerminateTask(); // 结束当前task_Base_demo1运行
或者
ChainTask(task_Base_demo2); // 结束当前task_Base_demo1运行,并且切换到 other_task 运行
}
2.2.3 扩展task

扩展task与基本task的区别在于允许使用OS WaitEvent,这可能会导致等待状态。等待状态允许释放处理器并将其重新分配给低优先级的task,而不需要终止正在运行的扩展task。
从操作系统的角度来看,扩展task的管理在原则上比基础task的管理更加复杂,需要更多的系统资源。因为切入到等待状态之后,会把当前task的堆栈信息保存起来。
扩展task有四种task状态,比基础Task多一个Waiting:
- 等待: task不能继续执行,因为它必须等待至少一个事件(后续章节描述)。
不提供从挂起状态到等待状态的直接转换。这种转换是多余的,会增加调度器的复杂性。
可以如下定义一个扩展task:
其他触发源:触发了激活task,有以下两种方式:
{
ActivateTask(Extend_demo1);
或者
ChainTask(Extend_demo1);
}
task(Extend_demo1)
{
code_1; // 每次从ready到running状态时,都是从头code_1开始往下运行
code_2;
// 此处被中断或者更高优先级的task抢占,待他们运行完成之后,继续执行code_3
code_3;
code_4;
WaitEvent(Event_XXX|Event_YYY); // 此处可以 WaitEvent 等待 Event_XXX 或者 Event_YYY 事件,之后进入waiting状态,直到其中一个发生
// 等待到了其中一个事件,task变化为ready状态,调度器开始运行之后的代码逻辑。
GetEvent(TaskId_Extend_demo1, &ev); // 获取当前TASK的事件合集
if ((ev & Event_XXX) != (EventMaskType)0) // 发生事件 Event_XXX
{
ClearEvent(ev & (Event_XXX)); // 清除对应的事件
code_5;
}
if ((ev & Event_YYY) != (EventMaskType)0) // 发生事件 Event_YYY
{
ClearEvent(ev & (Event_YYY)); // 清除对应的事件
code_6;
}
code_7;
TerminateTask(); // 结束当前task_Base_demo1运行
或者
ChainTask(task_Base_demo2); // 结束当前task_Base_demo1运行,并且切换到 other_task 运行
}
其他触发事件:触发了激活task:
{
SetEvent(Event_XXX);
或者
SetEvent(Event_YYY);
或者
SetEvent(Event_XXX|Event_YYY);
}

逐一分析task的运行状态:
task初始化之后,初始状态是Suspended,执行激活(activate)之后,状态由 suspended改变为ready,放入调度器中进行排队等着运行,OS保证task是由这个task的第一句指令开始执行的。之后调度器选用了这个task,便start这个task,把CPU资源交给它,由ready状态到running状态开始正式运行。
如果在task运行过程中需要某个事件才可以继续往下运行,那么这个task可以调用WaitEvent,开始wait事件,交出当前CPU资源,状态由Running变为Waiting,如果等待到了需要的Event,那么就release(释放)当前的Waiting状态变为ready状态,再接着等待调度器选用是否执行这个task。(以上running到waiting到ready状态只有扩展task有)。
如果在当前task运行过程中被更高优先级的taskpreempt(抢占),状态由running到ready,会保存当前的堆栈状态,等待调度器选用,选用了之后恢复堆栈状态继续执行之前的代码。
如果在当前task运行结束了 terminate(终止) ,需要自己调用TerminateTask,表示跑完了,由running到suspended,等待被激活。(调用TerminateTask不一定在task的最后哦,也可以在中间,某个条件满足了之后,他表示的是当前的动作执行完成了,可以不需要CPU了)
2.2.4 task类型的比较
基本task没有等待状态,因此只包含task开始和结束的同步点。具有内部同步点的application parts应由多个基本task实现。基本task的一个优点是它们对运行时上下文(RAM)的需求适中。
扩展task的一个优点是,无论哪个同步请求是活动的,它们都可以在单个task中处理一致的作业。当需要进一步处理的当前信息缺失时,扩展task将切换到等待状态。当相应的事件发出接收或更新所需数据或事件的信号时,它将退出此状态。扩展task还包含比基本task更多的同步点。可以理解为扩展task比基础task多了一个同步的功能,等待某个Event之后再运行。
2.3 激活一个task
2.3.1 General
task激活通过操作系统服务ActivatTask或ChainTask进行。激活后,task就可以从第一条语句开始执行了。操作系统在启动task时不支持类c参数传递。这些参数应该通过消息通信(参见后面章节)或全局变量传递。需要注意的是:task是没有周期运行的属性,如果需要达到周期运行,那么就是对应激活task这个动作周期执行,便达到了周期运行task的需求。
2.3.2 task激活的多个请求
根据一致性类的不同,基本task可以被激活一次或多次。
“task激活的多个请求”意味着操作系统接收并记录一个已激活的基本task的并行激活。如果未达到多个请求的最大数量,则请求将进入队列。并行多个请求的最大数量在系统生成期间在一个基本task特定属性中定义。
基本task激活的请求按激活顺序按优先级排队。
相当于给这个基础task定义了一个FIFO,并且这个FIFO的优先级就是对应基础task的优先级,调度器会根据优先级先判断释放需要执行这个FIFO中的基础task,并根据先被激活的顺序执行这个基础task。
2.4 task切换机制
决定启动哪个task和触发所有必要的操作系统内部活动的实体被称为“scheduler(调度器)”。根据所述的调度策略,只要有可能进行task切换,就激活所述 scheduler。scheduler 可以被看作是一种可以被task占用和释放的资源。因此,task可以保留scheduler,以避免task切换,直到它被释放(后续6.4描述,这个资源叫做RES_SCHEDULER )。
2.5 task优先级
scheduler 根据task优先级来决定哪个task是下一个要转移到 running 状态的 ready task。
0被定义为task的最低优先级。因此,数字越大,优先级越高。
为提高效率,不支持动态优先级管理。因此,task的优先级是静态定义的,即用户在运行时不能更改task的优先级。但是,在特定情况下,OS可以处理具有定义的更高优先级的task(看后续资源天花板机制,6.6章节)。
一致性类BCC2和ECC2支持具有相同优先级的task。
具有相同优先级的task根据其激活顺序启动,处于等待状态的扩展task不会阻塞具有相同优先级的后续task的启动。
被抢占的task被认为是其当前优先级的就绪列表中的第一个(最早的)task。比如task1和task2优先级是4,task3优先级是8,且都是可抢占,先激活了task1再激活task2,都是ready状态,scheduler选择task1先到running,在运行途中被task3被激活,scheduler立马把task1保存堆栈,切换到ready状态,把task3切换到running,task运行完成之后,scheduler寻找处于ready状态的task,发现有task1和task2,但是由于task1是之前scheduler切换抢占运行的,所以先running task1,task1运行完成之后再运行task2.
从等待状态释放的task将被视为其优先级就绪队列中的最后一个(最新的)task。比如task1和task2和task3优先级是4,且都是可抢占,先激活了task1,是ready状态,scheduler选择task1先到running,在运行途中被task1需要等待一个事件,进入waiting状态,之后一起Event到达和激活task2,task3,scheduler会先运行task2,再task3,最后才是task1.
图6显示了使用每个优先级级别的队列实现scheduler的示例。几个优先级不同的task处于就绪状态;即三个优先级为3的task,一个优先级为2,一个优先级为1,再加上两个优先级为0的task。根据请求的顺序,等待时间最长的task显示在每个队列的底部。处理器刚刚处理并终止了一个task。scheduler选择要处理的下一个task(优先级3,第一个队列)。在处理优先级2的task之前,所有高优先级的task都必须处于运行就绪状态,即启动,然后由于终止或过渡到等待状态而从队列中移除。

以下基本步骤是确定下一个要处理的task所必需的:
- 调度器搜索所有处于就绪/运行状态的task。
- 从处于就绪/运行状态的task集中,scheduler确定具有最高优先级的task集。
- 在处于就绪/运行状态且优先级最高的task集中,scheduler找到最早的task。
2.6 调度策略
2.6.1 全抢占式调度 Full Preemptive Scheduling
完全抢占式调度是指当前正在运行的task可以根据操作系统预先设定的触发条件,在任何指令下重新调度。一旦高优先级的task就绪,完全抢占式调度就会将正在运行的task置于就绪状态。task上下文被保存,以便被抢占的task可以在它被抢占的位置继续执行。
完全抢占式调度的延迟时间与低优先级task的运行时间无关。某些限制与节省上下文所需的(RAM)内存空间的增加,以及task之间同步所需特性的复杂性的增强有关。由于理论上每个task都可以在任何位置重新调度,因此与其他task联合使用的数据访问需要同步。
在图7中,低优先级taskT2不会延迟高优先级taskT1的调度。

在完全抢占系统的情况下,用户应该想象到正在运行的task在任意时刻被抢占。如果一个task片段不能被抢占,可以通过系统服务 GetResource 暂时阻塞scheduler来实现。
综上所述,在以下所有情况下都会执行重调度:
- 成功终止task(系统服务TerminateTask,11.3.2章节);
- 通过显式激活后继task成功终止task(系统服务ChainTask,11.3.2章节);
- 在task级别激活task(如系统服务 Activatetask,,11.3.2章节消息通知机制,Alarm过期;如果定义了task激活,7.3章节);
- 显式wait调用,此时系统会发生向Waiting状态的转换(仅扩展task、系统服务 WaitEvent,11.6.2章节);
- 设置事件为task级别的等待task(如系统服务 SetEvent,,11.6.2章节,消息通知机制,Alarm过期;如果事件设置已定义,7.3章节);
- task级资源释放( 系统服务ReleaseResource ,11.5.2章节);
- 从中断级返回到task级。
在ISR(中断服务例程)中,不执行重调度。(因为ISR中不属于可调度上下文,一旦跳出去就回不来了)
使用“完全抢占式调度”调度策略的应用不需要系统服务Schedule,而其他调度策略使用该系统服务。为了使可移植的应用程序能够在不同的调度策略下编写,用户可以通过系统服务Schedule在用户认为正确的CPU分配的位置执行重新调度。
2.6.2 非抢占式调度
如果task切换只通过一个显式定义的系统服务(显式重调度点)来执行,则调度策略被描述为非抢占式。
非抢占式调度对task的可能时序要求施加了特殊的限制。具体来说,运行中的低优先级task的不可抢占部分将高优先级task的开始延迟到下一个重调度点。
在图8中,优先级较低的taskT2将优先级较高的taskT1延迟到下一个重调度点(在本例中,taskT2终止)。

2.6.3 重调度点
对于非抢占task,重调度应在以下情况发生:
- task成功终止(系统服务 Terminatetask,11.3.2章节)。
- 通过显式激活后继task成功终止task(系统服务ChainTask,11.3.2章节)。
- 显式调用scheduler(系统服务Schedule,11.3.2章节)。
- 当前task发生了向Waiting状态的转换(系统服务WaitEvent,11.6.2章节)。
如果传递给WaitEvent的事件掩码中有一个事件已经设置,WaitEvent的调用不会导致等待状态。在这种情况下,WaitEvent不会导致重新调度。非抢占式系统的实现可能规定导致重调度的操作系统服务只能在最高的task程序级别调用(而不是在task子函数中)。
注: 在这些调度点上的task切换通常需要保存较少的task上下文信息。
2.6.4 task组
OS允许task通过定义task组来结合抢占式调度和非抢占式调度。对于与组内最高优先级相同或更低优先级的task,组内task的行为类似于不可抢占的task: 重调度只发生在4.6.2中所述的重调度点。对于优先级高于组内最高优先级的task,组内task的行为类似于可抢占task。
内部资源章节描述了使用内部资源定义组的机制。非抢占性task是内部资源概念最常见的用法;它们是具有特殊内部资源的task,具有最高的task优先级。
2.6.5 混合抢占调度
如果在同一个系统上混合使用可抢占和不可抢占的task,则产生的策略称为“混合抢占”调度。在这种情况下,调度策略取决于正在运行的task的抢占属性。如果正在运行的task是非抢占式的,则执行非抢占式调度。如果正在运行的task是可抢占的,则执行抢占调度。
非抢占task的定义在全抢占操作系统中是有意义的:
- 如果task的执行时间与task切换的时间大小相同;
- 如果要经济地使用RAM来提供空间以保存task上下文;
- 如果task没有被抢占的需求。
许多应用程序只包含几个执行时间很长的并行task,对于这些task,完全抢占式操作系统会很方便。而许多具有确定执行时间的短task时,非抢占式调度会更有效。对于这种配置,混合抢占式调度策略是一种折衷(参见14.3.5中的设计提示)。
2.6.6 选择调度策略
软件开发人员或系统集成商通过配置task优先级和将可抢占性作为task属性来确定task的执行顺序。
task类型(基本或扩展)独立于task的调度类型(可抢占或不可抢占)。因此,完全抢占式系统可以包含基本task和非抢占式系统扩展task。
如果OS服务正在运行,抢占和上下文切换可能会延迟到服务完成。
2.7 task的终止
在操作系统中,task只能自行终止(self- terminate)。操作系统提供了ChainTask服务,保证在一个正在运行的task结束后,立即激活执行另一个特定task。Chain本身会将task放到优先级队列的最后一个元素中。
每个task都将在其代码结束时自行终止。当task结束时,调用Terminate task或ChainTask;不这样做会导致未定义的行为。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)