,,,
(1.西安工业大学 计算机科学与工程学院,西安 710021; 2.西北工业大学 计算机学院,西安 710072)
在实时操作系统中,事件触发指一个任务只有在与之相关的特定事件发生的条件下才能开始运行。早在最初的嵌入式领域中,事件触发机制已经得到了广泛应用[1],然而在航空、航天及核电等领域,由于系统的高可靠性与硬实时性要求,事件触发方式无论在设计,还是维护方面都存在较大困难[2]。许多任务可能因为等待资源而超时,这样就无法满足任务的实时性要求,更无法保证安全关键任务的完成。为此,很多基于事件触发的操作系统变得愈发复杂,不仅降低了研发效率,而且增加了维护成本[3]。
时间触发机制指在预先计划好的时间点执行系统行为,从而使系统具有良好的先验性和确定性[4]。时间触发设计思想中时间可控的特点从根本上提高了系统的实时性,避免了可能的直接冲突。因此,对于任务确定的嵌入式系统,时间触发设计思想具有明显优势[5-7]。然而单纯采用时间触发的系统在某种程度上会降低系统的灵活性和动态交互性,很难完全满足多样性的系统需求,尤其针对混合关键任务的执行[8-10]。因此,在实际应用中,往往需要将时间触发和事件触发机制相结合,设计出一种支持时间触发和事件触发的混合调度机制。
由于μC/OS-II是一款仅支持事件触发的开源、可移植、可裁剪的的实时操作系统,通过分析该系统的特点,将其改造为支持两种触发机制的操作系统。
μC/OS-II操作系统中的任务由三个部分组成:任务程序代码,任务堆栈和任务控制块。
任务程序代码通常被设计为一个无限循环结构。在这个循环中可以响应中断。任务堆栈是给每个任务分配的一块独立的连续内存空间,用来在任务切换和响应中断时保存CPU寄存器中的内容,或任务调用其他函数时使用。任务控制块(Task Control Block, TCB)的数据结构OS_TCB,用来记录任务的堆栈指针、任务的当前状态、任务的优先级等一系列与任务管理有关的属性。
任务调度主要包括两部分内容:一是判断哪些任务处于就绪状态;二是从处于就绪态的任务中确定应该马上执行的任务并为其分配CPU。μC/OS-II中完成第一部分工作的是任务就绪表,完成第二部分工作的是调度器。μC/OS-II中设计了一个位图来登记系统中所有处于就绪状态的任务,这个位图就是任务就绪表。位图的每一位代表系统中的每一个任务,该位置的状态(1或0)就表示对应任务是否处于就绪状态。
为了能够同时支持TT(Time-Triggered)任务和ET (Event-Triggered) 任务,将系统分为两大部分:时间触发模块和事件触发模块,这两部分相对独立又紧密联系。以时间触发模块为上层主要模块,事件触发模块为下层基础模块的层次性架构如图1所示。
图1 时间和事件双重触发的系统调度架构图
应用API任务集输入位于整个内核架构的最外层,包括对应用提供的系统服务、系统配置和用户应用程序。系统配置模块可以配置与具体应用相关的参数和选项等,用户应用程序即包括用户创建的任务,其中有时间触发模块提供的API,也有事件触发模块提供的API。
任务调度入口将应用API分为时间触发任务和事件触发任务。在调度器内核中,时间触发模块为TT任务的管理和调度服务,事件触发模块为ET任务进行管理和服务,该模块仍然采用原μC/OS-II中的管理方法。
在TT任务调度中,系统任务仅由预设的时间点来触发,调度器在运行期间每一时刻,都是通过提前计算好的调度表来进行调度决策的。
事件触发模块主要负责ET任务的管理、调度以及与安全性相关的访问控制决策和实施。此外,还包括中断管理和任务间通信等内核与硬件无关的部分。
包括与具体硬件平台相关的任务堆栈管理、任务切换、时钟中断等模块,根据不同的应用环境进行个性化移植。
整个内核中,时间触发模块占主体地位,系统为其预先分配固定的时间槽用以执行时间触发任务,主要保证系统的可靠性和确定性。事件触发模块在时间触发模块的空闲时间内处理,而空闲时间也可以根据时间触发任务的实际运行情况动态调整,提高了系统的灵活性。
时间触发模块主要完成TT任务的管理与调度、任务间的通信以及中断管理等。为了更好的保留μC/OS-II的原有特性和代码结构,时间触发模块的实现是在由改造μC/OS-II实现的事件触发模块之上,另外新建的一个模块。其结构定义如下:
typedefstructos_tt_tcb
{OS_STK* OSTT_TCBStkPtr; /*TT任务的堆栈指针*/
ttTaskTypeOSTT_TCBTaskID; /*任务标识符*/
ttAppModeTypeOSTT_TCBAppMode; /*任务所属应用模式*/
INT8U OSTT_TCBStartTime; /*TT任务的发布时间*/
INT8U OSTT_TCBDeadline; /*TT任务的时限时间*/
ttTaskStateTypeOSTT_TCBState; /*TT任务的状态*/
INT8U criti_level; /*混合关键任务的关键级别*/
OS_TT_TASK_INFO info[MAX_CRITICALITY];
}OS_TT_TCB;
其中OS_TT_TASK_INFO包含时间触发的关键任务处于各个关键级别时的信息,包括任务在各关键级别上的最坏执行时间和后续任务。而常量MAX_CRITICALITY表示系统的最大的关键级别数,如果MAX_CRITICALITY等于1,则表示系统中只有一种关键级别的任务,则整个任务的调度就简化为普通的时间触发任务调度,只需要一张调度表即可。OS_TT_TASK_INFO的定义如下:
typedefstructos_tt_task_info
{ INT8U OSTT_TCBWcet; /*TT任务的最坏执行时间*/
structos_tt_tcb* OSTT_TCBNext; /*指向下一个OS_TT_TCB的指针*/
}OS_TT_TASK_INFO;
和μC/OS-II一样,事先定义了一批时间触发任务控制块,其数量由宏定义OS_TTTASK_MAX静态配置。系统将建立一个空闲TT任务链表和一个TT任务控制块链表来管理这些时间触发任务控制块。
时间触发的任务调度需要多张调度表,每张调度表就是一个任务控制块链表,对应每一个任务的优先级别。为此,设计了以下结构:
typedefstructos_tt_appmode
{
structos_tt_tcb* first[MAX_CRITICALITY]; /*指向任务的头结点*/
}OS_TT_APPMODE;
structos_tt_tcb*类型的数组first包含了指向各张调度表中第一个TT任务控制块的指针,即各个TT任务控制块链表的头结点指针。
为了更方便快捷地管理被抢占的TT任务,设计了任务恢复链表,将被抢占的TT任务的任务控制块链接在一起,其中的任务按照最早时限排列,在恢复任务执行时,总是最先取出表头时限时间最找的任务。TT任务恢复链表由任务恢复链表节点OS_TT_PREEMPTED_TCB链接而成,其定义如下:
typedefstructos_preempted_tt_tcb
{
OS_TT_TCB*OSTT_PTcb; /*指向需要恢复执行的时间触发任务*/
structos_preempted_tt_tcb*OSTT_PNext; /*指向链表中下一个元素*/
}OS_TT_PREEMPTED_TCB;
和空闲TT任务链表类似,设计了一个OS_TT_PREEMPTED_TCB类型的数组OSTTPTCBTbl[],其元素个数由OS_TT_PREEMPTED_MAX静态配置,表示系统最多可以维护被抢占的任务控制块的个数。把各个元素链接成一个链表,这就是空闲TT任务恢复链表,其头指针为OSTTPTCBFreeList,用于任务恢复链表节点的分配;OSTTResumeFirst和OSTTResumeLast分别为任务恢复链表头与表尾指针。
图2 任务控制块链表和任务恢复链表
图2表示了TT任务控制块链表和空闲TT任务链表,任务恢复链表和空闲任务恢复链表及其相互间的关系。其中,OSTTCur表示当前正在运行的TT任务的控制块,OSTTNext指向下一个将要运行的TT任务的控制块。
时间触发模块中的任务调度和事件触发一样,也分为中断级调度和任务级调度。
3.3.1 中断级调度
时间触发模块中,中断级调度主要发生在时钟中断服务程序OSTickISR()中,调度通过ttSchedTick()函数实现。中断服务程序完成后退出中断时,调用ttSchedTick()函数,该函数会确认是否开始新一轮调度、是否有新的TT任务需要运行、是否TT任务都已执行完等各种情况。
1)如果开始新一轮调度,则时钟节拍重置为0,将当前执行任务指针OSTTCur置为NULL,将下一个将要执行的任务指针OSTTNext指向低级调度表中的第一个任务,开始重新执行;
如果不开始新一轮调度:
2)还没到下一个TT任务的触发时间,当前的TT任务还未执行完且执行时间未达到当前级别下的最坏执行时间时,则当前任务继续执行,不进行任务切换;
3)当前的TT任务是高级别安全关键任务,执行时间已达到当前级别下的最坏执行时间而还未执行完时,则系统发生状态切换,提高系统所处关键级别和调度表,根据调度表将OSTTNext指向高级调度表中下一任务,当前任务继续执行,不进行任务切换;
4)如果下一个TT任务触发,则抢占当前的TT任务并进行任务切换;
5)如果当前TT任务已执行完且下一个任务还未到达,则对时间触发模块来说,当前处于空闲时间,将系统切换到事件触发模块,进行ET任务的调度和切换。
中断级调度中的任务切换不需要保存任务上下文,因为任务被中断时已经进行了保存。中断级调度的主要流程图如图3所示。
图3 中断级调度流程图
3.3.2 任务级调度
时间触发模块中,任务级调度发生在任务执行完毕时,调用ttTaskEnd(),可以让系统提前进行任务调度,而不必等时钟中断到来。
1)如果恢复链表为空,说明当前所有TT任务均已执行完毕,则将系统切换到事件触发模块,进行ET任务的调度和切换。
2)若恢复链表不为空,当前结束任务为恢复链表中最后一个任务,则将当前任务从恢复链表中删除,启动ET任务调度和切换。
3)若恢复链表不为空,当前结束任务不是恢复链表中最后一个任务,将当前任务从恢复链表中删除,然后恢复链表中下一个TT任务的执行,进行TT任务的任务切换。
4)若恢复链表不为空,且当前任务不在恢复链表中,则将恢复链表中的第一个任务恢复运行,进行TT任务切换。
任务级调度的主要流程图如图4所示。
图4 任务级调度流程图
将设计的内核移植到Xilinx Virtex-5 FXT FPGA ML507 的评估平台上进行测试。内核在开发板上的移植工作主要通过Xilinx ISE Design Suite 12.3软件完成。实验选取4个事件触发任务(ET任务)和3个时间触发任务(TT任务);任务的各项参数如表1和2所示。
表1 事件触发任务
表2 时间触发任务
ET任务的优先级数字越小表示其优先级越高,实验环境下系统时钟节拍为1 ms,TT任务执行周期为50 ms。在完成多次实验后,选取任务开始执行的第一个周期内的执行结果,实验结果如图5所示。
图5 实验结果图
由实验结果可知,tick = 0时刻,ET任务etTask1,etTask2,etTask3,etIdle在任务创建完成后就绪,由于优先级etTask3 > etTask2 > etTask1 >etIdle,所以任务etTask3优先执行,此后etTask2,etTask1和etIdle依次执行。当tick = 10时,ttTask1开始执行,直到tick = 12时,ttTask1还未执行结束,这时ttTask2触发并抢占ttTask1任务执行。当tick = 15时,ttTask2执行结束,ttTask1恢复执行;当tick = 22时,ttTask1执行结束。之后,ET任务etIdle,etTask2,etTask3依次就绪并执行。当tick = 30时,ttTask3抢占etTask3开始执行;在tick = 32时ttTask3执行结束,之后etTask3恢复执行并在tick = 37时执行结束。此后,ET任务etIdle,etTask1,etTask2依次就绪并执行。
随着外部事件的不可预知性和实时任务复杂性的增加,单纯的事件触发机制或时间触发机制都难以保证系统的安全性和可靠性。只有将事件触发和时间触发结合起来,才能解决单一触发机制无法满足复杂嵌入式系统要求的问题,才能既保证系统的可靠性和确定性,又保证系统对外部事件拥有良好的响应能力。实验说明了这种方式可行,后期的工作将在时间触发模块内部,还要实现针对混合关键任务的调度算法和对应的安全级别调度表的设计,保证系统对安全关键任务的支持。在事件触发模块内部,还要实现包括决策和实施在内的访问控制机制,保证系统具备一定的安全性。