uCOS-II是一个公开源码的抢占式、多任务的实时操作系统,因其具有开源性、实时性强、代码紧凑、稳定可靠等特点在各种系统中得到了广泛应用.uCOS-II在ARM 处理器上的移植也已经实现.ARM处理器加uCOS-II操作系统的嵌入式系统常用于工业实时控制,对执行效率特别是实时性要求较高.提高uCOS-II执行效率的关键是减少任务切换时间,任务切换时主要的操作是任务栈的出栈和入栈操作.由于ARM处理器在不同的处理器运行模式具有不同的堆栈,合理利用该特性并结合处理器运行模式切换特点能够减少任务栈操作次数,减少任务切换时间.本文主要提出了两类提高uCOS-II在ARM 上执行效率的方法:
① 减少任务栈操作次数.
② ARM处理器上的uCOS-II的可重入中断的实现.

1 uCOS-II在ARM 上的任务切换
   uCOS-II中进行任务切换的函数是OSStartHighRdy()、OSCtxSw()、OSIntCtxSw().其中OSStartHighRdy()是在操作系统第一次启动的时候调用的,OSCtxSw()是用户主动调用进行任务切换,这两个函数和中断无关,所以和ARM 的处理器模式无关,在ARM上的实现方法和一般处理器类似.
   OSIntCtxSw()用于在中断中进行任务切换.嵌入式实时系统响应外部事件主要通过外部中断,在中断中将高优先级的任务设置为就绪状态,然后在中断退出时自动切换到高优先级的任务.为了实现该功能,uCOS-II要求用户在中断中:
① 若需运行高优先级任务,设置该高优先级任务的就绪标志位.
② 在中断函数末尾调用OSIntExit()函数,OSIntExit()函数检查是否有高优先级任务就绪,有则调用OSIntCtxSw(),并切换到该高优先级任务.
   进行任务切换时需将当前任务的状态变量压入堆栈(也就是当前任务栈),然后将堆栈指针指向高优先级任务的任务栈,并弹出该高优先级任务堆栈中的状态变量.由于OSIntCtxSw()在中断中调用,而进入中断函数后一般已经保存状态变量到堆栈,于是OSIntCtxSw()不需要进行上述压栈操作.但是由于ARM体系结构决定了中断和非中断中使用不同的运行模式和堆栈,使得该问题变得复杂.例如,假设在非中断中使用SVC模式(管理模式)和SVC堆栈;中断中使用IRQ模式(外部中断模式)和IRQ堆栈;任务切换时需要保存的状态变量为PC,R12-R0,RL,CPSR,SPSR,且它们任务栈中的排列顺序如图1所示.设中断函数为OSInt1(),则通常在ARM上实现OSInt1()和OSIntCtxSw()可用表1、表2的伪代码表述:

图1 任务栈结构图
表1 OSInt1中断服务程序的伪代码
OSInt1
{
将LR_irq-4、R12至R0的内容依次入栈;
/*LR-4是中断服务程序的返回地址即PC*//*A处*/
清除中断标志位; /*B处*/
调用OSIntEnter;
中断事件处理,例如使得高优先级任务就绪;
调用OSIntExit;
将堆栈的内容依次弹出到R0至R12、PC寄存器中,同时从SPSR_irq恢复状态寄存器;
}
表2 OSIntCtxSw程序的伪代码
OSIntCtxSw
{
调整堆栈指针SP到表1中”A处”; /*C处*/
保存SPSR_irq(中断返回状态寄存器),从堆栈中弹出R0-R12,LR_irq,并保存LR_irq(中断返回地址);/*D处*/
强制切换到SVC模式;
将LR_irq,LR_SVC,R12-R0,CPSR_irq,SPSR_irq依次压入堆栈; /*E处*/
调用_OSCtxSw;/*_OSCtxSw是OSCtxSW 函数去除入栈操作的部分*/
}
   从表1、表2可见在ARM 处理器上OSIntCtxSw()不仅没有节省压栈操作,而且还多了一步出栈操作.这是由于中断函数OSInt1()压入的状态变量不在任务栈(即SVC堆栈)中而是在IRQ堆栈中,所以OSIntCtxSw()必须重新从IRQ堆栈中弹出状态变量,并将其压入SVC堆栈.该额外的出栈入栈操作增加了任务切换时间.这也是现有的uCOS-II在ARM 上实现的方式.

2 减少任务栈操作次数的改进
   为了防止OSIntCtxSw()中进行额外的出栈入栈操作,本文提出了两种改进方案:
2.1 中断中使用SVC堆栈
   假如在中断中直接使用SVC堆栈,那么进入中断函数后的压栈操作将状态变量直接压入了任务栈,这样就不需要在OSIntCtxSw()中进行额外的出栈和入栈操作.实现方法是,在进入中断函数的第一时刻,将SVC堆栈寄存器SP_svc的内容拷贝到IRQ堆栈寄存器SP_irq中.此后虽然中断中仍然使用SP_irq寄存器,然而实际指向任务栈.本文实现该方案的伪代码如表3、表4所示:
表3 改进1的OSInt1中断服务程序的伪代码
OSInt1
{
切换到SVC模式;
将 SP_SVC保存到临时变量save_sp_svc中;
切换回IRQ模式;
将save_sp_svc的内容加载到SP_irq中;
跳转到_OSInt1;/*这里的_OSInt1和表1的OSInt1完全一样*/
}
表4 改进1的OSIntCtxSw程序的伪代码
OSIntCtxSw
{
调整堆栈指针SP_irq;
保存SPSR_irq(中断返回状态寄存器)到R0,保存SP_irq到R2;
强制切换到SVC模式;
复制R2(=SP_irq)到 SP_SVC;将LR_svc,R0(=SPSR_irq),R0(=CPSR),依次压入堆栈;
调用_OSCtxSw;
}
   本文实际汇编程序实现表1、2的代码共需45次出栈入栈操作,而实现表3、4的代码减少到17次出栈入栈操作.

