基于堆栈结构一致性实现的ΜC/OS-Ⅱ在ARM7上的移植

2009-09-26 09:37
新媒体研究 2009年18期

黄 睿

[摘要]讨论源代码开放的实时操作系统μC/OS-Ⅱ在目前流行的嵌入式微控制器ARM7上的移植,从非运行态任务的堆栈一致性这个角度来联通分析各个主要移植函数的编写。通过分析能更透彻的理解任务堆栈结构在操作系统移植中的重要性,对把操作系统移植到不同的处理器具有一定的参考价值。

[关键词]移植堆栈结构 μC/OS-Ⅱ ARM7

中图分类号:TP3文献标识码:A文章编号:1671-7597(2009)0920066-02

一、引言

在微处理器上引入操作系统代替传统的单片机前后台系统来管理整个系统,可以使系统整体性能得到明显优化。μC/OS-II是一个完整、可固化、可裁剪、可移植的占先式实时多任务内核,非常适合于在微处理器上进行移植。ARM7系列微处理器是目前使用最为广泛的微处理器之一,本文讨论的μC/OS-II在ARM7上的移植有着较为典型的意

义。

二、μC/OS-Ⅱ移植概述

移植就是使一个内核能够在某个微处理器上或者控制器上运行,也就是要为特定的CPU编写特定的底层代码。操作系统的移植涉及到处理器体系结构和编译器以及操作系统本身,是一项比较复杂的工作。μc/OS-Ⅱ的大部分代码是用C语言编写的,但移植时候还是需要用汇编语言编写一些与CPU硬件相关的代码。移植μC/OS-Ⅱ到一个微处理器上一般需要编写三个文件:C语言头文件OS_CPU.H,其中定义一些与编译器无关的数据类型以及与处理器有关的常量和宏;C程序源文件OS_CPU_C.C,其中定义了μC/OS-Ⅱ任务堆栈初始化函数以及钩子类函数;汇编程序源文件OS_CPU_A.S,其中定义了中断服务函数以及任务切换函数。这些移植文件的编写在很多文章中都有过详细的介绍[3][4],本文不再赘述。本文将从非运行态任务的堆栈一致性这个角度来联通各个主要移植函数的编写,以加深对μC/OS- II移植的理解。

三、移植难点

操作系统能够稳定的运行,关键在于任务能够稳定的切换。在μC/OS-Ⅱ中,任务通过模仿中断的方式来运行,并且拥有自己单独的任务堆栈;处于非运行态任务的堆栈结构看起来就像刚发生过中断一样,并且所有的寄存器都已经保存到堆栈中[2]。非运行态任务堆栈的一致性指任务没有处在运行态时其任务堆栈中保存的任务环境顺序具有一致性,这是任务切换能够稳定进行的最主要因素。大部分的处理器会通过提供软中断或者是陷阱的方法来实现任务的切换。而涉及到任务切换或者说是任务堆栈变化的移植函数则是移植的难重点,这些移植函数都要参考非运行态任务的堆栈结构来编写,以保证任务堆栈结构的一致性。本文将以μC/OS-Ⅱ在ARM7系列微处理器上的移植代码来分析讨论μC/OS-Ⅱ的移植难点,编译器为ADS1.2。注:移植程序来自周立功公司产品easyarm2200m[1]。

四、移植函数分析

涉及到任务堆栈的函数主要有堆栈初始化函数OSTaskStkInit(),任务级代码中的任务切换函数OS_TASK_SW(),中断退出时候的任务切换函数OSIntCtxSw(),首次进入多任务环境的OSStartHighRdy()以及中断汇编宏。

(一)函数OSTaskStkInit()

堆栈初始化函数OSTaskStkInit()由创建任务的函数调用,初始化非运行态任务的堆栈结构,它与cpu的体系结构有着密切的关系。这个堆栈结构一旦确立,以后所有的任务环境的压栈和出栈操作都必须参照这个堆栈结构来处理堆栈。在ARM7系列处理器上的任务堆栈结构如图1所示:

图1中的任务其它入栈数据是任务运行之后才有的,堆栈刚初始化后的任务堆栈栈底指向PC。堆栈初始化的一般顺序是先模拟中断到来时候处理器的动作,这里是保存中断返回地址PC以及连接寄存器LR;然后保存剩余的CPU寄存器,但是SP没有被保存在堆栈环境中,SP保存在任务控制结构TCB中以用于任务的切换。SPSR是没有必要保存在堆栈中的,因为任何中断或者异常打断的任务的状态都保存在堆栈的CPSR中。OsEnterSum是保存开关中断次数的变量,它的入栈使得各个任务开关中断的状态互不影响。

