μC/OSII 内核任务调度模块的分析与改进

发 布 时 间 : 2008-11-18 来 源 : 来自网络 作 者 : 匿名 浏 览 :
现今,嵌入式系统的应用已十分广泛,也是新兴计算模式如普适计算等的支撑技术之一。在嵌入式技术发展的初期,由于嵌入式系统控制的外设和执行的任务较少,利用简单的循环和少量模块调用即可满足要求。随着应用的复杂化,多数嵌入式操作系统都采用了微内核结构。在当前较流行的操作系统如VxWorks、QNX、pSOS、WindowsCE、μC/OSII以及RTlinux中,μC/OSII是一个完整、可移植、可固化、可裁减的抢占式多任务内核,可用于各类8位、16位和32位单片机和DSP[1]。微内核与具体硬件环境相结合构成了嵌入式系统,硬件环境则通常由单片机为核心配合一些辅助电路和外围设备构成[2]。在嵌入式系统中使用的单片机较多,主要有80C51、AVR和ARM等系列。其中,ARM系列的应用较为广泛,并包括多个系列,尤其是ARM7系列更多用于多媒体和嵌入式设备,包括Internet设备、网络和调制解调器设备以及移动电话和PDA等无线设备,且开发环境容易获得,价格也较低廉。

  任务调度模块是嵌入式内核中最为核心的部分,任务调度算法的好坏以及执行效率直接关系到嵌入式内核的应用范围及实时性程度[3]。任务调度算法中,主要涉及的问题包括任务切换、现场保护以及优先级的确定和任务调度方法等。在应用中,必须将μC/OSII内核中与此相关的代码移植到具体硬件环境下。理论上,可以有多种任务调度模块的移植方法,但选择一种适合硬件环境的方法至关重要。此外,由于μC/OSII任务调度模块采用可抢占式的固定优先级分配方法,适用于较为简单的应用[4],而在实时系统中还有多种优先级的分配要求,这就要求在实际应用中对μC/OSII的任务调度模块加以改造,以适应不同的优先级分配要求。当然,改造要以不破坏原系统的高效性为前提。

1 内核移植分析

  μC/OSII内核较小,全部代码量约6000~7000行,共15个文件,其中90%的代码用C语言完成,已被移植到40多种单片机上[1]。任务调度模块中需要移植的与硬件环境相关的代码主要包括OS_ENTER_CRITICAL、OS_EXIT_CRITICAL和OSCtxSw模块,这里采用修改μC/OSII的状态寄存器CPSR并模拟中断保存现场的方法来实现移植。

1.1μC/OSII状态寄存器 

  程序状态寄存器CPSR包含条件码标志、中断禁止位和当前处理器模式以及其他状态和控制信息,其中的N(Negative)、Z(Zero)、C(Carry)和V(Overflow)位为条件码标志,经常作为标志位引用,可供大多数指令作为是否执行的检测标志。CPSR的最低8位I、F、T 和M[4:0]用作控制位,其中的I、F 为中断禁止位,当I位置1禁止IRQ中断,而F位置1则禁止FRQ中断[5]。

1.2移植代码

  当任务切换时要先关中断,保存状态寄存器和各个通用寄存器的内容。在μC/OSII中,这些功能由OS_ENTER_CRITICAL宏、OS_EXIT_CRITICAL宏和OSCtxSw()函数来实现[3]。OS_ENTER_CRITICAL和OS_EXIT_CRITICAL的功能分别为关中断和开中断,即将CPSR中的I、F 中断禁止位置1。实现代码如下。

① OS_ENTER_CRITICAL宏#define OS_ENTER_CRITICAL() IRQFIQDE
#define IRQFIQDE asm volatile ("stmfd sp!,{r0}");
/r0→堆栈中
asm volatile ("mrs r0,CPSR");
/CPSR→r0,需要修改r0
asm volatile ("stmfd sp!,{r0}");
/保存CPSR旧的值
asm volatile ("orr r0,r0,#0xc0");
/屏蔽IRQ和FIQ
asm volatile ("msr CPSR_c,r0");

② OS_EXIT_CRITICAL宏#define OS_EXIT_CRITICAL() IRQFIQRE
#define IRQFIQRE asm volatile ("ldmfd sp!,{r0}");
/恢复旧的CPSR的值
asm volatile ("msr CPSR_c,r0");
/恢复CPSR 的控制位
asm volatile ("ldmfd sp!,{r0}");
/恢复旧的r0

  OSCtxSw()函数的主要功能是保存处理器寄存器和状态寄存器的内容,再将OSPrioHighRdy的值赋予OSPrioCur并恢复当前最高优先级任务的断点。因为此段代码为临界代码,所以在调用OSCtxSw()前要使用OS_ENTER_CRITICAL()函数开中断,完成全部功能之后再使用OS_EXIT_CRITICAL()函数关中断。这种移植方法的长处是实现简单,不需在用户状态与系统状态间转换,但由于使用宏而会占用较大空间。