2.2 中断中使用SVC模式
   §2.1的改进需要首先切换到SVC模式,然后切换回IRQ模式(如表3所示),最后在OSIntCtxSw()中又要切换到svc模式(如表4所示),假如在IRQ中直接使用SVC模式,即可以同样使用SVC堆栈达到§2.1改进的效果,同时减少了在SVC和IRQ之间来回切换的麻烦.本文实现在中断中使用SVC模式的伪代码如表5、6所示:
表5 改进2的OSInt1中断服务程序的伪代码
OSInt1
{
保存lr-4到临时变量INT_RET_ADDR;
将SPSR_irq(返回后的CPSR内容)保存到INT_RET_CPSR;
强制切换到SVC模式;
将INT_RET_ADDR,R12-0,LR_SVC,INT_RET_CPSR依次入栈;
清除中断标志位; /*F处*/
调用OSIntEnter;
中断事件处理,例如使得高优先级任务就绪;
调用OSIntExit;
从堆栈中弹出INT_RET_CPSR,写入CPSR;
表6 改进2的OSIntCtxSw程序的伪代码
OSIntCtxSw
{
调整堆栈指针SP;
将SPSP_SVC压入堆栈;
调用_OSCtxSw;
}
   实际代码中该方案的出栈入栈次数也是17次.但该方法要求在IRQ模式切换到SVC模式之前保存IRQ模式下的一些变量(如SPSR_irq、LR_irq)这增加额外的一些操作.§2.3的实验表明该改进的效率没有§2.1的改进高效,但是该改进为§3的可重入中断做好了准备.

2.3 任务切换时间的比较
   为验证改进方法对减少任务切换时间的效果,本文设计如下实验:
   让uCOS-II的一个任务Task1等待(用OSSemPend函数实现)信号量mysem,此时Task1挂起.当键盘按下时进入OSInt1中断函数,在该中断中发送(使用OSSemPost函数)mysem信号量,使得Task1进入就绪状态.当OSInt1退出时切换到Task1任务.对于三种实现方法:通常的任务切换方法、中断中使用SVC堆栈的任务切换方法和中断中使用SVC模式的任务切换方法,分别测试键盘按下到切换到Task1的时间差.为此在OSInt1()中断函数开始处获取定时器初始值t1,在进入Task1以后读取定时器当前值t2,则t=|t1-t2|就是想要的时间差.
   对三种实现方法分别进行43次实验,测得的t如图2所示.实验时,定时器以PCLK/2(PCI K设置为50700000)的速率减法计数.

图2 三种方法的任务切换时间比较
   从图2可知,两种改进的方法比通常方法需要更少的任务切换时间;§2.2的方法比§2.1的方法需要更多的任务切换时间与原先的分析符合.
   从改善的性能上,这里可以从计数值的相对值来看,§2.1的方法比通常方法约改善了61/1403=4.3% ,但是必须考虑到时间t包含了中断产生到切换到Task1的整个代码的执行时间,其中包含了OSSemPost()、OSIntExit()等较复杂函数,所以可以说该改进是有效的.

3 可重入中断对任务切换时间的影响
3.1 uCOS-II对可重入中断的支持
   uCOS-II本身支持可重入中断,它在OSIntExit中使用IntNesting变量表示了中断嵌套的层数,uCOS-II只在退出最后一层嵌套时才进行任务的切换,这可以通过检查IntNesting变量来获知.
   中断的嵌套增加了任务切换的实时性,以例子说明如下:假设有两个任务:taskTimer和taskKey,taskTimer由定时器触发;taskKey由外部中断源(例如按键)触发,taskKey的优先级比taskTimer高.如图3所示,当外部中断发生时,若处理器正好处于定时中断中.中断可重入时,系统马上进入外部中断服务程序让Task_Key就绪,并在退出最后一层中断(定时中断)时,切换到Task_Key;中断不可重入时,需要首先切换到低优先级任务TaskTimer,然后再次进入中断,然后才切换到高优先级任务Task_Key.
   由此可见中断可重入时,在有多个优先级的任务共存的情况下,高优先级任务的实时响应时间更短了.

图3 中断可以重入和不可重入时任务切换的差别

3.2 ARM处理器对可重入中断的支持
   在默认情况下ARM处理器不支持可重入中断.当ARM进入IRQ中断以后自动关闭IRQ使能标志,禁止IRQ中断的嵌套.为了支持IRQ的可重入,需要:
(1)在重新使能IRQ中断之前保存必要的寄存器.
(2)在重新使能IRQ之前使用SVC模式,并在中断退出以前一直保持SVC模式.

3.3 在Arm上实现uCOS-II可重入中断
   §2.2的方法实际已具备可重入中断的要求,只要在表5的“F处”之后重新使能IRQ中断标志位即可.在重新使能IRQ中断之前需要注意的是首先需要清除中断源的中断位,防止重新使能IRQ中断标志位后同一中断源重复中断.

4 结论
   本文提出了两点提高uCOS-II在ARM 上执行效率的方法:减少任务栈操作次数和实现可重入中断.在中断中直接使用SVC堆栈和在中断中直接使用SVC模式的改进减少了额外的出栈入栈操作,减少了任务切换时间,实验结果证实该改进的有效性.可重入中断在ARM处理器的uCOS-II上的实现,使得高优先级任务的实时响应时间更短了.两点提高效率的方法不仅有助于提高ARM 上uCOS-II的执行效率.同时也对在ARM上实现高实时的嵌入式操作系统有借鉴意义.