(二)函数OS_TASK_SW()

任务级代码中的任务切换函数OS_TASK_SW()是通过软件中断0号功能来实现的,软件中断向量直接指向汇编语言程序段OSIntCtxSw(程序段B+C)。在讨论切换函数之前先确定软件中断时候的堆栈结构(参考程序段A),软件中断将使系统进入管理模式。

SoftwareInterrupt

LDR SP, StackSvc ;设置管理模式下的堆栈指针

STMFD SP!, {R0-R3, R12, LR} ;保存任务环境,LR是管理模式下的

MOV R1,SP

MOV R3,SPSR

………………………

程序段A

调用OSIntCtxSw之前管理模式的堆栈结构如图2所示,位于系统模式下的任务堆栈并没有开始保存任务环境。OSIntCtxSw函数首先做的就是保存任务环境于任务堆栈(注意:R3保存有管理模式下的SPSR即系统模式下的CPSR,管理模式下的LR保存有系统模式下的PC)。

OSIntCtxSw

;先保存当前任务的任务环境

LDR R2,[SP,#20];获取管理模式下的LR

LDR R12,[SP,#16] ;获取R12

MRS R0,CPSR ;保存管理模式下的CPSR

MSR CPSR_c,#(NoInt | SYS32Mode);进入不带中断的系统模式

MOV R1,LR

STMFD SP!, {R1-R2} ;保存PC,LR于任务堆栈

STMFD SP!,{R4-R12} ;保存R4-R12于任务堆栈

MSR CPSR_c,R0 ;恢复原先模式

LDMFD SP!,{R4-R7};获取R0-R3,任务环境下的R4-R7已经保存

ADD SP,SP,#8 ;出栈R12,PC,图2堆栈指针指向栈底

MSR CPSR_c,#(NoInt | SYS32Mode)

STMFD SP!,{R4-R7};保存R0-R3于任务堆栈

LDR R1,=OsEnterSum

LDR R2,[R1] ;获取OsEnterSum值

STMFD SP!,{R2, R3};保存任务环境的CPSR以及OsEnterSum

;保存当前任务堆栈指针到当前任务的TCB

LDR R1,=OSTCBCur;R1保存有OSTCBCur的地址值

LDR R1,[R1] ;R1保存有OSTCBCur的首元素地址值

STR SP,[R1] ;把SP的值赋给OSTCBCur的首元素

…………………… ; B1

程序段B

系统模式下面的SP指向的是任务堆栈,而管理模式下面的SP指向的是管理模式堆栈,它对于每个任务来说不是独立的。程序段B在管理模式和系统模式下的来回跳转,为的只是在任务堆栈中保存如图1所示的任务堆栈结构,并且将软件中断对管理模式下堆栈的影响消除即其堆栈指针恢复到栈底。程序语句B1会把OSTCBHighRdy赋值给OSTCBCur,把OSPrioHighRdy赋值给OSPrioCur,标志新老任务交替的开始。

OSIntCtxSw_1;恢复新任务的任务环境

LDR R4,[R6];获取新任务堆栈指针SP

ADD SP,R4,#68 ;此时SP指向图1堆栈中PC+4

LDR LR,[SP,#-8] ;恢复LRC1

MSR CPSR_c,#(NoInt | SVC32Mode)

MOV SP,R4;设置管理模式下的堆栈指针

LDMFD SP!, {R4, R5};拷贝CPSR,OsEnterSum

LDR R3,=OsEnterSum

STR R4,[R3];恢复新任务的OsEnterSum

MSR SPSR_cxsf,R5 ;对管理模式下的SPSR进行修改,以便恢复任务环境CPSR

LDMFD SP!,{R0-R12,LR,PC }^ ;恢复其它环境,运行新任务

程序段C

程序语句C1恢复的是系统模式下的LR,在管理模式下是无法恢复的,因为管理模式和系统模式下LR寄存器的地址是不一样的。程序用管理模式下SP指向任务堆栈栈顶,然后先恢复CPSR,OsEnterSum,最后恢复系统模式下的R0-R12,PC。注意程序段C最后一句恢复的LR是管理模式下的LR,它不属于任务环境。寄存器组中含有PC并且有“^”后缀使得系统由管理模式返回系统模式,并且用管理模式的SPSR恢复新任务的CPSR,这样返回的任务才能正确的切换CPU的模式和状态。不难发现虽然程序比较复杂,但是依然是围绕图1的堆栈结构来对旧任务进行保存,对新任务环境进行恢复。

(三)函数OSStartHighRdy()

OSStartHighRdy()函数为第一次调用运行最高优先级的任务。其编程比较简单。其思想为把OSTCBHighRdy赋值给R6,然后直接转到OSIntCtxSw_

1处执行。

(四)函数OSIntCtxSw()以及中断汇编宏

中断退出时的任务切换函数OSIntCtxSw()只由OSIntExit()调用,而OSIntExit()只在中断汇编宏中被调用。OSIntExit函数中重新设置了OSTCBHighRdy但是肯定不会进行任务切换,因为移植程序中定义了OSIntCtxSw()为return,任务切换功能由中断汇编宏做出判断后调用OSIntCtxSw程序段来实现。中断汇编宏调用OSIntCtxSw之前要在中断模式下拥有图2相同的堆栈结构,并且R3要保存着中断模式下的SPSR(系统模式下的CPSR)。这样才能保证任务堆栈结构在进行切换的时候不至于出现混乱的现象。这些都是由IRQ汇编宏来实现。分析如下:

MACRO

………………………

SUB LR, LR, #4 ; 调整中断返回地址

STMFD SP!, {R0-R3, R12, LR}; 保存任务环境同图2D1

MRS R3, SPSR; 保存SPSR到R3

STMFD SP, {R3, SP, LR}^ ; 保存系统模式下的R3,SP,LR D2

……………………… ; OSIntNesting++

SUB SP, SP, #4*3 ;调整SP

MSR CPSR_c, #(NoInt | SYS32Mode)

CMP R1, #1

LDREQ SP, =StackUsr ;D3

BL $IRQ_Exception_Function ;调用C语言的中断处理程序

MSR CPSR_c, #(NoInt | SYS32Mode)

……………………

BL OSIntExit

……………………

MSR CPSR_c, #(NoInt | IRQ32Mode); 切换回irq 模式

LDMFD SP, {R3, SP, LR}^ ; D4

LDR R0, =OSTCBHighRdy

LDR R0, [R0]

LDR R1, =OSTCBCur

LDR R1, [R1]

CMP R0, R1; D5

ADD SP, SP, #4*3

MSR SPSR_cxsf, R3

LDMEQFD SP!, {R0-R3, R12, PC}^

LDR PC, =OSIntCtxSw ; 进行任务切换

MEND

程序段D

进入中断的时候,PC的值将会自动复制到中断模式下的LR中,CPSR复制到中断模式下的SPSR中。这些都是和软件中断没有什么太大的区别,但进入的异常模式不同,一个是管理模式一个是中断模式(OSIntCtxSw程序段的注释是在管理模式下调用时的注释,中断模式调用时的注释只要把程序段B注释中的管理模式改为中断模式即可)。在程序语句D1中保存的堆栈结构和图2一样,是为了使得调用OSIntCtxSw程序段之后旧任务保存的堆栈空间和图1一致。语句D2在中断模式下对系统环境的保存是为了在调用中断服务程序C语言函数时不破坏任务环境(特别是任务堆栈指针),保存的环境由编译器和处理器共同决定,在调用C语言函数之后由语句D4进行恢复。语句D3中的StackUsr是为中断服务程序C语言部分准备的堆栈栈顶,对任务堆栈没有影响。语句D5比较OSTCBHighRdy和OCSTCBCur的任务堆栈指针,如果相等说明当前任务为最高优先级任务,不用进行任务切换;反之,则需要调用OSIntCtxSw程序段进行任务切换。图2的堆栈结构其实是由中断程序与软件中断程序共同决定的,一旦决定了以后两者都必须严格遵从执行,这样两者都调用OSIntCtxSw程序段的时候就能保证堆栈的一致性了。中断汇编宏的编写使得所有中断服务程序的编写都变得简单,中断只需要用C语言编写其特定的中断功能部分。

五、总结

本文较新颖地从堆栈结构方面来分析了操作系统的移植。在上例的分析中,充分体现了堆栈结构对任务切换以及代码移植的重要性。从非运行态任务堆栈结构的一致性来联通分析各个移植函数的编写,让移植过程变得更清晰明了。

参考文献:

[1]周立功,ARM嵌入式系统基础教程[M].北京:北京航空航天大学,2005.1.

[2]陈是知,Μc/OS-Ⅱ内核分析、移植与驱动程序开发[M].北京:人民邮电出版社,2007.9.

[3]宋晖、高小明,基于ARM的嵌入式操作系统Μc/OS-II的移植[J]. 微计算机信息,2006, 22(2):135-136.

[4]王明奇,μC/OS-Ⅱ在ARM7微处理器上的移植[J].电脑与信息技术,2006,14(1):22-24.

作者简介:

黄睿(1980-),男,佈依族,贵州省兴仁县人,研究生,副科,研究方向:计算机科学与技术、数据库应用。