任务
任务调度
RTA OS根据任务固定的优先级来执行任务切换,一般任务优先级的确定原则包括:
- 根据截止时间: 超时时间短的任务优先级高
- 根据执行频率: 执行频率快的任务优先级高
Autosar OS支持三种调度策略:
- 打断式调度 Preemptive Scheduling
始终执行当前ready任务中优先级最高的。一个任务运行时,当更高优先级任务处于就绪状态,则中断当前任务执行高级任务,高级任务完成后恢复原任务。 - 非打断式调度 Non-Preemptive Scheduling
按优先级高低执行当前ready的任务,但是当一个任务运行时,就算更高优先级任务就绪,也继续执行完当前任务。(非打断式调度中任务仍然会被中断打断) - 混合式调度 Cooperative Scheduling(配置不支持)
任务默认按非打断式处理,但是任务执行过程中,可以通过调用Schedule API的方式自行决定可以在何时被打断。如果当执行API时有高级任务处于ready状态,则执行高级任务。
打断式任务具有最高的效率,可以确保任务从开始到结束的时间最短,但是需要考虑共享资源对任务调度的影响。非打断式实时性较差,任务需要更长的响应时间,但是不需要考虑共享资源问题(资源不允许并发)。混合式为上述两种方式的折衷方案。
基本任务和扩展任务
任务可分为以下两类:
1.基本任务 Basic Task
基本任务只包含开始、执行、结束动作,只有在结束或是被打断时才会释放CPU,执行快,效率高。
2. 扩展任务 Extended Task
扩展任务包含了开始、执行、等待事件(可选)、结束动作。扩展任务能在执行中自行释放CPU的能力提供了一种任务中同步方法。
任务状态
基本任务包含三种状态
- 终止 Suspended
- 就绪 Ready
- 运行 Running
扩展任务在此基础上包含了第四种状态 - 等待 Waiting
任务默认均为Suspended状态,激活后进入Ready状态,激活的方式可能为通过激活任务API、Alarm触发、调度表节点触发等。激活的任务处于当前系统中最高优先级时,即会进入Running状态并开始执行,执行过程中可能会被更高优先级打断。被打断后,正在运行的任务转为Running状态。同时只能有一个任务处于Running状态。任务结束后,即进入Suspended状态,开始新一轮循环。
扩展任务会从Running自行终止,转而等待事件,进入Waiting状态。操作系统重新从Ready任务中选择优先级最高的来执行。等待的事件到来后,任务从Waiting释放为Ready状态,而非直接继续,因为在等待过程中可能有更高优先级的任务被激活并运行。
任务优先级
Autosar OS支持共享优先级,即不同任务可以有相同的优先级。相同优先级任务按照FIFO原则顺序执行。
提示: 为确保OS效率,共享优先级任务和队列任务激活最好不要同时使用。如果要实现一系列顺序执行任务,最好使用不同优先级任务,配合Autosar OS内部资源实现,确保执行顺序。
提示: 共享优先级并非一个好的实现方式,因为任务的释放时间点无法计算,不能精准设定每个任务的截止时间。
队列任务激活
大部分情况下任务只允许从终止状态激活,从其他状态激活会被OS视为错误。但是实际使用时会有上一次任务没执行完下一次激活请求就开始的情况,Autosar OS提供了队列任务激活实现这一功能。
队列激活只针对Basic任务,且需配置队列最大长度。达到长度后,再接收到新的激活请求时,会被视为错误。
当一个任务使用队列激活,并与其他任务共享优先级时,相同优先级内所有任务会共同组成FIFO队列依次激活,队列长度等于优先级内所有任务队列长度之和。同时每个任务仍受该任务自身队列长度限制。
异步任务激活
Autosar OS允许一个核激活另一个核的任务。因为Autosar OS要求在任务状态更新之前,请求方必须保持阻塞,而跨核激活任务时请求激活的核需要用自旋锁与被请求的核同步状态,所有核的性能都会受到影响。
RTA-OS提供了一个异步任务激活方式来代替用自旋锁阻塞请求核的方式。请求核首先以队列方式向被请求核发送请求消息。被请求核收到消息后实际执行激活动作。
性能提升
应尽量使用Basic任务,避免使用共享优先级和队列激活,来提升性能和减少内存消耗。
符合类
功能约多的符合类性能表现越差,RTA-OS会自动根据配置情况得出当前所需的功能最少的符合类,来实现最大性能。
任务配置
Autosar OS中任务大部分属性为静态配置,可以离线计算并存储在ROM中。Autosar任务包含五大属性:
- Name
- Priority
- Scheduling: 打断/非打断
- Activations: 任务队列长度
- Autostart: OS启动时自动开始任务
调度策略
任务分为打断和非打断任务,非打断任务效率较低,可以考虑其他方式如:
- 用标准资源(standrand resources)来为数据和设备访问排序
- 用内部资源(internal resources)来指定具体哪些资源不能被打断
队列激活
任务设置为队列激活时,自动指定为BCC2
自动开始任务
自动开始任务在StartOS()时即被自动激活。对于基础任务,自动开始会使其进入终止状态前只执行一次。自动开始主要用于开启需要等待事件的扩展任务,免去手写代码激活任务。RTA OS可以指定任务在哪种应用模式(application mode)下自动开始。
栈管理
RTA OS使用单栈模型,所有任务和中断均使用同一个栈来运行。任务运行和终止的过程伴随着入栈出栈,和普通函数调用没有区别。栈大小数量应该与优先级的数量成正比,而非任务和中断数量,因为相同优先级的任务不会同时出现在栈中。
扩展任务对应的栈工作方式
- 对于基础任务,进入running状态时直接依次入栈,加入当前栈的最顶端;任务终止时出栈即可。
- 对于扩展任务,入栈的时候要预留出最差情况下(Waiting过程中所有可能激活的低优先级任务全部激活,且都没有执行完就被打断)比它优先级低的任务可能使用的栈空间,以此确保在等待过程中栈永远不会使用到它当前位置,让等待结束后它可以把栈内容恢复到原位置。到达等待事件后,将当前栈内容全部移入OS内部buffer,并将该部分内容出栈,等待事件到来,且任务重新开始执行后,再将buffer中内容全部移回原来位置。
必需的栈信息
因为扩展任务入栈位置要根据最差情况决定,要计算入栈位置,就需要知道所有任务实际占用栈空间是多少,这需要读取target/compiler接口来获取。
指定任务在栈中定位
任务在栈中的开销包括:
- OS上下文内容
- 局部变量和函数体(入口地址、参数、返回值等)
- 函数内部调用的其他函数所需的开销
RTA-OS可以精确测量上述值
优化扩展任务上下文存储
栈超限处理
如果栈定位有误,可能出现以下三种情况:
- 扩展任务无法启动,因为当前栈顶已经超过了最差情况栈空间
- 扩展任务等待后无法恢复
- 扩展任务无法进入等待状态
当RTA OS检测到扩展任务栈管理异常时,会调用ShutdownOS(). 用户可在次过程中添加hook用于调式和处理等。
任务实现
#include <Os.h>
TASK(task_identifier) {
/* your code */
}
TASK(BCC_Task) {
do_something();
/* Task must finish with TerminateTask() or equivalent. */
TerminateTask();
}
TASK(ECC_Task) {
InitializeTheTask();
while (WaitEvent(SomeEvent)==E_OK) {
do_something();
ClearEvent(SomeEvent);
} /* Task never terminates. */
}
激活任务
任务可以由任务或二类中断来激活,被激活后的任务会进入ready状态或进入ready队列等待。在一个好的设计中,任务不应该激活比自己优先级高的任务,因为这会导致当前任务立刻被打断执行新任务。由高优先级任务或中断来激活其他任务就不会有该问题。
直接激活
通过调用API来激活任务。
- ActivateTask(): 直接激活指定任务;
- ChainTask(): 终止当前运行任务并激活指定任务。
间接激活
- 通过警报激活
- 通过调度表激活
控制任务执行顺序
链式直接激活
每个任务结束前依次直接激活下一个任务,来确保执行顺序。
#include <Os.h>
TASK(Task1) {
/* Task1 functionality. */
ActivateTask(Task2); /* Runs when Task1 terminates. */
/* More Task1 functionality. */
ActivateTask(Task3); /* Runs when Task2 terminates. */
TerminateTask();
}
TASK(Task2) {
/* Task2 functionality. */
TerminateTask();
}
TASK(Task3) {
/* Task3 functionality. */
TerminateTask();
}
设定任务优先级
TASK(Task1) {
/* Task1 functionality. */
ActivateTask(Task2); /* Runs when Task1 terminates. */
/* More Task1 functionality. */
ActivateTask(Task3); /* Runs when Task2 terminates. */
TerminateTask();
}
TASK(Task2) {
/* Task2 functionality. */
TerminateTask();
}
TASK(Task3) {
/* Task3 functionality. */
TerminateTask();
}
非打断任务
依次执行非打断任务,来确保执行顺序。
混合式调度
非打断任务中增加Schedule点可供打断。
任务终止
任务终止必须通过调用以下两个API之一来实现:
- TerminateTask(): 终止任务,执行下一个优先级最高的Ready任务。
- ChainTask(TaskID): 终止任务,执行参数中任务。
终止API可在任务中任何地方执行,包括Task中嵌套的其他函数,但在Task入口函数执行完成后运行终止API,可减少非必要的上下文存储,效率最高。
延迟任务
可以通过以下三个API来设置延迟任务:
- Os_SetDelayedTasks()
- Os_AddDelayedTasks()
- Os_RemoveDelayedTasks()
![[Pasted image 20230310100804.png]]
空闲机制
系统默认在空闲状态下不做任何事,只做空循环。但是RTA-OS提供了一个Os_Cbk_Idle,可以在空闲中执行。它基本等同于一个优先级比所有其他任务都低的任务,除了以下几点:
- 不能被激活
- 不能被终止
- 不能等待事件
- 不能链式触发其他任务
- 不能使用内部资源
此外,Os_Cbk_Idle会提供一个布尔返回值,如果为True,则继续执行下一次,如果为False,则系统恢复到空闲空循环状态。
任务开始前/结束后Hook
RTA-OS为所有任务提供了一组公用的hook函数,可以用于记录任务打断/恢复情况:
- 开始前Hook在每个任务转为running状态时执行(包括被打断后恢复时);
- 结束后Hook在每个任务退出running状态时执行,包括任务完成和被打断时。
在打断时保存硬件寄存器
在打断时,除了将上下文入栈以外,可能还需要将一些硬件寄存器中的数据保存(例如:浮点寄存器内容)。可以在配置工具中指定有哪些寄存器集存在,并与任务进行关联,由RTA-OS来自动决定保存时机。
中断
中断为现实世界和软件之间提供了一个接口。
单优先级和多优先级平台
- 单优先级:中断按顺序依次执行;
- 多优先级:中断可以被更高优先级中断打断,即支持中断嵌套。
ISR(Interrupt Service Routines)
ISRs和任务的区别在于:
- 不能通过RTA-OS API激活;
- 不能调用任务终止API;
- ISRs从对应中断优先级的入口点(中断向量表)开始执行;
- 只能调用一部分RTA-OS API。
一类中断和二类中断
一类中断(CAT1)
一类中断不与OS交互,始终以程序中最高优先级执行。OS察觉不到一类中断的运行,只能将其运行时间计算到被其打断的任务或二类中断里。
二类中断(CAT2)
二类中断的中断向量表指向OS内部,中断触发后,由OS内部代码调用中断处理流程,中断处理执行完成后,再次执行OS内部代码从中断中返回。
中断优先级
IPL(Interrupt Priority Level)为0时表示user level,所有任务均使用该IPL;1及以上为interrupt level。IPL不等于任务优先级,IPL为1时即大于任何任务优先级了。
对于单优先级平台,IPL只有0和1两个值;对于多优先级平台,高优先级中断可以打断低优先级中断。
一类中断绝不能被二类中断打断,因为一类中断执行结束后会直接切换至被它打断的任务,而不是当前优先级最高的任务。也就是说,所有CAT2中断的IPL必须小于所有CAT1中断的IPL。
User Level
用户等级是最低的中断等级,允许所有中断处理,所有任务都运行在用户等级下。但是某些情况中,任务也会利用资源机制提升运行等级。等级提升后,只有IPL高于该等级的中断才会打断该任务。
OS Level
系统中二类中断的最高优先级即为OS Level,RTA-OS使用OS Level来阻止对OS内部数据的并发访问。
使能和禁用中断
禁用中断并不能阻止中断发生,只是禁止CPU处理中断。禁用/使能中断可以用以下三组API实现:
- DisableAllInterrupts() and EnableAllInterrupts():不支持嵌套,DisableAllInterrupts之后,除了EnableAllInterrupts之外不能调用其他OS API;
- SuspendAllInterrupts() and ResumeAllInterrupts():支持嵌套,SuspendAllInterrupts之后,除了SuspendAllInterrupts() / ResumeAllInterrupts(), SuspendOSInterrupts() / ResumeOSInterrupts()之外不能调用其他OS API;
- SuspendOSInterrupts() and ResumeOSInterrupts(): 支持嵌套,只针对所有二类中断,SuspendOSInterrupts之后,除了SuspendAllInterrupts() / ResumeAllInterrupts(), SuspendOSInterrupts() / ResumeOSInterrupts()之外不能调用其他OS API。
保存寄存器内容
该功能和Task中保存硬件寄存器功能作用一致。
默认中断
中断向量表中所有未使用的位置,均可以用同一个默认中断handler来填充。可以用于调试或防止非预期中断触发导致运行错误。