uc/os-II在ARM7上的移植探索

1 RTOS简介
随着电子产品和设备的应用的复杂化和智能化,原来在电子产品中占统治地位的单片机和较简单的程序结构已经难以满足要求。加之微处理器芯片本身性能的大幅提高,对软件的要求也随之提高,而实时操作系统(Real Time Operation System,以下简称为RTOS)很好满足了这些需求。
RTOS以其较好的实时性、较少的硬件资源以及强大的任务管理、资源管理等功能,在嵌入式产品的应用领域日趋广阔。RTOS同时体现了一种新的系统设计思想和开放的软件开发框架,开发者在无需过多考虑和更改系统其他任务的情况下可以集中精力设计应用任务模块,缩短了研发周期和对单个开发者的依赖。因此RTOS的应用是嵌入式软件设计编写中从小规模设计到团队开发的一个必然选择。
µC/OS-II(MicroC/OS-II)是一个可以基于ROM运行的、源码开放的、可裁减的、抢占式实时多任务内核, 内核的设计者在编写时尽最大限度采用ANSI C语言,并充分考虑了可移植性,因此,µC/OS-II已经移植到数十种处理器上,且已成功应用于从消费电子产品到航空设备的领域中。

2 ARM7系列处理器简介
依靠ARM公司提供的优秀的ARM7 内核,其合作伙伴根据市场需要和技术发展趋势生产出各具特色的芯片。ARM7 系列处理器可具有16位/32位双RISC指令集、片内高速cache、三级流水线、片上JTAG调试、高速内存管理(MMU)等功能,支持大多数操作系统,在中低端16/32位嵌入式产品解决方案中应用十分广泛。比较有代表性的S3C44B0x是三星公司开发的基于ARM7 内核的32位微处理器,除了具备ARM7系列处理器的低功耗,高性能,小体积等优点外,且价格低廉。
ARM7系列处理器结构支持7种处理模式,方便用户管理某些需要保护的硬件资源,处理器模式见表1。除用户模式外,其他模式均可自由的访问系统所有资源并改变处理器工作模式,称为特权模式,而特权模式中除系统模式(sys)外,也被称为异常模式。在用户模式下,用户任务可以通过软中断指令,进入异常模式,来访问需要的资源。µC/OS-Ⅱ移植与ARM7 处理器上时,一个难点就在于处理器模式切换时对另一工作模式下现场的保存和恢复,本文分析了任务切换时不使用软中断而直接在svc模式下运行操作系统和用户任务的移植方法,可以较简洁易懂地实现。



表1 ARM7 处理器模式
处理器模式 说明
用户 usr 正常用户程序执行模式
快速中断 fiq 高速数据传送及DMA通道处理模式
通用中断irq 通用中断处理模式
管理 svc 操作系统保护模式
中止abt 存储器保护或实现虚拟存储器
未定义und 支持硬件协处理器的软件仿真
系统 sys 供需访问系统资源的任务使用


3 µC/OS-Ⅱ在ARM7处理器上的移植
3.1 移植的要求
要使µC/OS-Ⅱ正常运行于某处理器上,须满足以下要求:
1)处理器的C编译器能产生可重入代码;
2)可以在C语言代码中打开和关闭中断;
3)处理器支持中断,并且能产生定时中断(通常在10至100Hz之间);
4)处理器支持能够容纳一定量数据(可能是几千字节)的硬件堆栈;
5)处理器有将堆栈指针和其它CPU寄存器读出和存储到堆栈或内存中的指令。
笔者针对目前应用较广泛的基于ARM7内核的S3C44B0x处理器,在ARM公司的IDE开发环境ADS1.2中完成了µC/OS-Ⅱ的移植,并编写了应用任务实现多任务的调度。
3.2 移植过程
基于µC/OS-Ⅱ的硬/软件体系结构如下图1。



图1 µC/OS-Ⅱ硬件/软件体系结构
µC/OS-Ⅱ的移植集中在OS_CPU.h,OS_CPU_A.s,OS_CPU.c这三个文件上,下面分别详细介绍三个文件中的函数和需要修改或者编写的代码。

