uC/OS-II读做“micro COS 2”,意为“微控制器操作系统版本2”。uC/OS-II是源代码开放的实时性内核,可用于各类8位、16位和32位单片机。
uC/OS-II是一个完整、可移植、可固化及可裁剪的占先式实时多任务内核。uC/OS-II用ANSI C语言编写,包含一小部分汇编代码,使之可以供不同架构的微处理器使用。
1 编译器选择:ADS1.2
2 任务模式的取舍
ARM7处理器核具有用户、系统、管理、中止、未定义、中断和快中断7种模式,其中除用户模式外,其他均为特权模式。管理、中止、未定义、中断和快中断与响应异常相联系,任务使用这些模式不合适。而系统模式除了是特权模式外,其他与用户模式一样,因此可选给任务使用的模式只有用户模式和系统模式。为了尽量减少任务代码错误对整个程序的影响,缺省的任务模式定位用户模式,可选为系统模式,同时提供接口是任务可以在这两种模式间切换。
3 支持的指令集
带T变量的ARM7处理器具有两个指令集:标准32位ARM指令集合16位Thumb指令集,两种指令集有不同的应用范围。为了最大的支持芯片的特性,任务应当可以使用任意一个指令集并可以自由切换,而且不同的任务应当可以使用不同的指令集。
根据uC/OS-II的要求,移植uC/OS-II到一个新的体系结构上需要提供2个或3个文件:OS_CUP.H(C语言头文件)、OS_CUP_C.C(C程序源文件)及OS_CUP_A.ASM(汇编程序源文件),其中OS_CPU_A.ASM在某些情况下不需要,但极其罕见。
本移植包含OS_CPU. h、OS_CPU_C. c及OS_CPU_A. s三个文件。将OS_CPU_A.asm更名为OS_CPU_A.s是遵照编译器的惯例。
uC/OS-II要求所有.C文件都要包含头文件includes.h,这样使得用户项目中的每个.C文件不用分别去考虑它实际上需要那些头文件。使用includes.h的缺点是可能会包含一些实际不相关的头文件,这意味着每个文件的编译时间会增加,但却增强了代码的可移植性。
1. 不依赖于编译的数据类型
uC/OS-II不使用C语言中的short,int和long等数据类型的定义,因为他们与处理的类型有关,隐含着不可移植性。代之以移植性强的整数数据类型,这样既直观又可移植。
不依赖于编译器的数据类型
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U
typedef signed char INT8S
typedef unsigned short INT16U
typedef signed short INT16S
typedef unsigned int INT32U
typedef signed int INT32S
typedef float FP32;
typedef double FP64
typedef INT32U OS_STK
2. 使用软中断SWI做底层接口
为了是底层接口函数与处理器状态无关,同时在任务调用相应的函数不需要知道函数位置,本移植使用软中断指令SWI作为底层接口,使用不同的功能号区分不同的函数。
ADS关键字__swi,用它声明一个不存在的函数,则调用这个函数就在调用这个函数的地方插入一条SWI(ARM指令)指令,并且可以指定功能号。通过__SWI指令声明的函数当程序调用时会跳转到SoferwareInterrupt软件中断的汇编接口。
注:SWI指令用于产生软中断,从而实现从用户模式转变到管理模式。
SWI服务函数
__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */
__swi(0x01) void _OSStartHighRdy(void); /* 运行优先级最高的任务 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断 */
__swi(0x80) void ChangeToSYSMode(void); /* 任务切换到系统模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任务切换到用户模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任务代码是ARM代码 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任务代码是THUMB */
3. OS_STK_GROWTH指定堆栈的生长方向
为0,表示堆栈从下往上长;
为1,表示堆栈从上往下长
ARM处理器核对两种方式均支持。ADS的C语言编译器,只支持从上往下长。
OSTaskStkInt()任务堆栈初始化函数,在编写此函数之前,必须先确定任务的堆栈结构。而任务的堆栈结构是与CPU的体系结构、编译器有密切的关联。本移植的堆栈结构见图2.1所示。
图2.1 任务堆栈结构图
1. 函数OSTaskStkInt()代码
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 中断 */
*--stk = 0; /* 关中断计数器OsEnterSum; */
return (stk);
}
OsEnterSum是作者定义的一个全局变量,主要是用它来保存关中断的次数,这样关中断和开中断就可以嵌套了。在调用OS_ENTER_CRITICAL()时,它的值加1,同时关中断。在调用OS_EXIT_CRITICAL()时,它的值减1,同时开中断。每个任务都有独立的OsEnterSum,在任务切换时保存和恢复各自的OsEnterSum。这样,各个任务开关中断的状态可以不同,任务不必过分考虑关中断对别的任务的影响。
说明:用户创建任务时,OSTasKCreat()会调用OSTaskStkInt()函数初始化该任务的堆栈,并把返回的堆栈指针保存到该任务的TCB结构中的最前面的参数OSTCBStkPtr中,当该任务要被恢复时,任务切换函数从其TCB块中取得其任务堆栈指针,依次将堆栈内容弹到处理器对应的CPSR、r0、r1,…,r12,lr,pc的寄存器中,完成现场的恢复和程序指针PC的返回。
2. 软件中断异常SWI服务程序C语言部分
参数SWI_Num为功能号,而Regs为指向堆栈中保存寄存器的值的位置。软中断的0、1号功能并没有在这里实现,而是在OS_CUP_A.S中实现。
软中断的C语言部分代码
void SWI_Exception(int SWI_Num, int *Regs)
{
OS_TCB *ptcb;
switch(SWI_Num)
{
//case 0x00: /* 任务切换函数OS_TASK_SW,参考os_cpu_s.s文件 */
// break;
//case 0x01: /* 启动任务函数OSStartHighRdy,参考os_cpu_s.s文件 */
// break;
case 0x02: /* 关中断函数OS_ENTER_CRITICAL(),参考os_cpu.h文件 */
__asm /*C语言内嵌汇编指令关键字,通过__asm关键字可以在C程序中内嵌汇编程序*/
{
MRS R0, SPSR /*读取SPSR寄存器的值,保存到r0中
ORR R0, R0, #NoInt /*寄存器r0与立即数NoInt相或,并保存到r0中,
MSR SPSR_c, R0 /*将r0的值写入SPSR[7:0],关中断
}
OsEnterSum++;
break;
case 0x03: /* 开中断函数OS_EXIT_CRITICAL(),参考os_cpu.h文件 */
if (--OsEnterSum == 0)
{
__asm
{
MRS R0, SPSR
BIC R0, R0, #NoInt
MSR SPSR_c, R0
}
}
break;
case 0x80: /* 任务切换到系统模式 */
__asm
{
MRS R0, SPSR
BIC R0, R0, #0x1f /* 将R0的低5位清零,其它位不变 */
ORR R0, R0, #SYS32Mode
MSR SPSR_c, R0
}
break;
case 0x81: /* 任务切换到用户模式 */
__asm
{
MRS R0, SPSR
BIC R0, R0, #0x1f
ORR R0, R0, #USR32Mode
MSR SPSR_c, R0
}
break;
case 0x82: /* 任务是ARM代码 */
if (Regs[0] <= OS_LOWEST_PRIO)
{
ptcb = OSTCBPrioTbl[Regs[0]];/*ptcb指向当前任务堆栈的首
地址,也就是当前任务堆栈结构的OsEnterSum
if (ptcb != NULL)
{
ptcb -> OSTCBStkPtr[1] &= ~(1 << 5) /*OSTCNStkPtr指向任务堆栈的栈顶,所以OSTCBStkPtr[1]指向任务堆栈的SPSR寄存器,通过改变SPSR相应位切换处理器模式
}
}
break;
case 0x83: /* 任务是THUMB代码 */
if (Regs[0] <= OS_LOWEST_PRIO)
{
ptcb = OSTCBPrioTbl[Regs[0]];
if (ptcb != NULL)
{
ptcb -> OSTCBStkPtr[1] |= (1 << 5);
}
}
break;
default:
break;
}
3. 宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()
这里需要对关中断函数和开中断函数作些解释。与所有的实时内核一样,μC/OS-Ⅱ需要先禁止中断再访问代码的临界段,并且在访问完毕后重新允许中断。这就使得μC/OS-Ⅱ能够保护临界段代码免受多任务或中断服务破坏。实现OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这2个宏的定义有3种方法,本移植采用的是第三种方法,即在OS_CPU.H文件中使OS_CRITICAL_MEHTOD等于3,这种方法是利用某些编译器提供的扩展功能,用户可以得到当前处理器状态字的值,并将其保存在C函数的局部变量之中,这个变量可以用于恢复PSW,而本ARM内核关中断和开中断时,是通过改变程序状态寄存器CPSR中的相应控制位实现。由于使用了软件中断,程序状态寄存器 CPSR保存到程序状态保存寄存器 SPSR中,软件中断退出时会将SPSR恢复到CPSR中。所以程序只要改变程序状态保存寄存器SPSR中相应的控制位就可以了。
4. OSStartHighRdy
uC/OS-II启动多任务环境的函数是OSStart()。用户在调用OSStart()之前,必须已经建立了一个或更多任务。OSStart()最终调用函数OSStartHighRdy()运行多任务启动前优先级最高的任务,OSStartHighRdy()的代码如下。
void OSStartHighRdy(void)
{
_OSStartHighRdy();
}
通过函数_OSStartHighRdy()进入软中断SoftwareInterrupt,最终调用__OSStartHighRdy。__OSStartHighRdy代码如下所示
__OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode);/*关中断,切换到系统模式
/*告诉uC/OS-II自身已经运行,将1保存到OSRunning*/
LDR R4, =OSRunning /*将OSRunning的地址加载到R4,
R4里存放的是OSRunning的地址
MOV R5, #1
STRB R5, [R4] /*将R5的值保存到R4指定的地址中,
只存储1字节数据
BL OSTaskSwHook ;调用钩子函数
LDR R6, =OSTCBHighRdy /*将OSTCBHighRdy的地址加载到R6
LDR R6, [R6] /*加载R6指定地址上的数据(字),放入R6中
B OSIntCtxSw_1
5. 移植增加的特定函数
根据ARM核心的特点和移植目标,为此增加了两个处理器模式转换函数(ChangTo-SYSMode()、ChangeToUSRMode())和两个任务初始指令集设置函数(TaskIsARM()、TaskIsTHUMBle())。他们都是通过中断指令SWI转换到系统模式,通过软件中断服务程序实现的。
ChangeToSYSMode()把当前任务转换到系统模式,ChangeToUSRMode()把当前任务转换到用户模式。她们改变程序状态保存寄存器SPSR的相应位段,而程序状态保存寄存器会在软件中断退出时复制到程序状态寄存器CPSR,任务的处理器模式就改变了。
本移植增加了两个函数TaskIsARM()和TaskIsTHUMB()用于改变任务建立时默认的指令集。函数TaskIsARM()用于声明指定优先级的任务的第一条指令是ARM指令集中的指令,而函数TaskIsTHUMB()用于声明指定优先级的任务的第一条指令是THUMB指令集中的指令,她们都有唯一的参数,即需要改变的任务的优先级。值得注意的是,这两个函数必须在相应的任务建立后但还没运行时调用。这样,如果在低优先级的任务中创建高优先级的任务就十分危险了。此时,解决的方法有三种:
(1) 高优先级任务使用默认的指令集;
(2) 改变函数OSTaskCreateHook()使任务默认不是出于就绪状态,建立任务后调用函数OSTaskResume()来使任务进入就绪状态。
(3) 建立任务时禁止任务切换,调用函数TaskIsARM()或TaskIsTHUMB()后再允许任务切换。
6. …Hook()函数
uC/OS-II有很多由用户编写的…Hook()函数,它在移植中全为空函数,用户就可以按照uC/OS-II的要求修改它。
1. 软件中断的汇编接口
软件终端代码的汇编部分:
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针
STMFD SP!, {R0-R3, R12, LR}; 将R0-R3,R12,LR压入堆栈,SP指向LR
MOV R1, SP ; R1指向参数存储位置, 因为SWI软中断
MRS R3, SPSR ; 读取SPSR寄存器的值,保存到R3中
TST R3, #T_bit ; 中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号。NE不相等,H加载半字数据。将LR-2地址处的数据读出,保存在R0中,LR的值不变。
BICNE R0, R0, #0xff00 ; 将R0的高8位清零,其它位不变
LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号
BICEQ R0, R0, #0xFF000000 ; 将R0的高8位清零
; r0 = SWI号,R1指向参数存储位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw ; 小于,跳到OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ;等于,跳到__OSStartHighRdy,SWI 0x01为第一次任务切换
BL SWI_Exception
LDMFD SP!, {R0-R3, R12, PC}^;将R0-R3,R12出栈
;后缀”!”表示最后的地址写回到SP中。
;后缀”^”不允许在用户模式或系统模式下使用,若在LDM指令且寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,将SPSR也拷贝到CPSR中,这可用于异常处理返回。使用“^”后缀进行数据传送且寄存器列表不包含PC时,加载/存储的是用户模式的寄存器,而不是当前模式的寄存器。
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)
程序在一开始设置堆栈指针。软中断指令使处理器进入管理模式,而用户程序处于系统/用户模式,其它异常也有自己的处理器模式,都有各自的堆栈指针,不会因为给堆栈指针赋值而破坏其它处理器模式的堆栈而影响其它程序的执行。
软中断的功能号是包含在SWI指令当中的。程序通过读取该条指令的相应位段获得。由于ARM处理器核具有两个指令集,两个指令集的指令长度不同,SWI指令的功能号的位段也不同,所以程序先判断在进入软中断前处理器是在什么指令集状态,根据指令集状态的不同取出相应的SWI指令的功能号。然后,程序用功能号与1比较,当功能号符号小于1即为 0时,就跳转到任务切换函数OSIntCtxSw处。当功能号等于1时,就跳转到第一次任务切换OSStartHighRdy处。其它部分是给软件中断的C语言处理函数处理。这里有两个参数,第一个就是功能号,存于R0中;第二个是保存参数和返回值的指针,也就是堆栈中存储用户函数R0—R4的位置,就是当前堆栈指针的值,它存于R1中。
这里对功能号0说明一下,功能号0跳转到OSIntCtxsw程序段,而这段程序实际是实现了OS_TASK_SW()函数的功能。OS_TASK_SW()是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,因为ARM处理器核具有两个指令集,在执行Thumb指令的状态时不是所有寄存器都可见(参考ARM的相关资料),而且任务又可能不在特权模式(不能改变CPSR)。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0实现OS_TASK_SW()的功能,即使用SWI 0X00代替 OSTASKSW(),具体实现方法见下一小节。
2. OS_TASK_SW()和OSIntCtxSw()
OS_TASK_SW是在uC/OS-II从低优先级任务切换到最高优先级任务时被调用的,OS_TASK_SW()总是在任务级代码中被调用的,另一个函数OSIntExit()被用来在ISR使得更高优先级任务处于就绪状态时,执行任务切换功能,它最终调用OSIntCtxSw()执行任务切换。OS_TASK_SW()是使用SWI软件中断的0号功能实现的。根据程序清单可知,它是调用OSIntCtxSw实现的。有SoftwareInterrupt程序清单可知,此时的堆栈结构如图7.2所示。同时,R3保存至SPSR。这样如果调用OSIntCtxSw()时需要相同的堆栈结构,R3也要保存着SPSR,这需要中断服务程序保证。OSIntCtxSw()代码如下所示。
图2.2 调用OSIntCtxSw时的堆栈结构
OSIntCtxSw
;下面为保存任务环境
LDR R2, [SP, #20] ;获取PC (1)
LDR R12, [SP, #16] ;获取R12 (2)
MRS R0, CPSR ;R0保存管理模式CPSR (3)
MSR CPSR_c, #(NoInt | SYS32Mode) ;关中断,切换到系统模式 (4)
MOV R1, LR ;R1保存系统模式LR (5)
STMFD SP!, {R1-R2} ;保存LR,PC (6)
STMFD SP!, {R4-R12} ;保存R4-R12 (7)
MSR CPSR_c, R0 ;切换到管理模式 (8)
LDMFD SP!, {R4-R7} ;获取R0-R3,保存到R4-R7 (9)
ADD SP, SP, #8 ;出栈R12,PC (10)
MSR CPSR_c, #(NoInt | SYS32Mode) ;关中断,切换到系统模式 (11)
STMFD SP!, {R4-R7} ;保存R4-R7,也就是保存R0-R3 (12)
LDR R1, =OsEnterSum ;R1保存OsEnterSum的地址 (13)
LDR R2, [R1] ;R2保存R1保存的地址的值 (14)
STMFD SP!, {R2, R3} ;保存OsEnterSum,CPSR (15)
;保存当前任务堆栈指针到当前任务的TCB
LDR R1, =OSTCBCur ;将OSTCBCur的地址保存在R1中 (16)
LDR R1, [R1] ;加载R1指定地址上的数据,放在R1中(17)
STR SP, [R1] ;将SP的数据保存到R1指定的地址中 (18)
BL OSTaskSwHook ;调用钩子函数 (19)
;OSPrioCur← OSPrioHighRdy
LDR R4, =OSPrioCur ;将OSPrioCur的地址保存到R4 (20)
LDR R5, =OSPrioHighRdy ;将OSPrioHighRdy的地址保存到R5 (21)
LDRB R6, [R5] ;加载R5指定地址上的数据,放在R6中(22)
STRB R6, [R4] ;将R6的数据保存到R4指定的地址 (23)
;OSTCBCur← OSTCBHighRdy
LDR R6, =OSTCBHighRdy ;将OSTCBHighRdy的地址保存到R6 (24)
LDR R6, [R6] ;加载R6指定地址上的数据,放在R6中(25)
LDR R4, =OSTCBCur ;将OSTCBCur的地址保存到R4 (26)
STR R6, [R4] ;加载R6的数据保存到R4指定的地址 (27)
OSIntCtxSw_1
;获取新任务堆栈指针, OSTCBHirtRdy->OSTCBStkPtr保存的是任务堆栈位置
LDR R4, [R6] ;加载R6指定地址上的数据,放在R4中 (28)
ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP(29)
LDR LR, [SP, #-8] ;将[SP-8]指定地址的数据加载到LR (30)
MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式 (31)
MOV SP, R4 ;设置堆栈指针 (32)
LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum (33)
;恢复新任务的OsEnterSum
LDR R3, =OsEnterSum ;将OsEnterSum的地址保存在R3 (34)
STR R4, [R3] ;加载R4的数据保存到R3指定的地址 (35)
MSR SPSR_cxsf, R5 ;SPSR=R5;恢复CPSR (36)
LDMFD SP!, {R0-R12, LR, PC }^ ;运行新任务 (37)
这部分代码基本按照μC/OS-Ⅱ提供的函数原型编写的,其中程序清单(1)—(18)部分与OSCtxSw()和OSIntCtxSw( )的原型是没有对应语句的,寄存器应当保存到任务的堆栈中,但为了节省CPU的时间和RAM的空间,仅在必要的时候才将寄存器保存到任务堆栈。OSTCBCur->OSTCBStkPtr=SP也是在必要的时候才执行的。这部分正是在处理这两件事情,其流程图见图2.3。
图2.3 OSIntCtxSw部分代码流程图
由软中断的汇编与C接口程序可知,在调用OS_TASK_SW( )(即软件中断的功能号0)时,寄存器R0—R3、R12、PC已经保存到当前模式(管理模式)的堆栈中,任务的R4—R11、SP、LR没有发生变化。也就是说寄存器已经保存到了管理模式堆栈,但不是保存到任务的堆栈中。同样由异常处理代码与C语言的接口程序可知,在调用OSIntCtxSw( )时,寄存器R0—R3、R12、PC已经保存到当前模式的堆栈中,任务的R4—R11、SP、LR没有发生变化。也就是说寄存器已经保存,但不是保存到任务的堆栈中。当前处理器模式的堆栈结构如图2.4所示。
图2.4 当前处理器模式堆栈结构图
在执行(1)-(18)这部分程序时的寄存器和存储器之间的具体关系如图2.5~2.8所示,图中的标号为对应的程序段,这里是以调用OS_TASK_SW( )为例子来说明,调用OSIntCtxSw( )的工作过程和调用OS_TASK_SW( ) 的工作过程是一样的,只不过调用OS_TASK_SW( )时处理器当前模式为管理模式,而调用OSIntCtxSw( )时处理器当前模式为IRQ模式。
图2.5
此时处理器处于管理模式,因为本移植是使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0来实现OS_TASK_SW( )的功能。具体参见软中断的汇编与C接口程序代码。
图2.6
此时是通过执行程序段(4),利用MSR指令直接设置状态寄存器CPSR的模式位,使处理器进入系统模式此时需要注意的是处理器的可见寄存器与管理模式时是有区别的。在保存之后同样是通过执行程序段(8),利用MSR指令返回到管理模式。
图2.7
进入管理模式以后继续保存数据,当数据保存好以后,要注意调整当前模式堆栈指针因为当前模式的入栈比出栈的数据多,而在堆栈中剩余的数据(R12、PC)已经没有用处,所以要进行调整。完成指针调整的为程序段(10)。
图2.8
LDR R1, =OsEnterSum ;R1保存OsEnterSum的地址 (13)
LDR R2, [R1] ;R2保存R1保存的地址的值 (14)
STMFD SP!, {R2, R3} ;保存OsEnterSum,CPSR (15)
此时又使处理器进入用户/系统模式,继续把未保存的寄存器内容保存到任务堆栈,这里要注意的是R3保存着任务的CPSR(既当前模式的SPSR),这部分在异常处理代码与C语言的接口程序中完成的。至此,寄存器内容已经完全保存到任务堆栈里了。
从标号OSIntCtxSw_1处开始至程序的最后,是将所有处理器寄存器从新任务的堆栈中恢复出来。处理器执行这段代码时处于ARM状态,但用户任务可能处于Thumb状态。而由ARM的相关资料可知,程序不可以直接改变程序状态寄存器的CPSR的T位来改变处理器的状态。但“LDMFD SP!, {R0-R12, LR, PC }^ ” 异常返回指令是可以使用的(具体参见ARM的多寄存器加载指令),所以在恢复寄存器时,必须使处理器处于某种异常模式。由于FIQ模式和IRQ模式对应中断,SP指针不能随意改变,而未定义模式和中止模式以后可能会用到,所以本移植选择为管理模式。
在执行(28)-(37)这部分程序时的寄存器和存储器之间的具体关系如图2.9~2.11所示,图中的标号为对应的程序段。
图2.9
OSIntCtxSw_1
;获取新任务堆栈指针, OSTCBHirtRdy->OSTCBStkPtr保存的是任务堆栈位置
LDR R4, [R6] ;加载R6指定地址上的数据,放在R4中 (28)
ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP(29)
LDR LR, [SP, #-8] ;将[SP-8]指定地址的数据加载到LR (30)
由于 OSTCBHirtRdy->OSTCBStkPtr保存的是任务堆栈位置,而寄存器恢复后堆栈指针并不指向这里,所以要调整新任务堆栈指针,这是由程序段(29)完成的。
图 2.10
然后通过执行程序段(31),利用MSR指令直接设置状态寄存器CPSR的模式位,使处理器进入管理模式,先恢复新任务OsEntersum,这是由程序段(34)(35)完成的,然后把新任务的 CPSR恢复到 SPSR这是由程序段(36)实现的。
图2.11
最后通过中断返回指令恢复R0—R12,把SPSR拷贝到CPSR(恢复用户任务的处理器模式和指令集)和执行用户任务(恢复PC指针),这是由程序段(37)实现的。这里需要注意的是程序段(29)和(32)中的SP是不同的处理器寄存器分别为R13和R13_SVC。
关于中断和时钟节拍,UCOS-II对于ARM7通用的中断服务程序的汇编与c函数接口如下:
MACRO和MEND伪指令用于宏定义。MACRO……MEND伪指令可以将一段代码定义为一个整体,称为宏定义。其中,$标号在宏指令被展开时,标号会被代换为用户定义的符号。MACRO标识宏定义的开始,MEND标识宏定义的结束。定义之后在程序中就可以通过宏指令多次调用该段代码。MACRO宏定义如下
MACRO
$标号 宏名 $参数1,$参数2,……
指令序列
MEND
IRQ异常处理代码的汇编部分
MACRO
$IRQ_Label HANDLER $IRQ_Exception_Function
;$IRQ_lable标号,用于构造宏定义体内的其他标号。HANDLER是宏的名称,$IRQ_Exception_function是宏的参数
EXPORT $IRQ_Label ; 输出的标号。用于在程序中声明一个全局的标号,
该标号可在其他文件中引用
IMPORT $IRQ_Exception_Function ; 引用的外部标号。用于通知编译器要使用的标号在其他原文件中定义,但要在当前文件中引用
$IRQ_Label
SUB LR, LR, #4 ; 计算返回地址
STMFD SP!, {R0-R3, R12, LR} ; 保存任务环境
MRS R3, SPSR ; 保存状态
STMFD SP, {R3, SP, LR}^ ;保存用户状态的R3,SP,LR,注意不能回写
;后缀”!”表示最后的地址写回到SP中。
;后缀”^”不允许在用户模式或系统模式下使用,若在LDM指令且寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,将SPSR也拷贝到CPSR中,这可用于异常处理返回。使用“^”后缀进行数据传送且寄存器列表不包含PC时,加载/存储的是用户模式的寄存器,而不是当前模式的寄存。
注意:这里用的是中断模式的堆栈,一开始就计算真正的返回地址,即将保存在R14_irq的返回地址减4,然后采用IRQ模式的R13_irq来保存中段的上下文,这里保存了R0-R3,R12。LR这6个寄存器。同时,又将用户模式下的CPSR(因为切换到中断模式,所以这里变成了SPSR)和R13(用户模式),R14(用户模式)保存到IRQ模式的堆栈中。注意,这里最后一条没有采用SP!的形式,所以SP是不回写的,保持不变。STMFD SP, {R3, SP, LR}^指的是。如果寄存器列表里面没有PC,那么采用^就表示将用户模式的寄存器的值去出来,所以(R3,SP,LR)指的是用户模式寄存器。
; OSIntNesting++
LDR R2, =OSIntNesting ;将OSIntNesting的地址保存到R2
LDRB R1, [R2] ;将R2指定地址的数据加载到R1
ADD R1, R1, #1
STRB R1, [R2]
SUB SP, SP, #4*3
这段代码将中断嵌套层数加1,并且将刚才没有调整的SP指针向下指3个,这时候SP又指向栈顶。
MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式
CMP R1, #1
LDREQ SP, =StackUsr
BL $IRQ_Exception_Function ; 调用c语言的中断处理程序
这段代码将运行模式切换到SYS32模式。比较一下R1,看上面的OSIntNesting是否为1。如果为1,则表示第一次进入系统模式,那么将系统指针设定好。
MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式
LDR R2, =OsEnterSum ; OsEnterSum,使OSIntExit退出时中断关闭
MOV R1, #1
STR R1, [R2]
BL OSIntExit
这段为中断回复代码。这里又切回系统模式。用OsEnterSum变量保证退出OSIntExit不会把中断打开。
LDR R2, =OsEnterSum; 因为中断服务程序要退出,所以OsEnterSum=0
MOV R1, #0
STR R1, [R2]
MSR CPSR_c, #(NoInt | IRQ32Mode) ; 切换回irq模式
LDMFD SP, {R3, SP, LR}^; 恢复用户状态的R3,SP,LR,注意不能回写
LDR R0, =OSTCBHighRdy
LDR R0, [R0]
LDR R1, =OSTCBCur
LDR R1, [R1]
CMP R0, R1
ADD SP, SP, #4*3 ;
MSR SPSR_cxsf, R3
LDMEQFD SP!, {R0-R3, R12, PC}^ ; 不进行任务切换
LDR PC, =OSIntCtxSw ; 进行任务切换
因为上面没有回写弹出的堆栈指针,所以将堆栈指针加3个寄存器内存。根据CMP指令的结果。当优先级没有改变,就直接把终端模式生下来的R0-R3,R12,PC和SPAR弹出,中断结束,用户态程序继续执行。如果优先级发生改变,则进行任务切换。注意,如果寄存器列表里面有PC寄存器,那么在带”^”的情况下,自动将SPSR拷贝到CPSR。
MEND
END