uCOS-II在ARM上的移植
详细方法:http://blog.eccn.com/space.php?uid=170730&do=blog&id=4847
移植工作介绍
实际上uC/OS-II可以简单地看作是一个多任务调度器,在这个任务调度器上完善地添加了与多任务操作系统相关的一些系统服务,如信号量、邮箱等。其90%的代码是用C语言写的,可以直接移植到有C语言编译器的处理器上。移植工作主要都集中在多任务切换的实现上,因为这部分代码用来保存和恢复CPU现场(即写/读相关寄存器),不能用C语言,只能使用汇编语言完成。
uC/OS-II的全部源代码量大约是6000-7000行,共15个文件。将 uC/OS-II 移植到ARM处理器上,需要修改三个与ARM体系结构相关的文件,代码量大约是500行。以下分别介绍这三个文件的移植工作。
OS_CPU.H OS_CPU_A.ASM OS_CPU_C.C
OS_CPU.H 文件
图1 ARM体系结构的寄存器位置
·1----数据类型定义
数据类型的修改与所用的编译器相关,不同的编译器使用不同的字节长度表示同一数据类型,比如int,同样在x86平台上,GNU的gcc编译为4 bytes,而MS VC++则编译为2 bytes。
·2-----堆栈单位
在任务切换时,CPU现场的寄存器将保存在当前运行任务的堆栈中,所以OS_STK 数据类型应该与CPU的寄存器长度一致。
typedef unsigned int os STK;
typedef unsigned char BOOLEAN; /* 布尔变量 */
typedef unsigned char INT8U; /* 无符号8位整型变量 */
typedef signed char INT8S; /* 有符号8位整型变量 */
typedef unsigned short INT16U; /* 无符号16位整型变量 */
typedef signed short INT16S; /* 有符号16位整型变量 */
typedef unsigned int INT32U; /* 无符号32位整型变量 */
typedef signed int INT32S; /* 有符号32位整型变量 */
typedef float FP32; /* 单精度浮点数(32位长度) */
typedef double FP64; /* 双精度浮点数(64位长度) */
typedef INT32U OS_STK; /* 堆栈是32位宽度 */
#define BYTE INT8S
#define UBYTE INT8U
#define WORD INT16S
#define UWORD INT16U
#define LONG INT32S
#define ULONG INT32U
·3-----堆栈增长方向
void OS_TASK_SW(void); /* 任务级任务切换函数 */
void OSStartHighRdy(void); /* 运行优先级最高的任务 */
void OS_ENTER_CRITICAL(void); /* 关中断 */
void OS_EXIT_CRITICAL(void); /* 开中断 */
#define OS_STK_GROWTH 1 /* 堆栈是从上往下长的 */
#define USR32Mode 0x10 /* 用户模式 */
#define SYS32Mode 0x1f /* 系统模式 */
#define NoInt 0x80
#ifndef USER_USING_MODE
#define USER_USING_MODE SYS32Mode /* 任务缺省模式 */
#endif
#ifndef OS_SELF_EN
#define OS_SELF_EN 0 /* 允许返回OS与任务分别编译、固化*/
#endif
OS_CPU_A
用汇编编写任务开始函数
OSStartHighRdy
MSR CPSR_c,#(NoInt | SYS32Mode )
LDR R1,=OSRunning
MOV R2,#1
STRB R2,[R1] ;OSRunning=1
BL OSTaskSwHook ;调用OSTaskSwHook()
B __OSStartHighRdy ;运行最高优先级任务
任务切换函数
OS_TASK_SW
STMFD SP!,{LR} ;保存当前任务的PC
STMFD SP!,{R0-R12,LR} ;依次保存R0-R12、LR
MRS R0,CPSR
STMFD SP!,{R0} ;保存CPSR
LDR R1,=OSTCBCur
LDR R1,[R1]
STR SP,[R1] ;OSTCBCur->OSTCBStkPtr = SP(在当前任务控制块中保存当前任务的堆栈指针)
BL OSTaskSwHook ;调用OSTaskSwHook()
LDR R3,=OSPrioCur
LDR R4,=OSPrioHighRdy
LDRB R4,[R4]
STRB R4,[R3] ;OSPrioCur=OSPrioHighRdy
中断任务切换函数
OSIntCtxSw
BL OSTaskSwHook ;调用钩子函数OSTaskSwHook()
MSR CPSR_c, #(NoInt | IRQ32Mode)
LDR R0, =OSTCBHighRdy
LDR R0, [R0]
LDR R1, =OSTCBCur
STR R0, [R1] ;OSTCBCur=OSTCBHighRdy
LDR R0,=OSPrioCur
LDR R1,=OSPrioHighRdy
LDRB R1,[R1]
STRB R1,[R0] ;OSPrioCur=OSPrioHighRdy
LDR R0,=IRQStack
LDR R0,[R0]
SUB R0,R0,#4 ;
MOV SP,R0 ;IRQ模式堆栈保留一个字
MSR CPSR_c, #(NoInt | SYS32Mode) ;进入系统模式
LDR R2, =OSTCBHighRdy
LDR R2, [R2]
LDR R2, [R2] ;取得新任务堆栈指针OSTCBHighRdy->OSTCBStkPtr并存入R2中
LDR R1,[R2] ;取得新任务的CPSR存入R1中
LDR R3,[R2,#15*4] ;取得新任务的PC存入R3中
STR R3,[R0] ;把新任务的PC存入IRQ模式的堆栈中
MSR CPSR_c, #(NoInt | IRQ32Mode) ;进入IRQ模式
MSR SPSR_cxsf,R1 ;把新任务的CPSR保存到IRQ模式的SPSR中
MSR CPSR_c, #(NoInt | SYS32Mode) ;进入系统模式
ADD R2,R2,#4 ;调整新任务堆栈指针
MOV SP,R2 ; 系统模式SP→|R0 |
LDMFD SP!,{R0-R12,LR} ;恢复新任务的R0-R12,LR, 系统模式SP→|PC |
ADD SP,SP,#4 ;调整系统模式堆栈指针
MSR CPSR_c, #(NoInt | IRQ32Mode) ;进入IRQ模式
LDMFD SP!,{PC}^ ;恢复新任务的CPSR、PC和IRQ模式SP
中断禁止和允许函数
OS_ENTER_CRITICAL
STMFD SP!,{R0}
MRS R0,CPSR
ORR R0,R0,#NoInt
MSR CPSR_cxsf,R0
LDMFD SP!,{R0}
MOV PC,LR
OS_EXIT_CRITICAL
STMFD SP!,{R0}
MRS R0,CPSR
BIC R0,R0,#NoInt
MSR CPSR_cxsf,R0
LDMFD SP!,{R0}
MOV PC,LR
节拍中断函数
OS_CPU_C.C 文件
·1-------任务堆栈初始化
在此讨论任务初始化时的堆栈设计,也就是在堆栈增长方向上如何定义每个需要保存的寄存器位置。在ARM体系结构下,任务堆栈空间由高至低依次将保存着pc、lr、r12、r11、r10、... r1、r0、CPSR、SPSR,有两点需要说明:一是,当前任务堆栈初始化完成后,OSTaskStkInit 返回新的堆栈指针STK,在 OSTaskCreate()执行时,将会调用 OSTaskStkInit 的初始化过程,然后通过OSTCBInit()函数调用,将返回的SP指针保存到该任务的TCB块中。二是,初始状态的堆栈是模拟了一次中断后的堆栈结构,因为任务创建后并不是直接就获得执行,而是通过OSSched()函数进行调度分配,满足执行条件后才能获得执行。为了使这个调度简单一致,就预先将该任务的PC指针和返回地址LR都指向函数入口,以便被调度时从堆栈中恢复刚开始运行时的CPU现场。
·2-------系统钩子函数(6个c函数)
在该文件中需要实现几个操作系统规定的hook函数,如下:
OSSTaskCreateHook( )
OSTaskDelHook( )
OSTaskSwHook( )
OSTaskStatHook( )
OSTimeTickHook( )
若无特殊需求,只需简单地将它们都实现为空函数即可。
任务堆栈初始化
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
opt = opt; /* 'opt' 没有使用。作用是避免编译器警告 */
stk = ptos; /* 获取堆栈指针 */
/* 建立任务环境,ADS1.2使用满递减堆栈 */
*--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 = (unsigned int) pdata; /* r0,第一个参数使用R0传递 */
*--stk = (USER_USING_MODE|0x00); /* spsr,允许IRQ, FIQ 中断 */
return (stk);
}
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()
分别是退出临界区和进入临界区的宏指令实现。主要用于在进入临界区之前关闭中断,在退出临界区的时候恢复原来的中断状态。它的实现比较简单,可以直接开关中断来实现,也可以通过保存关闭/恢复中断屏蔽位来实现。
堆栈由高地址向低地址增长,也与编译器有关,在函数调用时,入口参数和返回地址一般保存在当前任务的堆栈中,编译器的编译选项和由此生成的堆栈指令就会决定堆栈的增长方向。
#define OS_STK_GROWTH