3.2.1 OS_CPU.h的移植
该文件定义了和处理器及编译器相关的定义及一些全局函数声明。由于ARM7 处理器字长为32位,半字长为16位,字节为8位,因此在OS_CPU.h文件修改与编译器相关的定义如下:
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U; /*某些编译器中int是32位的,故统一用short表示*/
typedef signed short INT16S;
typedef unsigned long INT32U;
typedef signed long INT32S;
typedef float FP32;
typedef double FP64;
typedef unsigned long OS_STK; /*堆栈宽度为32位,即ARM7种的字对齐方式*/
/*下面是与处理器相关的代码*/
#define OS_CRITICAL_METHOD 2 /*使用方式2保护临界代码*/
#define OS_ENTER_CRITICAL() ARMDisableInt() /*临界段代码保护宏定义*/
#define OS_EXIT_CRITICAL() ARMEnableInt()
#define OS_STK_GROWTH 1 /*定义堆栈生长方向为向下生长 */
#define OS_TASK_SW OSCtxSw /*宏定义,用于非中断级的任务切换*/
/*下面开始声明全局函数声明,均是OS_CPU_A.S中需要编写的函数*/
extern void OSCtxSw(void); /*声明任务级任务切换函数*/
extern void OSIntCtxSw(void); /*声明中断级任务切换函数*/
extern void ARMDisableInt(void); /*声明中断禁止函数*/
extern void ARMEnableInt(void); /*声明中断恢复函数*/
extern void OSTickISR(void); /*声明时钟中断服务函数*/
3.2.2 OS_CPU_C.C文件
移植OS_CPU_C.C文件时,需要编写的是任务堆栈初始化函数OSTaskStkInit和时钟节拍中断服务钩子函数OSTimeTickHook。
在µC/OS-II中,每一个任务都有自己的任务堆栈,当发生任务切换或者中断时,其CPU使用权被剥脱,为了任务能被再次运行,那么这个被打断的任务所用到的处理器的寄存器内容均应得到保存,按照ARM7 处理器的压栈和入栈指令的特点,设计任务堆栈如下图2:





CPSR

R0
R1
……
R12
LR(R14)
PC(R15)

