OSEK/VDX标准产生以后,很多操作系统供应商对其进行研究,并实现了商业化,例如国外:Elektrobit Auto—motive、 Accelerated Technology、dSPACE、 ETAS、Metrowerks、Hitex、iSYSTEM、Lauterbach Datentechnik、Vector、windRiver等公司。这些公司的OSEK操作系统产品都通过了OSEK官方认证,并提供了配套的集成开发环境来方便应用程序的开发。
国内依据OSEK/VDX开发的操作系统内核,大部分参考了μC/0S操作系统内核的设计或者是在它的基础上修改的,也有在Linux的基础上修改的。国内OSEK操作系统配套的集成开发环境很少,目前有浙江大学开发的SmartIDE、清华大学开发的清华车用集成开发环境等,其他的基本上没有配套的集成开发环境。因此目前国内对OSEK标准的研究和应用还处于初级阶段。
由于购买商业化的OSEK需要大量资金,并且很多时候不能获取源代码,因此希望有一些开源OSEK操作系统来满足研究的需要。现有的OSEK操作系统的开源实现,有PICosl8,一个OSEK/VDX OS 2.1r1的部分实现。它专门为PICl8XXX系列微控制器设计,针对该系列微控制器硬件,特别是在内存使用方面作了高度优化。但是,它的定制能力很差,并且移植到其他平台时代码修改量很大。另外还有openOSEK项目,但是该项目一直没有开发出正式发布的版本。
Trampoline主要是由法国通信研究所(IRCCyN)的实时系统组负责开发的。它的源代码是开放的,能够兼容OSEK/VDX OS 2.2.3标准(当前最新版本为1.1)。它目前还没有通过OSEK/VDX的官方认证,因此只能说它兼容OSEK标准,而不能说它是通过OSEK认证的操作系统。Trampoline目前能够在4个平台上运行,分别是POSIX(包括Linux、Unix、Mac OS等)系列操作系统平台、Infineon C166平台、Freescale s12x平台、Darwin/PowerPC平台,其中在POSIX系列操作系统上运行时需要libpcl库的支持。因此,即使没有微控制器,也可以在常见的Linux/x86平台上使用Trampoline。
要对整个OSEK/VDX标准进行研究,对OSEK操作系统的研究是基础。只有有了一个可以使用的OSEK操作系统,才能在它的基础上进行COM、NM及OIL标准的研究,才能进行OSEK操作系统应用程序开发、汽车电子集成开发环境的开发等方面的研究。Trdmpo1ine能够在Linux平台上运行,并且有一个可用的GOIL OIL文件解析器,可以作为进行OSEK/VDX标准研究的基础。
2 OSEK OS及Trampoline分析
OSEK操作系统是一种单处理器操作系统,主要用于汽车电子的分布电子控制单元ECU(E1ectronic Contr01 Unit)上。
OSEK操作系统对不同版本OSEK操作系统提供的应用程序接口进行了标准化,这样,为一个OSEK操作系统设计的应用程序不用修改就可以移植到其他OSEK操作系统上运行。 为了使OSEK操作系统能够在有不同计算能力(不同CPU、不同存储容量)的ECU上运行,OSEK定义了4个符合类,分别是BCCl、ECCl、BCC2、ECC2。每一个符合类包括一组操作系统特性,代表一类应用程序的需求,也包含了这些操作系统特性运行所需要的硬件需求。使用符合类的概念既方便用户根据自己的需求及自己的硬件条件来选择符合自己需要的操作系统特性集合,也使得操作系统供应商可以先提供一部分操作系统,也就是先提供对部分符合类的支持,然后再增加更多操作系统特性,支持其他符合类,从而方便了操作系统的开。 OSEK操作系统是静态配置的,而不是动态生成的,用户在系统生成阶段能够使用OIL语言对系统进行配置,指定系统里使用多少个任务、使用哪些资源、需要调用哪些系统服务等。通过这种方式,用户可以选择需要的部分,裁剪掉不需要的,以减少不必要的系统资源。另外,由于系统是静态生成的,既减少了动态生成系统对象的开销,减少系统内存的使用,又可以使系统运行时的行为可预知,提高系统的可靠性和确定性。
由于OSEK操作系统是静态生成的,系统运行时的很多信息在系统生成阶段就可以确定,于是它提供的任务间同步和通信方法比其他常用的嵌入式实时操作系统要少。它用OSEK PCP(Priority Ceiling Protocol)协议来同步资源的并发访问,从而提供了任务与ISR 之间及ISR与ISR之间共享资源的机制。另外,OSEK操作系统还提供了完善的错误处理机制,提高了系统的可靠性和容错能力。
Trampoline完全支持OSEK标准要求,实现了OSEK操作系统统一的API接口,支持静态配置,支持4个符合类,支持OSEK PCP协议。另外,Trampoline的设计还考虑到两个方面——高可移植性和减少内存使用量。
为了达到高可移植性,Trampoline设计了一个硬件抽象层来隔离底层的硬件差异,把平台有关的代码与平台无关的代码进行隔离。把Trampoline从一个目标平台移植到另一个目标平台,仅需要把与目标平台有关的那部分代码改写一下就可以了,硬件抽象层之上的那部分不用修改,这大大减少了操作系统移植的工作量。在Trampoline代码的组织中,不同目标平台代码放在不同的文件中,分离得很清楚。与目标平台有关的代码,仅仅是任务上下文切换、操作系统初始化及一些与硬件相关的函数(中断使能、睡眠模式等)代码。这部分代码量减到了罪少。
由于车载嵌入式系统中的微控制器RAM容量很小,一般从几百字节到几K字节,而增加RAM容量会增加产品的成本,在产品批量生产时往往会难以接受。Trampo-line在设计时尽量减少内存的使用,并优化了任务管理和中断管理的数据结构,把一部分不变的内容放到ROM中,以减少RAM的使用要求。
下面着重分析Trampoline最核心的调度机制、任务管理、中断管理的设计与实现。
2.1 调度机制
Trampoline使用静态优先级调度算法。在系统生成阶段,用户为每一个任务分配一个优先级。在不同的符合类下,优先级与任务的对应关系不同。在BCCl和ECCl符合类下,一个优先级仅对应一个任务,不同的任务有不同的优先级,任务之间不能共享优先级;而在BCC2和ECC2符合类下,一个优先级可以对应多个任务,不同的任务可以共享同一个优先级。任务有4种状态:就绪状态、等待状态、挂起状态(仅ECCl和ECC2符合类下有)及运行状态。
由于使用处于等待或者挂起状态的任务时直接给出了该任务结构,因此Trampoline没有使用数据结构来管理等待状态和挂起状态的任务;而对于就绪状态的任务,在不同的符合类下,Trampoline采用了两种不同的数据结构来管理。由于在BCCl和ECCl符合类下不同的任务有不同的优先级,Trampoline使用一个简单的链表,按照任务的优先级由高到低把就绪态任务描述符给连接起来;而在BCC2和ECC2符合类下,几个任务可以共享一个优先级,Trampoline使用了一个任务子集链表数据结构来组织就绪任务。共享一个优先级的任务组成了一个任务子集,它们也组成了一个链表。然后把不同子集的链表表头按优先级由高到低链接起来,组成了所有就绪任务的链表,如图1所示。由于按照优先级由高到低的顺序来组织任务子集链表,因此最高优先级的任务总是在链表头部,这样会使调度器能快速选取到最高优先级的任务,但也会导致低优先级任务选取得很慢。
Trampoline使用一个tpLrLinning—task指针指向当前正在运行的任务。调度器管理着就绪任务的集合,当重新调度发生时,从就绪任务集合中选取一个最高优先级的任务来执行,并把它从就绪任务集合里删除。然后,tpl_run-nling_task指针指向该任务,并把任务的状态由就绪态改为运行态。该任务将一直处在运行状态,直到运行结束或一个系统服务阻塞了它的执行,或被一个更高优先级任务抢占。另外,一个任务可以是不可抢占的。在这种情况下,它将一直占有CPU,直到运行结束(即使有一个更高优先级就绪任务在等待)。Trampoline也支持使用任务组的结构来实现混合调度。在这种调度模式下,把所有就绪任务分成不同的任务组,同一个任务组里的任务之间是不可抢占的,但它可以被这个组外的更高优先级任务抢占。
2.2 任务管理
Trampoline使用任务描述符结构(struct tpl_task)来管理任务的信息,其中包括系统运行时不断变化的信息,如任务状态、任务优先级、任务的激活次数、任务的资源、任务的事件等;还包括系统运行时不变的信息,如任务的上下文、任务的堆栈、任务代码段入口地址、任务ID、任务基础优先级、最大激活次数和类型等信息。为了减少内存的使用,Trampoline任务描述符结构被分成图2所示的两个部分:第一部分是系统运行时不断变化的数据,保存在tpl_exec_common结构里,它必须常驻RAM中;另一部分是在系统运行时不变的部分,保存在tpl_execl_static结构里。在tpl_exec_common结构里设置了一个指针staticdesc,指向任务的tpl_exec_static结构。由于tpl_exec_static里存放的信息在系统运行时是不变的,因此可以把这部分放到ROM里,以节省RAM的使用。在tpl_exec_static结构里有两部分是体系结构相关的,即上下文结构context和堆栈结构stack,它们使用指向一个或多个RAM区域的指针来保存任务执行的上下文和堆栈信息。这种设计使得不同的任务之间可以通过共享指向上下文或堆栈结构的指针就能共享上下文和堆栈,从而可以减少任务上下文和堆栈所占用的存储空间。另外,Trampoline上下文结构的设计可以使用尽可能少的RAM。例如,如果目标平台处理器没有FPU(浮点处理器),Trampoline上下文结构有两个指针,第一个指向整数上下文的RAM区域,第二个指向浮点上下文RAM区域,这些RAM区域都是用来保存任务运行时要使用的整数寄存器或浮点寄存器的。然而,并不是每个任务都需要使用浮点寄存器,如果任务没有使用FPU,第二个指针将会设为空,以避免分配浮点寄存器所占用的RAM空间。任务上下文和堆栈结构都属于与体系结构有关的代码,内核不直接同这部分打交道,而是通过硬件抽象层来使用它们。这样,使得与体系结构相关的代码与无关的代码隔离起来,从而便于把它移植到其他平台。
2.3 中断管理
在OSEK操作系统中,ISR(Interrupt Service Rou-tine)分成了两类,即ISRl和ISR2。ISRl不使用操作系统服务,也不能调用其他的用户定义函数。该类中断服务例程执行完了以后直接执行中断发生位置后的下一条指令,因此ISRl对任务管理没有影响,运行时消耗的资源也比较少。ISR2是可以调用其他用户定义的函数或使用部分OSEK服务的中断例程,OSEK操作系统专门为它准备了一个堆栈Frame,用作调用其他函数的执行环境。在系统生成阶段,由用户指定ISR2要调用的用户定义函数或系统调用。ISR2能够和任务之间共享资源,而这可能会造成死锁:当ISR2启动后试图获得一个已经被一个任务占用的资源时,该任务也在等待中断完成,因此ISR2和任务之间共享资源时需要使用同步机制。OSEK操作系统提供了用于资源访问的GetResource和ReleaseResource系统调用,任务和ISR2之间可以使用它们来共享资源,但是这种方法需要关闭访问资源的中断,可能使中断长时间关闭,降低了操作系统的实时响应能力。另一种任务和ISR2之间共享资源的方法是OSEK标准所建议的方法,也就是使用OSEK PCP协议。使用这种方法时,当一个任务要获取同ISR2共享的资源时,会把它的优先级提升到比ISR2更高的优先级,当任务执行完成之后,再把优先级恢复到原来的优先级。这时ISR2更像任务,但是比普通任务有更高的优先级。
Trampoline实现了一种延迟的ISRl和ISR2,从而使操作系统内核更小。任务和ISR的描述符都继承自一个tpl_exec_common结构,如图2所示。任务描述符在tpl_exec_common结构里增加了事件管理的数据成员,也就是evt_set和evt_wait数据成员;而ISR描述符在tpl_exec_common结构里增加了一个指向附加数据的指针,也就是static_isr_desc数据成员,static_isr_desc指向的内容可以放到ROM中,以减少RAM的使用。一个ISR对应着一个中断向量。当一个中断触发时,Trampoline激活对应的ISR中断服务例程并且返回。如果是ISRl,执行完了以后将执行触发中断位置后面的代码;如果是ISR2,ISR2将运行预先定义的用户定义函数或者系统服务,然后像普通任务一样由调度器根据任务级的调度策略来调度执行。
另外,Trampoline为ISR增加了一个抽象层。这样,一方面几个硬件中断可以共享相同的中断向量偏移,另一方面对应到一个中断向量偏移的,有一个ISR的集合,而不是一个ISR。当一个硬件中断触发时,为了找到一个与该硬件中断匹配的ISR,每组共享中断偏移的ISR都必须提供一个函数来测试它对应的设备中断标志是否为真。如果函数返回TRUE,该ISR将被激活。Trampoline设计了一种GIH(General Interrupt Handler)函数来完成这种测试工作。
而这样做有两个问题。第一,由于ISR2的后期执行是在任务态运行,这时如果有一个硬件中断触发,就会由GIH来确定一个ISR来执行,从而打断了原来的ISR2。即使后来触发的ISR的优先级比原来ISR2的优先级低,这种情况也能发生。这就造成了一个低优先级的硬件中断抢占了高优先级的中断,而这种情况是不应该发生的,因此是一个很大的问题。第二,根据OSEK操作系统标准,当ISR运行时,不能进行重新调度。在Trampoline中,ISR2作为高优先级的普通任务进行调度,当一个高优先级ISR2到来时,任务调度器会重新调度一次,从而打断了原来的ISR2的执行。另外,在OSEK操作系统标准中,重新调度是在任务之间的重新调度;而在Trampoline中,只要有一个ISR2在运行,重新调度只能在有比普通任务更高优先级的ISR2之间进行。当最后运行的ISR2结束时,CPU的重新调度才给了有最高优先级的任务,因此,Trampoline的中断管理部分的实现还有待改进。
3在Linux/x86上开发Trampoline应用程序
Trampoline目前可以在四种目标平台上使用:带有Keil编译器的Infineon C167、Darwin/PowerPC、Freescale$12x和POSIX系列操作系统平台。前三种平台的硬件不常见,如果没有就不能运行;而POSIX系列的Linux/x86平台则很容易得到。下面以Linux/x86平台为例,说明开发一个Trampoline应用程序的步骤和方法:
①生成应用程序的OIL配置文件。OSEK/VDX OS是一个静态操作系统,系统对象需要在系统生成时定义。OIL是书写这种定义的标准语言。它可以定义所有的应用程序使用的各种对象(任务、中断、警报、计数器、资源、事件等)。OIL配置文件可以手工编写,也可以使用图形化开发配置工具来生成。目前Trampoline没有图形化配置工具,只能手工编写OIL配置文件。
②使用OII文件解析器GOIL将应用程序的OIL文件转化为一个.c文件和.h文件,其主要功能是进行与应用程序相关的各种系统对象参数的定义、初始化等工作。
③使用GCC工具链将②生成的文件和Trampoline操作系统内核文件及libpcl库文件、VIPER虚拟处理器文件等进行编译和链接,生成一个Linux平台的可执行文件,也就是最终的应用程序可执行文件。