uC/OS-II在ARM系统上的移植与实现
引言
在开发嵌入式系统时,一般选择基于ARM 和uC/ OS - II 的嵌入式开发平台,因为ARM 微处理器具有处理速度快、超低功耗、价格低廉、应用前景广泛等优点[1 ] . 将uC/ OS - II 移植到ARM 系统之后,可以充分结合两者的优势. 如果一个程序在一个环境里能工作,我们经常希望能将它移植到另一个编译系统、处理器或者操作系统上,这就是移植技术.移植技术可以使一种特定的技术在更加广泛的范围使用,使软件使用更加灵活,不局限于某一条件.uC/OS - II 是由Jean J . Labrosse 先生编写的完整的可移植、固化、裁剪的占先式实时多任务内核.uC/ OS - II 的源代码完全开放,这是其他商业实时内核无法比拟的[2 ] . 它是针对嵌入式应用设计的,在设计之初就充分考虑了可移植性,它的大部分源代码都是用高可移植性的ANSIC 编写的[3 ] . uC/ OS - II可以移植到从8 位到64 位的不同类型、不同规模的嵌入式系统,并能在大部分的8 位、16 位、32 位、甚至64 位的微处理器和DSP 上运行. 由于uC/ OS - II是一个实时操作系统,所以如果将它嵌入到ARM处理器上,就能够进一步简化ARM系统的开发.
图1 uC/ OS - II 文件体系结构
1 uC/ OS - II 的移植
uC/OS - II 的文件系统结构包括核心代码部分、设置代码部分、与处理器相关的移植代码部分[4 ] . 结构如图1 所示.其中最上边的软件应用层是uC/ OS - II 上的代码. 核心代码部分包括7 个源代码文件和1 个头文件. 功能分别是内核管理、事件管理、消息队列管理、存储管理、消息管理、信号量处理、任务调度和定时管理. 设置代码部分包括2 个头文件,用来配置事件控制块的数目以及是否包含消息管理相关代码. 而与处理器相关的移植代码部分则是进行移植过程中需要更改的部分,包括1 个头文件OS CPU. H ,1 个汇编文件OS CPU A. S 和1 个C 代码文件.实际上将uC/ OS - II 移植到ARM 处理器上,需要完成的工作主要是以下三个与体系结构相关的文件:OS CPU. H ,OS CPU. C 以及OS CPU A. S[5 ] .
1. 1 OS CPU. H 的移植
文件OS CPU. H 中包括了用# define 语句定义的与处理器相关的常数、宏以及类型. 移植时主要修改的内容有:与编译器相关的数据类型的设定;用#define 语句定义2 个宏开关中断;根据堆栈的方向定义OS STK GROWTH等.
在将uC/ OS - II 移植到ARM 处理器上时,首先进行基本配置和数据类型定义. 重新定义数据类型是为了增加代码的可移植性,因为不同的编译器所提供的同一数据类型的数据长度并不相同,例如int型,在有的编译器中是16 位,而在另外一些编译器中则是32 位. 所以,为了便于移植,需要重新定义数据类型,如INT32U 代表无符号32 位整型. typedefunsigned int INT8U ,就是定义一个8 位的无符号整型数据类型. 其次就是对ARM 处理器相关宏进行定义,如ARM处理器中的退出临界区和进入临界区的宏定义,退出临界区宏定义[5 ] : # define OS EXITCRITICAL () ARMDisable Int ( ) / / 关中断,进入临界区宏定义# define OS ENTER CRITICAL ( ) AR2MEnableInt () / / 开中断. 最后就是堆栈增长方向的设定. 当进行函数调用时,入口参数和返回地址一般都会保存在当前任务的堆栈中,编译器的编译选项和由此生成的堆栈指令就会决定堆栈的增长方向[6 ] ,定义为# define OS STK GROWTH 1.
图2 堆栈增长方向
1. 2 OS CPU. C 的移植
OS CPU. C 的移植包括任务堆栈初始化和相应函数的实现. 在这里,共有6 个函数:OSTaskStkInit( ) , OSSTaskCreateHook ( ) , OSTaskDelHook ( ) , OS2TaskSwHook( ) ,OSTaskStatHook ( ) , OSTimeTickHook () . 其中后面的5 个HOOK函数又称为钩子函数,主要是用来对uC/ OS - II 进行功能扩展. 这些函数为用户定义函数,由操作系统调用相应的HOOK函数去执行,在一般情况下,他们都没有代码,所以实现为空函数即可. 而函数OSTaskStkInit ( ) 对堆栈进行初始化,在ARM 系统中,任务堆栈空间由高到低依次为PC ,LR ,R12 ,R11 , ?,R1 ,R0 ,CPSR ,SPSR. 在进行堆栈初始化以后,OSTaskStkInit ( ) 返回新的堆栈栈顶指针.
1. 3 OS CPU A. S 的移植
OS CPU A. S 文件的移植需要对处理器的寄存器进行操作,所以必须用汇编语言来编写. 这个文件的实现集中体现了所要移植到处理器的体系结构和uC/ OS - II 的移植原理[6 ] . 它包括4 个子函数:OSStartHighRdy() , OSCtxSw() , OSIntCtxSw() ,OSTick2ISR() . 其中难点在于OSIntCtxSw() 和OSTickISR() 函数的实现,因为这两个函数的实现与移植者的移植思路以及相关硬件定时器、中断寄存器的设置有关.在实际的移植工作中,这两处也是比较容易出错的地方.
OSIntCtxSw( ) 函数由OSIntExit ( ) 函数调用,而OSIntExit () 函数又由OSTickISR() 调用. OSIntCtxSw()函数最重要的作用就是它完成在中断ISR 中直接进行任务切换,从而提高了实时响应的速度. 它发生的时机是在ISR 执行到OSIntExit ( ) 时,如果发现有高优先级的任务因为等待time tick 的到来获得了执行• 7 2 • 第4 期李学桥等:uC/ OS - II 在ARM系统上的移植与实现的条件,就可以马上被调度执行,而不用返回被中断的那个任务之后再进行任务切换. 实现OSIntCtxSw() 的方法大致也有两种情况[7 ] :一是通过调整SP 堆栈指针的方法,根据所用的编译器对于函数嵌套的处理,通过精确计算出所需要调整的SP 位置来使得进入中断时所作的保护现场的工作可以被重用. 二是设置需要切换标志位的方法,在OSIntCtxSw( ) 里面不发生切换,而是设置一个需要切换的标志,等函数嵌套从进入OSIntExit ( ) = > OS ENTER CRITI2CAL() = > OSIntCtxSw( ) = > OS EXIT CRITICAL() = > OSIntExit ( ) 退出后,再根据标志位来判断是否需要进行中断级的任务切换.
其次是对OSTickISR() 修改.OSTickISR() 首先在被中断任务堆栈中保存CPU 寄存器的值,然后调用OSIntEnter () . 随后调用OSTimeTick() ,检查所有处于延时等待状态的任务,判断是否有延时结束就绪的任务. 最后调用OSIntExit ( ) . 如果在中断中(或其他嵌套的中断) 有更高优先级的任务就绪,并且当前中断为中断嵌套的最后一层,OSIntExit ( ) 将进行任务调度. 如果进行了任务调度,OSIntExit () 将不再返回调用者,而是用新任务的堆栈中的寄存器数值恢复CPU 现场,然后实现任务切换. 如果当前中断不是中断嵌套的最后一层,或中断中没有改变任务的就绪状态, OSIntExit ( ) 将返回调用者OSTickISR ( ) ,OSTickISR() 返回被中断的任务. 最后就是退出临界区和进入临界区函数. 进入临界区时,必须关闭中断,用ARMDisableInt () 函数实现. 在退出临界区的时候恢复原来的中断状态,通过ARMEnableInt ( ) 函数来实现[7 ] . 至于进行任务级上下文切换,则是由汇编子程序OSCtxSw 实现.
- 在uclinux下实现拨号(04-21)
- 基于uClinux嵌入式系统的汽车黑匣子的设计(07-08)
- uClinux进程调度器的实现分析(04-13)
- 嵌入式操作系统uCLinux详解(03-19)
- 多任务操作系统Nucleus简介(04-21)
- UC/OS与uClinux的比较(04-21)