图2 任务堆栈的结构
根据任务堆栈结构示意图,OS_STK函数编写如下:
#define SVCMODE 0x13 /*定义svc模式的命令字,用户任务运行在svc模式下*/
OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt){
OS_STK *stk; /*定义堆栈指针*/
opt = opt;
stk = (OS_STK) ptos; /*保存任务堆栈栈顶指针*/
*--stk = (OS_STK) task; /* 用来保存PC,初始化成任务入口地址在被保护存现场的则是该任务运行时被中断时的地址*/
*--stk = (OS_STK) task; /* 用来保存LR*/
*--stk = 0; /* r12 */
*--stk = 0; /* r11 */
*--stk = 0; /* r10 */
*--stk = 0; /* r9 */
*--stk = 0; /* r8 */
*--stk = 0; /* r7 */
*--stk = 0; /* r6 */
*--stk = 0; /* r5 */
*--stk = 0; /* r4 */
*--stk = 0; /* r3 */
*--stk = 0; /* r2 */
*--stk = 0; /* r1 */
*--stk = (INT32U) pdata; /* 在ARM的编译建议中,r0用于参数传递 */
*--stk = (SVC32MODE|0x40); /*用来保存CPSR,并禁止FIQ,由于任务和操作系统均运行在svc模式,被中断到其他模式再返回后仍然回到svc模式,故SPSR没有用到*/
return ((OS_STK *)stk); /*返回任务堆栈的指针*/
}
说明:用户创建任务时,OSTaskCreat()会调用OSTaskStkInit函数初始化该任务的堆栈,并把返回的堆栈指针保存到该任务的TCB结构中的最前面的参数OSTCBStkPtr中,当该任务要被恢复时,任务切换函数从其TCB块中取得其任务堆栈指针,依次将堆栈内容弹到处理器对应的CPSR、r0,r1,…,r12,lr,pc的寄存器中,完成现场的恢复和程序指针PC的返回。
另一个需要编写的函数是OSTimeTickHook,该函数被时钟节拍中断服务函数OSTickISR中的OSTimeTick函数调用,用来清除时钟节拍中断发生设备的请求。本移植方案使用S3C44B0X处理器的RTC模块的tick中断作为时钟节拍中断,该函数编写如下:
void OSTimeTickHook(void){
rI_ISPC =((INT32U)0x01) << 20;/*清RTC模块的tick中断*/
}
注意:用户也可不修改此函数,但是必须在OSTickISR中执行清除发生节拍中断的设备的中断请求标志,为便于说明,本文将利用内核提供给用户的OSTimeTickHook函数来完成清中断的任务。
另外几个hook函数不必去改它们。至此,OS_CPU.C编写完成。
3.2.3 OS_CPU_A.S文件的移植
该文件是移植过程中唯一需要用汇编语言来实现的文件,也是移植的重点和难点所在。在这个文件里,需要编写的函数有OSStartHighRdy,OSCtxSW,OSIntCtxSW,OSTickISR,ARMDisableInt,ARMEnableInt几个。
下面先结合us/os的任务切换的过程分析一下这几个函数的作用。
1)OSStartHighRdy()函数
当程序执行内核的OSStart函数时,表示多任务系统开始启动, OSStart函数将调用OSStartHighRdy函数从最高优先级任务的TCB块中获得该任务的堆栈指针,通过该指针,依次从该任务的任务堆栈中恢复CPU的现场。由于任务在堆栈初始化时,已经设定了弹出到程序指针寄存器PC的是该任务函数的入口地址,因此,OSStartHighRdy函数只需依次弹出任务栈内容到处理起寄存器,该任务便将得以运行。
2)OSCtxSw()函数
该函数是任务级的上下文切换函数,当任务被阻塞而主动请求CPU开始任务调度时执行,其过程是将当前任务的的CPU现场保存到该任务堆栈中去,然后从OSTCBHighRdy中获得更高优先级任务的堆栈指针,再从该指针指向的堆栈中恢复此任务的CPU现场,使之继续执行,从而完成一次任务级别的切换。表2为OSCtxSw函数的伪代码。
void OSCtxSw(void) {
保存处理器寄存器;/*将欲挂起的任务的CPU寄存器压入当前堆栈*/
OSTCBCur->OSTCBStkPtr = sp; /*OSTCBCur目前指向的是被打断的任务TCB,此操作
将该任务的栈顶指针保存到其OSTCBStkPtr中去,便于下次恢复时从这里获取栈顶指针*/
OSTCBCur = OSTCBHighRdy; /*OSTCBHighRdy 指向的是就绪的高优先级任务的TCB,
将其装载到OSTCBCur 中来*/
SP = OSTCBHighRdy->OSTCBStkPtr; /*取得就绪的高优先级任务的栈顶指针*/
恢复该任务的现场(); /*于是便可通过刚取得的栈顶指针恢复该任务 */
执行中断返回指令; /*若OSCtxSw含有软中断指令则需中断返回,本移植不使用软中断*/
}
表2 OSCtxSw函数的伪代码
3) OSIntCtxSw() 函数
该函数用于中断级的上下文切换。由于CPU响应时钟节拍中断后,处理器从svc进入了irq模式,并进入时钟节拍中断服务函数OSTickISR,OSTickISR函数发现若有高优先级任务需要运行,则系统不返回中断前的任务,而直接调度就绪的高优先级任务使之尽快得到执行,以保证实时性能。但是由于OSTickISR函数一开始已经保存过任务中断前的CPU现场,因此OSIntCtxSW()不需要再进行类似的操作。当OSTickISR调用OSIntExit函数找出需要运行的更高优先级任务后,OSIntExit会将该任务的TCB指针放在OSTCBHighRdy中,然后OSIntExit在最后调用OSIntCtxSW函数来从OSTCBHighRdy中获取堆栈指针然后恢复该高优先级任务的现场,使得其继续执行,并不再返回时钟节拍中断服务程序。显然,OSIntCtxSW函数的过程和OSCtxSW函数的后半部分操作相同,因此,OSCtxSW可以借用OSIntCtxSW的代码。
4) OSTickISR()函数
在CPU响应时钟节拍中断后,程序指针PC发生跳转后进入该函数,由于OSTickISR调用OSTimeTick函数使得所有的延时节拍不为0的任务延时节拍数减1,并调用OSIntExit函数来找出就绪的高优先级任务,若需要切换,则最后由OSIntCtxSw来完成新任务的调度,否则仍然返回到被时钟节拍中断的任务。OSTickISR函数的伪码和注释见表3。
5) ARMDisableInt和ARMEnableInt函数
ARMDisableInt是用来暂时禁止FIQ及IRQ中断的函数,ARMEnableInt则是恢复ARMDisableInt执行前的中断使能状态,二者成对使用,用来保护临界段代码不被中断破坏。本移植使用方式2,即在进入临界段代码前关中断,完成后恢复先前的中断使能状态。
void OSTickISR(void) {
保存处理器寄存器;/*注意在irq模式下保存CPSR_svc时,要先强制切换到svc模式*/
调用OSIntEnter(); /* 防止在嵌套的中断中发生调度 */
给产生中断的设备清中断; /*本操作已由OSTimeTick中的OSTimeTickHook函数完成*/
调用OSTimeTick(); /*将所有任务不为0的延时节拍数减1*/
调用OSIntExit(); /*检索就绪任务,并将需要调度的高优先级任务的堆栈指针存到
OSTCBHighRdy中去,然后调用OSIntCtxSW恢复该任务的现场*/
恢复处理器寄存器;
执行中断返回指令; /*若没有中断级任务切换,则返回到中断前的任务*/
}
表3 OSTickTime函数的伪码
下面给出OS_CPU_A.S的全部内容和注释。
; *****OS_CPU_A.S文件汇编代码开始*****
AREA |subr|, CODE, READONLY ;声明为代码段
;***** OSStartHighRdy代码开始*****
EXPORT OSStartHighRdy ;关键词EXPORT表示声明此函数被其他文件使用,下同
IMPORT OSTaskSwHook ;关键词IMPORT声明此函数/参量在其他文件中定义,下同
IMPORT OSTCBHighRdy
IMPORT OSRunning
OSStartHighRdy ; 使就绪表中任务最高的优先级的任务开始运行
BL OSTaskSwHook ; 调用用户的Hook函数,空函数
LDR r4,=OSRunning ; 将OSRunning置1,声明多任务OS开始运行
MOV r5, #1
STRB r5, [r4]
LDR r4, =OSTCBHighRdy ; 伪指令,取得存储OSTCBHighRdy的地址
LDR r4, [r4] ; 得到最高优先级任务的任务堆栈地址
LDR sp, [r4] ; 切换到新任务的堆栈
LDMFD sp!, {r4} ;从新任务堆栈中读取第一个参数(CPSR)到(r4)
MSR cpsr_cxsf, r4 ;再传给cpsr,堆栈中的CPSR弹出到CPU的cpsr寄存器
LDMFD sp!, {r0-r12,lr,pc} ;依次恢复该任务r0~r12,lr,pc,切换到该任务
; *****下面开始OSCtxSw函数,完成任务级的任务切换*****
EXPORT OSCtxSw
IMPORT OSPrioCur
IMPORT OSPrioHighRdy
IMPORT OSTCBCur
IMPORT OSTaskSwHook
IMPORT OSTCBHighRdy;该变量指向任务切换后即将运行的任务的OS_TCB
OSCtxSw
STMFD sp!, {lr} ; OSCtxSw是被调用的,lr的值就是调用前的PC值,入栈
STMFD sp!, {r0-r12,lr} ; 将lr和其他寄存器入栈
MRS r4, cpsr ;通过MRS指令将cpsr入栈
STMFD sp!, {r4} ; 被挂起的当前任务的寄存器保存完毕,下面接着保存该
;任务的堆栈指针,以便下次恢复时,可以找到其堆栈指针,便可恢复其寄存器
LDR r4, =OSTCBCur ; 得到当前TCB块的地址,传给r4
LDR r5, [r4] ; 将OSTCBCur中的值传给r5,注意OSTCBCur存的是指针
STR sp, [r5] ; 将当前任务的sp传到OSTCBCur存的指针中去
; *****下面OSCtxSw准备恢复优先级更高的就绪任务,这部分可共用OSIntCtxSw的代码*****
; *****OSIntCtxSw函数开始*****
EXPORT OSIntCtxSw
IMPORT OSTaskSwHook
OSIntCtxSw ;准备任务切换
BL OSTaskSwHook ;调用Hook函数,此为空函数
LDR r4, =OSTCBHighRdy
LDR r4, [r4] ;将高优先级的任务栈顶指针存到r4中
LDR r5, =OSTCBCur
STR r4, [r5] ; OSTCBCur = OSTCBHighRdy
LDR r6, =OSPrioHighRdy;取出高优先级
LDRB r6, [r6] ;优先级,字节传送
LDR r5, =OSPrioCur
STRB r6, [r5] ; OSPrioCur = OSPrioHighRdy
LDR sp, [r4] ;从r4中取得要恢复的任务的栈顶指针
LDMFD sp!, {r4} ;弹出任务栈中的第一个参数,即cpsr
MSR cpsr_cxsf, r4 ;首先开始恢复cpsr
LDMFD sp!, {r0-r12,lr,pc} ;依次恢复r0~r12,lr,pc,任务切换
; *****OSTickISR开始*****
EXPORT OSTickISR
IMPORT OSIntEnter
IMPORT OSTimeTick
IMPORT OSIntExit
LINK_SAVE DCD 0 ;用来保存时钟节拍中断前的lr,以便计算出pc而使之入栈
PSR_SAVE DCD 0 ;用来保存中断前的spsr,中断产生时,svc模式下的cpsr存到spsr
OSTickISR ;时钟节拍中断服务程序入口,需要用户在主函数中安装
STMFD sp!, {r4} ;因为r4下面要使用,故先保存r4到irq模式的堆栈中
LDR r4, =LINK_SAVE ; 准备保存LR,SPSR,以便得到中断前的pc和cpsr_svc
STR lr, [r4] ; LINK_SAVE = lr_irq,此时lr=PC(中断发生前)+4
MRS lr, spsr ;lr已保存,用lr取得spsr(保存的是中断前的cpsr)
STR lr, [r4, #4] ; PSR_SAVE = spsr_irq
LDMFD sp!, {r4} ;恢复r4
ORR lr, lr, #0x80 ;在上下文切换前,屏蔽irq中断。注意lr存的是中断前的cpsr
MSR cpsr_cxsf, lr ;中断产生前是svc模式,故必须要切换到此模式下保存现场
SUB sp, sp, #4 ;按任务栈结构,空一个空间预留给PC
STMFD sp!, {r0-r12,lr} ; 依次保存lr、r12~r0
LDR r4, =LINK_SAVE ;准备保存pc,取得存svc模式下发生中断前lr的地址
LDR lr, [r4, #0]
SUB lr, lr, #4 ;中断前的pc = LINK_SAVE - 4,此前lr为异常前pc+4的值
STR lr, [sp, #(14*4)];保存pc到任务栈中预留的空间
LDR r4, [r4, #4] ;开始保存cpsr,r4 = PSR_SAVE,即中断前的cpsr_svc
STMFD sp!, {r4} ;保存svc模式下任务的cpsr,寄存器保护完毕
LDR r4, =OSTCBCur ;下面开始将该堆栈指针传给OSTCBCur所指向的指针
LDR r4, [r4] ;便于OSIntExit函数判断是否当前任务优先级最高
STR sp, [r4] ;在OSTCBCur->OSTCBstkptr保存被中断的任务的栈顶指针
BL OSIntEnter ;异常前的上下文保存好之后,开始准备中断服务,将OSIntNesting++
BL OSTimeTick ;将所有延时节拍不为1的任务的节拍数都减1,并清中断标志
BL OSIntExit ;将OSIntNesting--,并判断是否有高优先级任务就绪,若有,则调
;用OSIntCtxSw()调度该任务并不再返回; 若没有则返回到这里
LDMFD sp!, {r4} ;这里sp存的仍是调用OSIntEnter前的sp,即被中断的任务栈顶指针
MSR cpsr_cxsf, r4 ;从堆栈中恢复中断前任务的cpsr,注意此时irq才被重新允许
LDMFD sp!, {r0-r12,lr,pc} ;恢复中断前任务的 r0-r12,lr和pc,返回被中断的任务
;******OSTickISR函数代码完成,下面是临界段代码前后开关中断的函数******
EXPORT ARMDisableInt
ARMDisableInt
MRS r0, cpsr ;由于任务和内核都运行在svc模式下,因此可方便地操作cpsr
STMFD sp!, {r0} ; 保存当前的cpsr
ORR r0, r0, #0xc0 ;屏蔽FIQ,IRQ中断
MSR cpsr_c, r0 ;回写cpsr,只屏蔽IRQ中断
MOV pc, lr ;返回
EXPORT ARMEnableInt
ARMEnableInt ;必须和ARMDisableInt成对使用
LDMFD sp!, {r0} ;弹出在ARMDisableInt中被保存的cpsr
MSR cpsr_c, r0 ;恢复关中断前的cpsr
MOV pc, lr ;返回
END ;汇编代码结束
;*****OS_CPU_A.S文件结束******

4 小结
本文较详细地叙述了轻量级的实时内核µC/OS-II在ARM7处理器(S344B0X)的移植过程,并给
出了简练而实用的代码,移植代码和移植思想对于µC/OS-II在其他ARM处理器上的移植具有较强的借鉴意义。
一些航空电子设备比如发动机测控系统,由于功能复杂和实时性要求较高,各任务将激烈地竞争系统硬件资源,如何合理分配资源和及时调度任务则是摆在设计人员面前的重大问题。而RTOS在多任务调度和资源管理以及程序设计与系统维护升级上具有独特的优势,笔者相信RTOS在航空电子设备中应用将日趋广泛。

参考文献
1.Jean J.Labrosse. 嵌入式实时操作系统µC/OS-II. 北京航空航天大学出版社,2003.
2.徐睿等. 基于ARM的嵌入式系统开发与应用.人民邮电出版社,2004.
3.李岩等. 基于S3C44B0X嵌入式µClinux系统原理及应用.清华大学出版社,2005
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值