APP下载

基于Cortex-M3处理器的uC/OS-II移植方法的研究

2012-08-15王宜结

淮南师范学院学报 2012年5期
关键词:堆栈指针调用

王宜结

(淮南师范学院 电气信息工程学院,安徽 淮南 232038)

1 引言

随着应用系统复杂程度的不断提高,程序编制也变得越来越难控制。解决复杂问题的最好办法就把它分解成一个个相对简单的问题,即一个个单独的任务,分而治之。UC/OS-II是一个实时多任务操作系统,因其短小精悍又源代码开放,在一些小型系统中得到了较广泛的应用。在MCU上加载uC/OS-II操作系统,再对每个问题编写相应的任务代码,就可以实现复杂的控制和应用。

2 UC/OS-II简介

uC/OS-II是基于优先级的抢占式实时多任务操作系统,最多可管理64个任务,可固化,可剪裁,具体高稳定性和可靠性。它包含了实时内核、任务管理 、时间管理、任务间通信同步(信号量 ,邮箱,消息队列)和内存管理等功能。绝大部分代码用C语言写成,与硬件相关部分用汇编语言编写,最鲜明的特点是源代码是公开免费的,便于移植和维护。uC/OS-II是面向中小型嵌入式系统的,包含全部功能模块的内核大约为10KB,如果经过裁减只保留核心代码,则可压缩到3KB左右。RAM的占用量与系统中的任务数及堆栈空间大小有关,堆栈的大小取决于任务的局部变量、缓冲区大小及可能的中断嵌套层数。应用程序的时间精度由系统时钟节拍决定,uC/OS-II需要用户提供周期性的时钟信号源,用于实现时间延时和确认超时,一般时钟节拍在10到100Hz之间,因为uC/OS-II在每一个节拍都要检查有没有更高优先级的就绪任务在等待执行 ,若有 ,就要进行任务切换。所以时钟节拍率越高,系统的额外负荷就越重。

3 uC/OS-II移植到Cortex-M3处理器的详细过程

移植就是要修改与处理器有关部分的代码,也就是要修改以下三个文件:OS_CPU.H、OS_CPU.C及OS_CPU_A.ASM。其中OS_CPU.H文件包括了用#define语句定义的与处理器相关的常数、宏以及数据类型。对于Cortex-M3,用于开中断和关中断的两个宏可定义如下:

#define OS_ENTER_CRITICAL() {cpu_sr=OS_CPU_SR_Save();}

#define OS_EXIT_CRITICAL()

{OS_CPU_SR_Restore(cpu_sr);}

这两个宏用汇编代码实现如下:

OS_CPU_SR_Save;这个函数用于关中断

MRS R0,PRIMASK

CPSID I;关闭除硬fault以处的全部可屏蔽中断

BX LR;函数返回

OS_CPU_SR_Restore;这个函数用于开中断

MSR PRIMASK,R0;回到关中断之前的状态

BX LR;函数返回

uC/OS-II用宏“OS_STK_GROWTH”来设置堆栈的增长方向,值为0时表示堆栈从低地址向高地址增长,值为1则相反。由于Cortex-M3内核的堆栈是向下生长的满栈,故应把宏定义成“#define OS_STK_GROWTH 1”。定义数据类型宏比较简单,这里就不介绍了。

在OS_CPU.C文件中要求我们必须编写10个简单的C函数,它们是:

