引言
汽车电子实时操作系统为汽车电子软件的复杂性和实时性提供了保障,但由于不同的操作系统没有统一的应用程序接口,所以难以保证汽车电子软件的可移植性[1]。1993年5月,德国汽车工业界联合推出了汽车电子类开放系统和对应接口(Open Systems and the Corresponding Interfaces for Automotive Electronics,OSEK)标准。后来,法国汽车工业界使用的分布式执行系统(Vehicle Distributed eXecutive,VDX)也加入其中,共同组成了OSEK/VDX标准[2]。OSEK/VDX标准诞生之后,引起了国内外各大厂商和高校的广泛关注。德国3soft公司开发的ProOSEK是全世界最早的OSEK/VDX操作系统,为BMW、VM/Audi、DaimlerChrysler提供了基于OSEK的软件开发平台;Freescale公司开发的OSEKTurbo是目前市场上使用最为广泛的OSEK/VDX操作系统,支持8、16和32位微处理器,在稳定性和软件质量方面表现出色。符合OSEK/VDX标准的操作系统还有Wind River公司的OSEKWorks、德国Vector公司的OSCAN以及国内浙江大学开发的SmartOSEK OS等。此外,一些开源项目比如法国的Trampoline、瑞典的Arctic Core等也对OSEK/VDX标准作了相应的研究。
这些优秀的汽车电子实时操作系统通常可以分为两类:一类虽然已经通过OSEK/VDX官方的认证,但是源代码不公开,无法知道其详细的内部实现,因此对于使用过程中可能存在的安全隐患或者系统缺陷无法预知和判断,存在不确定性;另一类虽然源代码公开,但是却没有通过OSEK/VDX官方的认证,一旦出现问题,很难得到有效的系统维护。由于OSEK OS规范是OSEK/VDX标准的重要组成部分,所以北京理工大学计算机学院和机械与车辆学院针对OSEK OS规范进行相应研究,以32位PowerPC处理器为核心,联合开发了一个符合OSEK OS规范的汽车电子实时操作系统PCM_OS(Operating System for Powertrain Control Module)。根据OSEK OS规范,PCM_OS操作系统主要分为任务、中断、资源、事件、计数器和警报6个模块。
在设计思想上,PCM_OS操作系统受国内RTThread实时操作系统[3]将内核对象抽象和继承的设计思路的启发,进一步地加入多态特性,用C和少量的汇编语言完全体现了基于面向对象的程序设计思想。在任务模块的设计上,参考文献[4]提出根据任务的状态(运行态、就绪态、等待态或挂起态)来优化任务切换的性能,参考文献[5]提出用PowerPC汇编指令cntlzw(Count Leading Zeros Word,数出前导零数目)来优化μC/OSII操作系统在PowerPC上的性能。PCM_OS操作系统在此基础上加入同优先级的任务调度机制,设计了一种优先级数目从16至1 024个可配置,支持不同优先级任务根据优先级调度、同一优先级任务根据时间片轮询调度的任务调度机制。在中断模块的设计上,一方面PCM_OS操作系统根据OSEK OS规范[6]将中断服务程序分为ISR1类中断服务程序和ISR2类中断服务程序。参考文献[7]描述了Trampoline操作系统实现的一种延迟的ISR1类和ISR2类中断处理机制。PCM_OS操作系统改进了ISR2类中断处理机制的设计,实现了一种三阶段处理策略,其中第一阶段和第二阶段运行在中断级别,第三阶段运行在任务级别。另一方面,为了减少中断嵌套和中断处理对任务空间的占用[8],PCM_OS操作系统采用软件模拟的方式实现了单独的中断堆栈[9]。在资源模块的设计上,参考文献[10]和[11]分别描述了常用的优先级继承协议(Priority Inheritance Protocols,PIP)和优先级天花板协议(Priority Ceiling Protocol,PCP)来保证任务和任务之间对共享资源的互斥访问。为了保证任务和任务、任务和中断以及中断和中断之间对共享资源的互斥访问,PCM_OS操作系统设计了扩展的优先级天花板协议。在计数器模块的设计上,PCM_OS操作系统以固定时间间隔计数器(FixedInterval Timer,FIT)和递减计数器(Decrementer,DEC)为基础分别设计了两个具有不同计数精度的系统计数器OS_FIT和OS_DEC,并把它们作为警报定时的基础以加快警报的响应速度。在警报模块的设计上,一方面,根据警报定时的精度将警报以差分时间链[12]的方式链接到系统计数器OS_FIT或OS_DEC上。另一方面,PCM_OS操作系统利用软中断将警报的处理分成两个阶段:第一阶段在计数器的中断服务程序中,只是简单地记录当前触发的警报的ID值;相应的进一步处理在第二阶段的软中断服务程序中进行,以减少计数器中断关中断的时间。
图1 任务、中断等类之间的关系图
1 PCM_OS的设计原理
1.1 面向对象的程序设计思想
PCM_OS操作系统的设计是基于面向对象思想的程序设计,系统中的任务、中断、资源、计数器和警报等都被封装成派生类,继承自基类OS_Object,它们之间的关系如图1所示。基类OS_Object有3个公有属性和2个公有方法,其作用分别如下:
① 属性id:它用唯一标识代表一个对象,这些对象包括任务、中断、资源、计数器或警报等;
② 属性type:它表示当前对象的类型,可能是任务、中断、资源、计数器或警报对象中的一种;
③ 属性list:它是一个通用的双向循环链表结构[13],该结构与对象的结构无关;
④ 方法EnQueue:它用于将对象自身加入到一直驻留系统中的对象就绪队列中;
⑤ 方法DeQueue:它用于将对象自身从对象就绪队列中删除。
由于PCM_OS操作系统使用C语言和部分汇编语言进行编程,所以在面向对象的程序设计上不可能和C++、Java一样方便灵活,比如C语言中没有this指针等等。但是,C语言中仍然有很多技巧可以实现面向对象的继承、封装和多态特性。
1.2 任务模块设计
PCM_OS操作系统通过优先级和时间片来调度任务,具有高优先级(根据OSEK OS规范,数值越大,优先级越高,0为最低优先级)的任务优先被调度,具有相同优先级的任务之间以时间片被轮询调度。处于就绪态的任务之间的关系略——编者注。每个优先级对应一个任务就绪队列,相同优先级的任务之间通过通用的双向循环链表依次挂载到其优先级对应的任务就绪队列中。假设系统支持的优先级数目为N,则优先级0~N-1被分为以下几个部分:
① 优先级0:留给系统常驻任务OS_IDLE使用,当系统空闲时,该任务运行;
② 优先级1~2:系统保留;
③ 优先级3~3N/4-3:被用户任务使用;
④ 优先级3N/4:资源RES_SCHEDULER(OSEK OS规定的调度器资源)的天花板优先级;
⑤ 优先级3N/4+1~N-3:被ISR2类中断使用;
⑥ 优先级N-2~N~1:系统保留。
如何快速地从这些任务就绪队列中取出优先级最高的就绪任务是制约任务实时调度的关键。与μC/OSII的位图机制[14]类似,PCM_OS操作系统也有两个位图,即UINT32 gOS_TaskRdyGrp和UINT32 gOS_TaskRdyTbl[N/32]。其中每32个优先级为一组,变量gOS_TaskRdyGrp的每个二进制位标识每个组中是否存在处于就绪态的任务,而数组gOS_TaskRdyTbl的每个元素的32个二进制位正好能够标识组内32个不同的优先级是否有任务处于就绪态。由于32位的PowerPC处理器中有一条汇编指令cntlzw,它能够数出一个32位寄存器(64位寄存器只影响低字)前导零的数目。所以,PCM_OS操作系统充分利用cntlzw指令寻找处于就绪态任务的最高优先级,然后根据最高优先级从对应的任务就绪队列的首部取出即将要调度的就绪任务,图略——编者注。利用cntlzw指令寻找处于就绪态任务的最高优先级的算法如下。
输入:位图gOS_TaskRdyGrp和gOS_TaskRdyTbl [N/32]
输出:处于就绪态的任务的最高优先级prio
BEGIN
计算gOS_TaskRdyGrp前导零的个数m;
计算拥有最高优先级的就绪任务处于哪个组内,grp=31-m;
计算gOS_TaskRdyTbl[grp]前导零的个数n;
计算拥有最高优先级的就绪任务处于grp组内的哪个位置,bit=31-n;
计算就绪任务的最高优先级prio=(grp<<5)+bit;
END
每个处于运行态的任务都有一个固定大小的时间片(128 μs)。当时间片耗尽时,当前运行的任务由其优先级对应的任务就绪队列的首部移至尾部,并从该任务就绪队列的首部重新取出第一个任务作为下一个即将运行的任务。
1.3 中断模块设计
按照OSEK OS规范,PCM_OS操作系统将中断服务程序分为ISR1类中断服务程序和ISR2类中断服务程序,而且只有在ISR2类中断服务程序中才可以使用操作系统提供的服务(即操作系统定义的应用程序接口),因此只有ISR2类中断能够引起任务的重新调度。
在PCM_OS操作系统中,ISR1类中断服务程序是在中断级别运行的,支持中断嵌套,且响应时间非常短。而ISR2类中断是分三个阶段响应的:第一阶段为外部中断服务程序,只是简单地记录当前中断的向量号,并激活软中断1,然后迅速地退出当前中断以响应优先级更低的外部中断;第二阶段为软中断1的服务程序,根据记录的中断向量号依次激活对应的ISR2任务,然后将ISR2任务像普通任务一样根据中断的优先级进行调度;第三阶段以ISR2任务的形式执行具体的中断服务程序。由于ISR1类中断会打断ISR2类中断的第三阶段的执行,所以PCM_OS操作系统强制规定ISR1类中断的优先级要大于ISR2类中断的优先级。
由于PCM_OS操作系统支持中断嵌套,而每一层的中断嵌套和中断处理都会占用当前任务的堆栈空间,造成任务堆栈空间大小的不确定,所以为了减少中断嵌套和中断处理对任务空间的浪费,PCM_OS操作系统利用软件模拟的方式设计了单独的中断堆栈,从而将中断堆栈与任务堆栈分离。然而,采用单独的中断堆栈带来的不足是每次进入和离开中断都要进行堆栈指针的调整。PCM_OS操作系统的中断处理流程图略——编者注。
PCM_OS操作系统运行在软中断向量模式[15]下,所有的ISR1类和ISR2类中断共用操作系统设定的中断预处理代码。ISR1类中断的处理遵循过程1,而ISR2类中断的处理分三个阶段执行,其中第一阶段遵循过程1,第二阶段遵循过程2,而第三阶段在任务级别运行。特殊地,软中断1的中断优先级低于所有的ISR1类和ISR2类中断的优先级。
过程 1ISR1类中断处理过程(或ISR2类中断处理过程的第一阶段)
BEGIN
根据中断向量表跳到中断向量入口地址处;
保存上下文环境到当前的任务堆栈中;
中断嵌套层数++;
IF 中断嵌套层数=1,THEN
DO 保存SP指针并将SP指针指向中断堆栈;
ENDIF
开中断;
跳转到InterruptTopHandler函数,判断中断类型type;
IF type=ISR1,THEN
DO 中断服务程序;
ELSE
记录中断ID;
激活软中断1;
ENDIF
关中断;
中断嵌套层数;
IF 中断嵌套层数=0,THEN
DO 恢复SP指针指向当前任务堆栈;
ENDIF
恢复上下文环境;
中断返回
END
过程 2ISR2类中断处理过程的第二阶段
BEGIN
跳到软中断1的入口地址;
保存上下文环境;
中断嵌套层数++;
IF 中断嵌套层数=1,THEN
DO 保存SP指针并将SP指针指向中断堆栈
ENDIF
开中断;
跳转到InterruptBottomHandler函数;
根据记录的中断ID依次激活对应的ISR2任务;
关中断;
中断嵌套层数;
寻找拥有最高优先级的处于就绪态的任务;
执行中断级任务切换;
END
资源和事件模块,以及计数器和警报模块的设计略——编者注。
结语
OSEK/VDX标准规范了汽车电子软件的开发,提高了代码的复用率,降低了软件的开发成本,缩短了软件的开发周期。本文详细介绍了32位PowerPC处理器上基于OSEK/VDX标准开发的汽车电子实时操作系统的设计原理和移植测试情况,对以OSEK/VDX标准为基础的汽车电子实时操作系统的开发具有借鉴意义。