2 任务调度算法分析

  任务管理、任务的调度与状态改变是任何内核必备的功能,其实现方法直接关系到系统的性能。对于某个任务需要创建任务控制块,以记录该任务执行的“上下文”,包括在某个特定时间点以前执行历史中尚未完成的任务,以及用于这些过程的局部变量在此时间点上的映像信息等。

  任务首先由OSTaskCreate()和OSTaskCreateExt()函数创建,后者为前者的扩展,它们的主要功能是从空闲的OS_TCB缓冲池中获得一个任务控制块,并用给定的参数初始化。任务的调度与切换由OSShed()函数管理,它首先从就绪表中找出优先级最高的任务,再调用OSCtxSw()实现任务切换,这是与硬件相关而需要移植到具体芯片的部分。工作时,OSCtxSw()先保存各个寄存器的值,并用新任务堆栈中的值来替换,同时将CPU的控制权交给该任务。此函数运行效率很高,理想情况下可在50个时钟周期内完成。

  μC/OSII中任何两个任务的优先级不能相同,允许用户改变任务的优先级。优先级采取查表法实现,具体过程是将优先级分为8组,每组8个,每组对应OSRdyCrp的一位。若该组有就绪任务则OSRdyCrp相应位置1,并用数组OSRdyTbl的每一位记录下每组中的就绪任务。通过OSRdyCrp和OSRdyTbl的值即可实现最高优先级就绪任务查表,且这种方法因无须反复查找比较而提高了效率。在μC/OSII中,任务可以被其他任务或自己删除,实现此功能依赖OSTaskDel()模块,其主要工作就是从就绪表中删除任务就绪的标志。考虑到一个任务可能占有系统资源,此时不能使用OSTaskDel()直接删除,可代之以OSTaskDelReq()模块,以便能给任务一个释放占有资源的机会。

3 任务调度算法的改造

  μC/OSII采用的是固定优先级的分配策略,其实现模块OSTaskChangePrio()仅以新优先级替换掉旧优先级,功能简单。事实上,在实时系统中常常需要采取其他优先级分配方案,如截止期最早优先算法、速率单调算法以及可达截止期最早优先算法等[6]。本文通过对内核代码改造,实现了较常用的截止期最早优先算法。

  截止期最早优先算法的实质是使截止期最早的任务优先级最高。这就需要将就绪队列中的任务按截止期排序并转换为与系统当前时间一致的形式。为此,在os_tcb任务控制块中增加表1所列的项目。

              表1添加的项目
项目 功能
INT8U deadline 记录任务的截止期,在任务调度时用当前系统时间OSTime和cstime的差更新deadline
INT8U cstime 记录任务调用时的系统时间
INT8U csRec[64][3] 记录每个优先级任务的调用时间、截止时间、优先级附加值(记录任务优先级的变化),任务创建或销毁时要根据其截止期分配合理的优先级并插入此数组,在任务发生调用时用数组来更新其相应项的值


  在OSInit()中将deadline和cstime初始化为0。上电后系统先调用OSInit()进行初始化,再调用OSTaskCreate() 来创建任务。在μC/OSII内核中,任务的优先级由用户指定。为了实现截止期最早优先算法,需要由系统指定任务优先级,但需要用户指定其截止期。因此,将OSTaskCreate()中的参数INT8U Prio改为INT8U Deadline,并定义局部变量INT8U Prio以记录分配给任务的优先级。μC/OSII 内核中优先级范围为0~64,OSTaskCreate()模块利用OSTaskPrioCreate()模块获得任务优先级。该模块先用系统时间来更新每个任务的截止期,如果截止期为零,则删除这个任务,再按从低到高的顺序逐个与每个任务比较截止期。主要代码如下。
if (OSTime-csRec[i][0]-csRec[i][1]>0) {
OSTaskDel(i);
csRec[i][0] = csRec[i][1] = csRec[i][2] = 0;
j=i;
while (csRec[j][1]&&(j>=0)){
csRec[j][1]=csRec[j-1][1];
csRec[j-1][2]--; j--;
}
}
  找到一个截止期低于当前任务截止期的任务时,记录下此任务的优先级并对数组内容进行如下调整。if (Deadline>csRec[i][1])
{ int j=i;
while(csRec[j][1]&&(j>0)) j--;
if(j){
for(j;j<=i;j++){
csRec[j][1]=csRec[j-1][1];
csRec[j-1][2]++;
}
}
csRec[i][1]=Deadline;
}
  实现任务调度OSSched()模块,通过查表确定当前就绪任务中最高的优先级。为了不破坏系统的调用机制,在查表之前要更新OSTCBPrioTbl[OSTCBHighRdy]的值。

  OSTCBPrioTbl[OSTCBHighRdy]=OSTCBPrioTbl[OSTCBHighRdy]->prio+csRec[OSTCBHighRdy][1];

  再查表取出优先级最高的任务进行比较和任务切换。系统通过OSTaskDel()模块删除一个任务,在删除前,需要先更新prio的值。prio=prio+csRec[prio][2];

  在删除任务后还要将csRec与prio对应位的三列清零。因任务优先级不再由用户分配,在任务调度模块中删去OSTaskChangePrio()模块,防止用户破坏任务优先级的排序,这样就完成了整个改造。

4 结论

  总体上说,μC/OSII的任务调度算法虽然高效但比较单调,且因任务的优先级由用户在任务创建时给定,存在一定的盲目性,用户在改变优先级时也很难照顾到当前任务同其他任务的关系。本文在对μC/OSII移植的基础上,通过内核改造实现了截止期最早优先算法。分析和实验表明,由于此方法只在任务被调用时才更新其优先级,有效地保持了系统调度的高效性。

                 参考文献
1毛德操.嵌入式系统——采用公开源代码和StrongARM/XScale处理器[M].浙江:浙江大学出版社,2003
2程红蓉.一种嵌入式操作系统内核DeltaCore的设计与实现[D].成都:电子科技大学,2001
3Khushu Sanjeev, Simmons Johnathan . Scheduling and Synchronization in Embedded RealTime Operating Systems[In],CSE211,2001
4王宝进.μC/OSII实时操作系统内存管理的改进.计算机应用,2002,5(19)
5吴明晖.基于ARM的嵌入式系统开发与应用[M].北京:人民邮电出版社,2004
6郑宗汉.实时系统软件基础[M].北京:清华大学出版社,2003
                               (收稿日期:2004-11-04)



上一篇:改进uC/OS II,减少内存使用量 下一篇:Linux操作系统下设置基本网络参数四法