OSTaskStkInit();OSInitHookBegin();OSInitHook-End();OSTaskCreateHook();OSTaskDelHook();OSTask-IdleHook();OSTaskStatHook();OSTaskSwHook();OSTCBInitHook();OSTimeTickHook()。在这10个函数中唯一必要的是OsTaskStklnt()函数。其他9个函数是为了扩展用户功能而定义的钩子函数,这些钩子函数必须声明,但可以都为空函数,也可以加上一些用户需要的扩展功能。OsTaskStklnt()被任务创建函数OSTaskCreate()或OSTaskCreateEXT()调用,用来初始化任务的堆栈,任务堆栈通常用数组来定义。OsTaskStklnt()函数首先将用户为任务分配的堆栈栈底地址赋值给一个堆栈型指针变量,然后再通过这个堆栈指针向任务的栈空间写入初值。初始化后任务堆栈图1所示(堆栈空间大小为SIZE),其中task为要创建的任务代码首地址,其他15个寄存器的值可为任意值,通常初始化为0。OsTaskStklnt()返回任务堆栈指针,这个指针在任务创建函数调用任务控制块初始化函数OS_TCBInit()后存入任务控制块中。

图1 初始化后的堆栈

OS_CPU_A.ASM文件:在此文件中需改写 4个简单汇编语言函数:OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()、OSTickISR()。

下面详细分析这几个函数的实现过程:

OSStartHighRdy():这个函数被 OSStart()调用,并且只执行一次,它的主要功能是触发PendSV异常,PendSV异常的核心工作是任务的切换,对Cortex-M3内核,其代码可以写成:

PendSV_Handler

CPSID I;关中断

MRS R0,PSP;当前堆栈指针送给R0,首次运行任务时,PSP此前被置为0了。

CBZ R0,OSPendSV_nosave;首次运行任务时,不保存运行环境

SUBS R0,R0,#0x20;保存R4-R11到当前任务的堆栈

STM R0,{R4-R11};R0为当前任务的堆栈指针,要把它存到当前任务控制块中去

LDR R1,=OS_TCBCur;OSTCBCur-〉OSTCBStkPtr〈=当前任务SP;

LDR R1,[R1];任务控制块的第一个单元存放的是堆栈指针

STR R0,[R1];R0 isSP ofprocessbeing switched out;

至此,当前任务的上下文都保存起来了OSPendSV_nosave

PUSH {R14};需要保护 LR exc_return值

LDR R0,__OS_TaskSwHook;调 用 OSTaskSwHook();

BLX R0

POP {R14}

LDR R0,__OS_PrioCur;OSPrioCur=OSPrio-HighRdy;

LDR R1,__OS_PrioHighRdy

LDRB R2,[R1]

STRB R2,[R0]

LDR R0,__OS_TCBCur;OSTCBCur =OSTCBHighRdy;

LDR R1,__OS_TCBHighRdy

LDR R2,[R1]

STR R2,[R0]

LDR R0,[R2];R0是新的进程堆栈指针SP:SP 〈=OSTCBHighRdy-〉OSTCBStkPtr;

LDM R0,{R4-R11};从待运行任务的堆栈中恢复R4-R11

ADDS R0,R0,#0x20

MSR PSP,R0;PSP〈=新的进程堆栈指针SP

ORR LR,LR,#0x04;确保异常返回后使用进程堆栈

CPSIE I;开中断

BX LR;异常返回后将恢复待运行任务的上下文

当主函数调用OS_START()首次运行多任务时,先查找优先级最高的就绪任务,然后调用OS-StartRdy()触发PendSV异常(刚进入异常时,硬件自动保存8个寄存器,使用的是原来的堆栈指针),然后取中断向量进入中断服务,再将堆栈指针切换为主堆栈指针MSP。PendSV异常服务程序中,首先判断是不是第一次运行任务,如果是,则不需要保存任务的运行环境(因为尚无任务在运行),而只要恢复待运行任务的运行环境,即把任务堆栈中的16个寄存器(如图1)的值恢复到相应的寄存器中(这时这个待运行任务的堆栈指针又指向栈底),这其中包含了待运行任务代码的入口地址,它会被恢复到程序计数器PC中,PC得到待运行任务入口地址后就开始运行该任务。

