ucos学习摘要(一)
1 引言
由于 C/OS在设计之初就充分考虑了本身在不同处理器上的移植问题,因此在任何处理器上的移植 C/OS都只需要关心三个文件:头文件OS_CPU.H 、C文件0S_CPU_C.C和汇编文件OS_CPU_A.ASM.下面我们分别由这三个文件入手来介绍移植需要解决的几点问题。
C/OS中的任务总是处于五种状态之一:睡眠态、就绪态、运行态、等待状态和中断服务态。任何任务必须首先创建处于就绪态之后才有可能运行,任务创建函数OSTaskCreate()和OSTaskCreateExt()会初始化任务的栈结构,使堆栈看起来就象刚刚发生过中断一样,所有寄存器保存在任务的堆栈之中。若要任务恢复执行,只须在最后执行一条中断返回指令即可。这就是初始化堆栈时把堆栈看起来就绪刚刚发生过中断一样。
在 C/OS-II的移植文件OS-CPU-C.C中,唯一必要的函数就是0STaskStkInit()。该函数会在任务创建时被调用,用来初始化任务的堆栈结构。因此,在真正动手编写移植代码之前必须首先设计好任务的堆栈结构。使任务看起来象刚刚发生过中断一样。例如任务的中断结构如下:
在另一个移植文件OS-CPU-A.ASM中,需要改写四个函数,分别是OSSstartHighRdy(); OSCtxSW(); OSIntCtxSW()和OSTickISR()。
OSSstartHighRdy()由函数OSStart()函数调用,功能是让进入就绪态的优先级最高的任务运行,函数原型如下:
void OSStartHighRdy()
{
调用用户定义函数OSTaskSwHook();
OSRunning=TRUE; //第一部分START(1)
获取堆栈指针SP=OSTCBHighRdy->OSTCBStkPtr;
从新任务堆栈中恢复所有寄存器;
执行中断返回指令; //第二部分START(2)
}
OSCtxSW()是一个任务级的任务切换函数,OSIntCtxSW()是一个中断级的任务切换函数。区别是前者是在任务需要进行切换时被宏OS_TASK_SW()调用。而后者是在中断退出时被函数OSIntExit()调用的。一个服务于任务,一个服务于中断。这两个函数颇有相似之处。
OSCtxSW()函数原型如下:
void OSCtxSW(void)
{
保存处理器的寄存器到当前任务的堆栈中;
将当前任务的堆栈指针保存的该任务的OS_TCB中:
OSTCBCur->OSTCBStkPtr=堆栈指针; //第一部分:CTX(1)
调用用户定义函数OSTaskSwHook();
OSTCBCur=OSTCBHighRdy;
OSPrioCur=OSPrioHighRdy; //第二部分:CTX(2)
获取新任务堆栈指针SP=OSTCBHighRdy->OSTCBStkPtr;
从新任务堆栈中恢复所有寄存器;
执行中断返回指令; //第三部分:CTX(3)
}
OSIntCtxSW()函数原型如下:
void OSIntCtxSW(void)
{
调用用户定义函数OSTaskSwHook();
OSTCBCur=OSTCBHighRdy;
OSPrioCur=OSPrioHighRdy; //第一部分:INTCTX(1)
获取新任务堆栈指针SP=OSTCBHighRdy->OSTCBStkPtr;
从新任务堆栈中恢复所有寄存器;
执行中断返回指令; //第二部分:INTCTX(2)
}
由以上两函数的原型可见,OSIntCtxSW()比OSCtxSW()所少的部分就是CTX(1)部分。而该保存处理器寄存器的部分在OSIntCtxSW()函数中没有的原因是:OSIntCtxSW()函数是在中断中被调用的,而在中断的一开始就已经保存了处理器的寄存器。因此在该函数中,无须再次保存。
因此,在移植程序的设计中,可以把OSIntCtxSW()设计为一个子函数,而在OSCtxSW()函数中进行调用即可。同样,OSStartHighRdy()的START(2)部分、OSIntCtxSW()的CTX(3)和OSIntCtxSW 的INTCTX(2)执行同样的功能,因此,可以用同一段代码实现。
二.由ARM体系结构的特殊性带来的移植问题
问题一:由于ARM体系结构采取七中处理器模式和两种指令状态,由此带来了程序运行的模式选择问题和状态切换。在一般情况下,为了防止用户的应用程序随意访问更改系统资源,我们采用操作系统(ucos)来保护系统资源。因此操作系统可以访问系统内的所有资源,而用户进程则会受到各种限制检查。ARM体系结构的七种处理器模式,除去五种异常模式外,就只剩下用户模式和系统模式。而每一种异常模式与相应的处理器异常对应(R13,R14分别对应于自己的处理器模式),不适合作为操作系统或用户进程的运行模式。
系统模式可以访问系统内部所有资源而不受任何限制,因此最适合做为操作系统的运行模式,用户进程则可以运行在用户模式下。在用户模式下,用户进程不能随意更改处理器的模式,因此也就限制了进程的访问权限,保护了系统资源。
也正因为如此,用户进程工作于用户模式,当发生中断时系统自动进入中断模式,在中断的刚开始执行时,要进行现场保护,按照 C/OS的要求,此时应该把现场保护到用户进程的堆栈之中。这对于象8086这种一般处理器来说,并不算难事,但是对于ARM核芯片来说却要花费很大的开销。例如:
INT1( )
{
PUSH A;
PUSH ES;
PUSH DS //第一部分:INT(1)
……….
TaskCode( );
OSIntExit( );
POP DS;;
POP ES;
POP A; //第二部分:INT(2)
}
在执行INT(1)部分代码时,由于此时处理器处于中断模式,此时的压栈操作并没有把寄存器压入用户任务的堆栈之中。当执行OSIntExit( )进行任务切换时,由于用户任务的堆栈之中并没有相应的现场保护积存器,系统就会崩溃。
而如果在中断的开始阶段就进行模式切换,并把所有的寄存器保护到用户任务的堆栈之中,当执行OSIntExit( )时,却并不一定真正进行任务切换(因为软件中断时,首先要判别中断号,如果为0才进行任务切换),那么中断开始执行时的开销就会无端的浪费处理器的宝贵时间。
INT2( )
{
//不进行模式转换到用户模式,暂时保存于中断模式堆栈之中
当需要实际进行切换时(当中断号为0时),再把寄存器保存到用户任务的堆栈之中
PUSH A;
PUSH ES;
PUSH DS //第一部分:INT(1)
……….
TaskCode( );
OSIntExit( ); //在OSIntCtxSw( )中进行保存寄存器于用户堆栈中的操作
POP DS;;
POP ES;
POP A; //第二部分:INT(2)
}
上面的编程方法可以很好的解决所存在的问题。
在中断的开始执行阶段不进行模式转换,仍然把寄存器暂时保存于中断模式堆栈之中。当调用OSIntExit( )时, C/OS会判断是否进行任务切换(中断号是否为0),若有更高的优先级任务就绪而需要进行任务切换时,在OSIntCtxSw( )中把寄存器保存到用户任务的堆栈之中。这样程序的执行就可以达到最优化。
问题二:此外,还存在一个细节问题。由ARM相关资料知,ARM处理器支持两种指令集合,分别是ARM指令和THUMB指令。因此,ARM处理器可能处于两种指令执行状态,而当移植函数OSIntCtxSw( )进行任务切换并恢复任务的运行环境时,由ARM规范知,我们不能通过直接给程序状态寄存器赋值来改变处理器的状态,而只能用异常返回指令恢复任务原来的运行状态。
OSIntCtxSw()
{
………
LDMFD SP! , {R0-R12, LR,PC}^ //该指令只能在异常模式下使用,如果在用户模式或系统模式下使用该指令,会产生不可预知的结果。
}
因此,我们必须首先使处理器进入一定的异常模式(这里用到管理模式,因为其他异常模式可能在下面会用到),然后,在该模式下恢复任务的运行环境,并返回到任务切换前的指令执行状态。
三.结束语
经过移植后的操作系统经过测试,运行稳定,并达到了实时系统的要求.