引言
随着技术的进步,嵌入式系统设计及其应用在近年中,对人类生活产生了巨大影响,并将继续改变人们未来的生活方式。研究嵌入式系统,一个必不可少的基础工作就是实现嵌入式操作系统在相关处理器平台上的移植。本文基于目前应用非常广泛的ARM处理器体系结构,对uC/OS-II嵌入式实时操作系统内核的移植工作做了分析和介绍,并给出了在国内一个开源项目 SkyEye 仿真器上的移植实例。
表2 CSPR的模式位
表3 ARM寄存器的命名和含义
uC/OS-II 概述
uC/OS-II 是一个简单、高效的嵌入式实时操作系统内核,被应用到各种嵌入式系统中。目前,它支持 x86、ARM、PowerPC、MIPS 等众多体系结构,并有上百个商业应用实例,其稳定性和可用性是经过实践验证的。同时,它的源代码公开,可以从www.ucos-ii.com网站上获得全部源码以及其在各种体系结构平台上的移植范例。
最新的 uC/OS-II 2.0 版以上的内核都具有可抢占的实时多任务调度功能,另外它还提供了许多系统服务,例如信号量、消息队列、邮箱、内存管理、时间函数等,这些功能可以根据不同的需求进行裁减。可以说,uC/OS-II是一个具备现代操作系统特点的RTOS,同时它结构清晰、注解详尽,具有良好的可扩展性和可移植性,被广泛地应用于各种架构的微处理器上。
ARM 的体系结构
ARM是目前嵌入式领域由应用最广泛的RISC微处理器结构,以其低成本、低功耗、高性能等优点占据了嵌入式系统应用领域的领先地位。ARM系列的处理器当前有ARM7、ARM9、ARM9E、ARM10等多个产品,此外,作为ARM公司合作伙伴的Intel,也提供基于XScale微体系结构的相关处理器产品。所有的ARM处理器都共享ARM通用的基础体系结构,开发者在不同的ARM处理器上做操作系统移植时,将大大节省工作量,降低软件开发成本。
任何操作系统的移植都有相当一部分工作和所用处理器的体系结构密切相关,因此在做具体的移植工作之前,需要了解该处理器的体系结构和相应的汇编语言。下面将简要介绍ARM的体系结构,主要侧重于与移植相关的一些概念。
处理器模式(CPU Mode):
ARM处理器可以工作在7种模式下,如表1所示。
除usr模式以外的其他模式都叫做特权模式,除 usr 和 sys 外的其他5种模式叫做异常模式。在usr模式下,对系统资源的访问是受限制的,也无法主动地改变处理器模式。异常模式通常都和硬件相关,例如中断或执行未定义指令等。与移植相关的处理器模式有两种:svc态和irq态,分别指操作系统的保护模式和通用中断处理模式。这两种模式之间的转换可以通过硬件方式或软件方式。uC/OS-II内核在执行过程中,大部分时间工作在svc态,当有硬件中断,例如时钟中断到来时,CPU硬件自动完成从svc态进入irq态,在中断程序结束处,则需要通过编程的方法使得CPU从irq态恢复到svc态。
程序状态寄存器( PSR:Program status register )在任意一种处理器模式中,都使用同一个寄存器来标识当前处理器的工作模式,这个寄存器叫做 CPSR ( Current Program Status Register ),它的 [0--4] 位用来表示CPU Mode,表2 为CSPR的模式位每一种处理器异常模式,都有一个对应的SPSR ( Saved Program Status Register )寄存器,用来保存进入异常模式前的CPSR。SPSR的作用就是当CPU从异常模式退出时,通过一条简单的汇编指令就能够恢复进入异常模式前的CPSR,该值保存在当前异常模式的SPSR中。例如:当从usr态进入中断irq 态时,原先的 CPSR_all 将被保存在当前的 SPSR_irq中,类似的异常模式下的 SPSR 还有 SPSR_fiq、SPSR_svc、SPSR_abt、SPSR_und。非异常模式的 usr和sys模式下没有 SPSR,只有 CPSR。不能显式地指定把 CPSR 保存到某个异常模式下的 SPSR,比如 SPSR_irq,而必须是变更到 irq 态之后CPU自动完成,不能硬性赋值,因为SPSR_irq在其他状态下不可见。
1、 ARM寄存器:( register )
ARM处理器共有37个寄存器,其中31个是通用寄存器,包括一个程序计数器 PC。另外6个就是上面提到的程序状态寄存器。
·通用寄存器:
i. R0-R7:与所有处理器模式无关的寄存器,可以用作任何用途。
ii. R8-R14:与处理器模式有关的寄存器,在不同的模式下,对应到不同的物理寄存器。其中 R13又叫做SP,一般用于堆栈指针。R14又叫做lr,一般用于保存返回地址。这两个寄存器在每种异常模式下都对应到不同的物理寄存器上,例如LR_irq、LR_svc、LR_fiq 等。
iii. R15:又叫做程序计数器,即PC,所有的模式下都使用同一个PC。
·状态寄存器:
iv. CPSR:当前程序状态寄存器,所有的模式下都使用同一个 CPSR。
v. SPSR:保存的程序状态寄存器,每种异常模式下都有自己的SPSR,一共有5种SPSR,即 SPSR_irq、SPSR_fiq、SPSR_svc、SPSR_abt、SPSR_und。usr和sys 态下没有 SPSR 。
所有的ARM寄存器的命名和含义,可以用表3来说明,其中相同命名的都是同一个物理寄存器,不同命名的寄存器都对应不同的物理寄存器。
表3 ARM寄存器的命名和含义
移植工作介绍
实际上uC/OS-II可以简单地看作是一个多任务调度器,在这个任务调度器上完善地添加了与多任务操作系统相关的一些系统服务,如信号量、邮箱等。其90%的代码是用C语言写的,可以直接移植到有C语言编译器的处理器上。移植工作主要都集中在多任务切换的实现上,因为这部分代码用来保存和恢复CPU现场(即写/读相关寄存器),不能用C语言,只能使用汇编语言完成。
uC/OS-II的全部源代码量大约是6000-7000行,共15个文件。将 uC/OS-II 移植到ARM处理器上,需要修改三个与ARM体系结构相关的文件,代码量大约是500行。以下分别介绍这三个文件的移植工作。
OS_CPU.H 文件
图1 ARM体系结构的寄存器位置
图2 任务堆栈初始化程序
·数据类型定义
数据类型的修改与所用的编译器相关,不同的编译器使用不同的字节长度表示同一数据类型,比如int,同样在x86平台上,GNU的gcc编译为4 bytes,而MS VC++则编译为2 bytes。本文使用GNU 的 arm-elf-gcc,相关的数据类型定义如下:
·堆栈单位
在任务切换时,CPU现场的寄存器将保存在当前运行任务的堆栈中,所以OS_STK 数据类型应该与CPU的寄存器长度一致。
typedef unsigned int os STK;
·堆栈增长方向
堆栈由高地址向低地址增长,也与编译器有关,在函数调用时,入口参数和返回地址一般保存在当前任务的堆栈中,编译器的编译选项和由此生成的堆栈指令就会决定堆栈的增长方向。
#define OS_STK_GROWTH
·宏定义
包括开关中断的宏定义,以及进行任务切换的宏定义。
#define OS_ENTER_CRITICAL() ARMDisable Int()
#define OS_EXIT_CRITICAL() ARMEnable Int()
#define OS_TASK_SW() OSCtxSw()
OS_CPU_C.C 文件
·任务堆栈初始化
在此讨论任务初始化时的堆栈设计,也就是在堆栈增长方向上如何定义每个需要保存的寄存器位置。在ARM体系结构下,任务堆栈空间由高至低依次将保存着pc、lr、r12、r11、r10、... r1、r0、CPSR、SPSR,如图1所示。
图1 ARM体系结构的寄存器位置
有两点需要说明:一是,当前任务堆栈初始化完成后,OSTaskStkInit 返回新的堆栈指针STK,在 OSTaskCreate()执行时,将会调用 OSTaskStkInit 的初始化过程,然后通过OSTCBInit()函数调用,将返回的SP指针保存到该任务的TCB块中。二是,初始状态的堆栈是模拟了一次中断后的堆栈结构,因为任务创建后并不是直接就获得执行,而是通过OSSched()函数进行调度分配,满足执行条件后才能获得执行。为了使这个调度简单一致,就预先将该任务的PC指针和返回地址LR都指向函数入口,以便被调度时从堆栈中恢复刚开始运行时的CPU现场。
·系统钩子函数
在该文件中需要实现几个操作系统规定的hook函数,如下:
OSSTaskCreateHook( )
OSTaskDelHook( )
OSTaskSwHook( )
OSTaskStatHook( )
OSTimeTickHook( )
若无特殊需求,只需简单地将它们都实现为空函数即可。
OS_CPU_A.S 文件
·OSStartHighRdy()
此函数是在OSStart()多任务启动后,负责从最高优先级任务的TCB控制块中获得该任务的堆栈指针SP,通过SP依次将CPU现场恢复,这时系统就将控制权交给用户创建的该任务进程,直到该任务被阻塞或者被其他更高优先级的任务抢占CPU。该函数仅在多任务启动时被执行一次,即执行最高优先级任务,之后多任务的调度和切换由以下函数实现。
·OSCtxSw()
任务级的上下文切换,当任务因为被阻塞而主动请求CPU调度时被执行,由于此时的任务切换在非异常模式下进行,因此区别于中断级别的任务切换。它的工作是先将当前任务的CPU现场保存到该任务堆栈中,然后获得最高优先级任务的堆栈指针,从该堆栈中恢复此任务的CPU现场,使之继续执行。这样就完成了一次任务切换。
·OSIntCtxSw()
中断级的任务切换,在时钟中断ISR(中断服务例程)中发现有高优先级任务等待的时钟信号到来,则在中断退出后并不返回被中断任务,而是直接调度就绪的高优先级任务执行,从而能够尽快地让高优先级的任务得到响应,保证系统的实时性能。其原理基本上与任务级的切换相同,但是由于进入中断时已经保存了被中断任务的CPU现场,因此不用再进行类似的操作,只需对堆栈指针做相应调整。
·OSTickISR()
时钟中断处理函数,其主要任务是负责处理时钟中断,调用系统实现的OSTimeTick函数,如果有等待时钟信号的高优先级任务,则需要在中断级别上调度其执行。其他相关的两个函数是OSIntEnter()和OSIntExit(),都需要在ISR中执行。
·ARMEnableInt()& ARMDisableInt()
分别是退出临界区和进入临界区的宏指令实现。主要用于在进入临界区之前关闭中断,在退出临界区的时候恢复原来的中断状态。它的实现比较简单,可以直接开关中断来实现,也可以通过保存关闭/恢复中断屏蔽位来实现。
全部移植代码在SkyEye仿真器上调试通过,在SkyEye的主页上可以下载获得(http://hpclab.cs.tsinghua.edu.cn/~skyeye/)。
结语
uC/OS-II作为一个优秀的实时操作系统已经被移植到各种体系结构的微处理器上,而ARM体系结构在嵌入式领域也获得了广泛的应用和支持。将uC/OS-II 移植到ARM平台上,能够使我们更深入地了解实时操作系统的构造,加快在ARM平台上的应用和开发,并为更高层次上的扩展和改进打下基础。
参考文献
1 ARM Architecture Reference Manual. http://www.arm.com
2 《ARM 嵌入式处理器结构与应用基础》.马忠梅等编著.北京航空航天大学出版社.2002年出版
3《uC/OS-II -源码公开的实时嵌入式操作系统》.Jean J.Labrosse 著.劭贝贝译.中国电力出版社.2001年出版
4 uC/OS 网站.http://www.ucos-ii.com