当前运行的任务可能调用void OSTimeDly(INT16U ticks)函数来主动延时,这个函数会使任务延时ticks个时钟节拍,并通过调用OS_Sched();函数引发一次任务调度。通常把这种情况下的调度称作任务级调度,另一种调度是在中断返回时进行的,叫做中断级调度。OS_Sched()函数的功能是先查找任务就绪表中优先级最高的就绪任务,然后调用OS_TASK_SW()函数(这个函数在os_cpu.h中用宏定义成:“#define OS_TASK_SW()OSCtxSw()”)触发PendSV异常完成一次任务切换。PendSV异常从第二次被调用开始,就要先保存当前正在运行任务的运行环境 (即压入R4-R11共8个寄存器,另外8个寄存器(PSR,PC,LR,R12,R3-R0)在进入PendSV异常时由硬件自动压入待切换任务的堆栈保护)。PendSV异常最后4行的功能是:把待运行任务的堆栈指针赋给进程堆栈指针PSP,然后调整LR的值,确保返回后使用进程堆栈。由于LR在出入ISR的时候,其值得到了重新的诠释,这种特殊的值称为“EXC_RETURN”,EXC_RETURN 的D0位对于Cortex-M3核必须为1(D0=1表示返回Thumb状态),D1位保留,D2位非常重要:当D2=0时,从主堆栈中做出栈操作,返回后使用MSP;当D2=1时,从进程堆栈中做出栈操作,返回后使用进程堆栈。所以指令 “ORR LR,LR,#0x04”的功能就是控制从PendSV异常返回时,待运行任务保存在堆栈中的“PSR,PC,LR,R3-R0”这 8 个寄存器能正确地弹出到对应的寄存器中,从而实现恢复现场的目的。EXC_RETURN的D3位的功能是控制返回后进入Handler模式(=0时)还是进入线程模式(=1时)。这里不修改此值是为了返回时仍然进入原来的模式,31:4位必须全为1。

OSIntCtxSw()函数是中断级任务切换函数,即在中断返回时通过调用该函数触发PendSV异常实现任务切换,功能与OSCtxSw()几乎一样。

OSTickISR()函数即节拍中断服务,其代码如下:

{OS_CPU_SR cpu_sr;

OS_ENTER_CRITICAL();//保存全局中断标志,关总中断,通知uC/OS-II正在开始进行ISR服务

OSIntNesting++;//统计中断嵌套次数

OS_EXIT_CRITICAL();//恢复全局中断标志

OSTimeTick();/*在os_core.c文件里定义,主要判断延时的任务是否计时到,或正在等待事件的任务是否超时。将到时或超时的任务由原来的挂起状态置为就绪状态*/

OSIntExit();/*在os_core.c文件里定义,如果有更高优先级的任务就绪了,则执行一次任务切换。*/}

4 结束语

本文详细分析了uC/OS的移植过程。因为不同处理器字长、使用的开关中断指令、堆栈组织方式、寄存器的数量、进出中断时的具体行为不同,所以要针对具体使用的处理器来写这部分代码,这就是移植的本质。本文着重分析了任务切换的详细过程以及与之关联的部分,这也是移植过程的难点所在。实践表明,上述方法移植后的操作系统工作正常,能长时间稳定运行。

[1]陈启军.嵌入式系统及其应用[M].上海:同济大学出版社,2011

[2][美]Jean J.Labrosse.嵌入式实时操作系统UC/OS-II(第2版)[M].邵贝贝等译.北京:北京航空航天大学出版社,2003

猜你喜欢

堆栈指针调用
核电项目物项调用管理的应用研究
偷指针的人
LabWindows/CVI下基于ActiveX技术的Excel调用
嵌入式软件堆栈溢出的动态检测方案设计*
为什么表的指针都按照顺时针方向转动
基于堆栈自编码降维的武器装备体系效能预测
基于系统调用的恶意软件检测技术研究
基于改进Hough变换和BP网络的指针仪表识别
ARM Cortex—MO/MO+单片机的指针变量替换方法
利用RFC技术实现SAP系统接口通信