UCOS在LPC上的移植
一、一、OS_CPU_A.ASM文件的编写
1、 1、 SoftwareInterrupt的编写
当发生软件中断时,程序通过异常向量表跳转到软中断的汇编与C接口程序SoftwareInterrupt处,下图为SoftwareInterrupt的流程图。
程序:
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针,堆栈指向的是SVC模式下的堆栈
STMFD SP!,{R0-R3,R12,LR}
STMFD SP!, {R0-R3, R12, LR} ;此时R0-R3, R12,在svc和用户模式下都是同一物理地址,lr为svc下的寄存器,保存着中断返回地址
MOV R1, SP ; R1指向参数存储位置(svc下的堆栈指针地址)
这个不知道用在哪??
MRS R3, SPSR ;R3中放SWI前的CPSR(即被中断的任务的cpsr),在切换函数中用到
TST R3, #T_bit ; 中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号
BICEQ R0, R0, #0xFF000000
; r0 = SWI号,R1指向参数存储位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换
BL SWI_Exception
LDMFD SP!,{R0-R3,R12,PC}^;
LDMFD SP!, {R0-R3, R12, PC}^ R12处的上一堆栈指针(&R12+4)存放的为LR,所以就把LR装入PC
分析:因为执行任务切换时(执行新任务)堆栈指针会指向用户的堆栈,这样下一次进入管理模式(在osintctxsw中就会用到管理模式)就会破坏用户堆栈,从而导致程序执行不正确。所以程序在一开始设置堆栈指针。软中断指令使处理器进入管理模式,而用户程序处于系统/用户模式,其它异常也有自己的处理器模式,都有各自的堆栈指针,不会因为给堆栈指针赋值而破坏其它处理器模式的堆栈而影响其它程序的执行。返回的地址已经存储在连接寄存器LR中而不是存储在堆栈中。由于进人管理模式自动关中断,所以这段程序不会被其它程序同时调用,设置的堆栈指针指向的位置肯定是空闲位置,后一次调用不会影响前一次调用。这样就可以保证“LDR SP, StackSvc”进行正确的堆栈指针设置。
因为ARM处理器核具有两个指令集,在执行Thumb指令的状态时不是所有寄存器都可见(参考ARM的相关资料),而且任务又可能不在特权模式(不能改变CPSR)。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0实现OS_TASK_SW()的功能,
附: (1)LR没有入栈:
从SWI和Undef异常返回时使用:
movs pc, LR;
从FIQ、IRQ和预取终止返回时使用:
SUBS PC, LR,#4;
从数据异常返回时使用:
SUBS PC, LR,#8
(2)LR有入栈:
在使用上述指令异常返回时,如果LR之前被压栈的话使用LDM “∧”,达到同样效果。 例如:
LDMFD SP!, {PC}∧
2 、OSIntCtxSw的编写:
在μC/OS-Ⅱ中,任务切换只是简单的将处理器寄存器保存到将被挂起的任务的堆栈中,并且将更高优先级的任务从堆栈中恢复出来。处于就绪状态的任务的堆栈结构看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。换句话说,μC/OS-Ⅱ要运行处于就绪状态的任务必须要做的事就是将所有处理器寄存器从任务堆栈中恢复出来,并且执行中断的返回。
在μC/OS-Ⅱ中,用户级任务调度时会调用宏(或者函数)OS_TASK_SW(),它是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,μC/OS-Ⅱ建议OS_TASK_SW()通过某种途径最终调用函数OSCtxSw()。函数OSCtxSw()是与系统相关的,μC/OS-Ⅱ提供的OSCtxSw()函数原型如下:
OSCtxSw()原型的程序清单
void OSCtxSw(void)
{
保存处理器寄存器;
将当前任务的堆栈指针保存到当前任务的OS_TCB中;
OSTCBCur->OSTCBStkPtr = Stack pointer;
调用用户定义的OSTaskSwHook();
OSTCBCur = OSTCBHighRdy;
OSPrioCur = OSPrioHighRdy;
得到需要恢复的任务的堆栈指针;
Stack pointer = OSTCBHighRdy->OSTCBStkPtr;
将所有处理器寄存器从新任务的堆栈中恢复出来;
执行中断返回指令;
}
下面将各部分分解来分析:
在进入OSIntCtxSw 前,当前处理器堆栈模式(SVC)为:SP指向R0
(1) (1)保存处理器寄存器:
OSIntCtxSw ;下面为保存任务环境
LDR R2, [SP, #20] LDR R2, [SP, #20] ;
;获取PC ,此时SP指向的是SVC的任务堆栈;SP+20=LR,即把中断返回地址给PC, 这里的PC是当前任务的PC , 跟下面需要恢复的新任务的PC(程序37)是不一样的 ……………………………………………………………………………………………(1)
获取PC ,此时SP指向的是SVC
钱
LDR R12, [SP, #16] ;获取R12 …………………………………………(2)
MRS R0, CPSR ;用来存储模式切换 ………………………… (3)
MSR CPSR_c, #(NoInt | SYS32Mode) ;系统模式…………………………………(4)
***注***自己的理解:
1. Ucos的任务运行在用户模式或是系统模式下,也就意味着所有任务的堆栈位于为用户模式开辟的堆栈区中(系统模式和用户模式拥有相同的堆栈区)。
2. 当进行任务切换时,ARM都会自动进入相应的处理器模式,如任务级调度进入管理模式,中断级调度进入IRQ模式,而每个处理器模式拥有不同的堆栈区。
3. 任务切换需要保存被中断中止任务的上下文,将寄存器内容压入自己的任务堆栈,而任务堆栈位于用户模式堆栈区,所以必须先将ARM处理器模式由管理模式或是IRQ模式切换到系统模式,才能正确完成上下文切换。
MOV R1,LR ……………………………………………………… (5)
STMFD SP!,{R1,R2};
STMFD SP!, {R1-R2} ; 保存LR,PC到任务堆栈下;ARM 7中模式R0-R12公用,
所以转到SYS,R1,R2还是不变 …………………………(6)
STMFD SP!, {R4-R12} ;保存R4-R12到任务堆栈下 …………………(7)
MSR CPSR_c, R0 ; ……………………………………………… (8)
LDMFD SP!, {R4-R7} ;获取R0-R3,在SVC的堆栈中R3存放着SPSR值 ………… (9)
ADD SP, SP, #8 ;
ADD SP, SP, #8 ; 出栈R12,PC,在堆栈中剩余的数据(R12、PC)已经没有用处,所以
要进行调整;清理软中断堆栈,复原到任务切换前的样子…… (10)
MSR CPSR_c, #(NoInt | SYS32Mode) ;系统模式……………………………(11)
STMFD SP!, {R4-R7} ;把获取的R0-R3参数保存到任务堆栈中………(12)
LDR R1, =OsEnterSum ;获取OsEnterSum单元地址(CPU寄存器下的地址)… (13)
LDR R2, [R1] ;…………………………………… (14)
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum 到任务堆栈 ………………………(15)
至此,处理器寄存器已保存完毕。
(2)将当前任务的堆栈指针保存到当前任务的OS_TCB中;
OSTCBCur->OSTCBStkPtr = Stack pointer
OSTCBCur:当前任务的OS_TCB任务块指针
.OSTCBStkPtr:指向当前任务堆栈栈顶的指针
栈顶:堆栈中刚存入有效数据的那一个单元地址
代码如下:
;保存当前任务堆栈指针到当前任务的TCB
LDR R1, =OSTCBCur ;取 & OSTCBCur 单元地址 …………………(16)
LDR R1, [R1] LDR R1, [R1] ;
STR SP, [R1] 取 OSTCBCur指向的内容,OS_TCB本身就是一个数据结构 ,因此OSTCBCu指向的内容即为该任务块的 第一个元素OSTCBStkPtr …………………………………………………………………………………(17)
STR SP, [R1] ;OSTCBCur->OSTCBStkPtr = Stack pointer…………………………(18)
(3)调用用户定义的OSTaskSwHook():
(3)调用用户定义的OSTaskSwHook():这样可直接访问OSTCBCur和OSTCBHighRdy这2个全局变量。OSTCBCur指向将被切换出去的任务控制块;而OSTCBHighRdy指向新任务的任务控制块
OSTCBCur = OSTCBHighRdy;任务控制块更新
OSPrioCur = OSPrioHighRdy; 任务优先级更新
代码如下:
BL OSTaskSwHook ;调用子函数 …………………………………………(19)
LDR R4, =OSPrioCur ; ………………………………………(20)
LDR R5, =OSPrioHighRdy ; 优先级更新 ………………… (21)
LDRB R6, [R5] ;……………………………………………………………… (22)
STRB R6, [R4] ;……………………………………………………………… (23)
任务控制块更新:
LDR R6, =OSTCBHighRdy ;取& =OSTCBHighRdy单元地址…………………………………… (24)
OSTCBHighRdy 指向的是新任务控制块的第一个元素
LDR R6, [R6] ;取OSTCBHighRd的值 ………… (25)
LDR R4, =OSTCBCur ; …………………………………………(26)
STR R6, [R4] ;把OSTCBHighRdy指向的内容复制给 OSTCBCur …………(27)
此时,任务块已更新,OSTCBHighRdy已复制到OSTCBCur,即OSTCBCur已指向新任务控制块的第一个元素(栈顶指针)。
(4)得到需要恢复的任务的堆栈指针;
Stack pointer = OSTCBHighRdy->OSTCBStkPtr;
将所有处理器寄存器从新任务的堆栈中恢复出来;
执行中断返回指令;
代码分析:从标号OSIntCtxSw_1处开始至程序的最后,是将所有处理器寄存器从新任务的堆栈中恢复出来。处理器执行这段代码时处于ARM状态,但用户任务可能处于Thumb状态。而由ARM的相关资料可知,程序不可以直接改变程序状态寄存器的CPSR的T位来改变处理器的状态。但“LDMFD SP!, {R0-R12, LR, PC }^ ” 异常返回指令是可以使用的(具体参见ARM的多寄存器加载指令),所以在恢复寄存器时,必须使处理器处于某种异常模式。由于FIQ模式和IRQ模式对应中断,SP指针不能随意改变,而未定义模式和中止模式以后可能会用到,所以本移植选择为管理模式。
OSIntCtxSw_1
;获取新任务堆栈指针,[R6] 的值为指针:指向的是新任务的栈顶指针,而新任务的栈顶指针指向哪?因为在堆栈初始化后(OSTaskStkInit()函数),堆栈指针已经指向任务堆栈的最低地址单元(OsEnterSum)
LDR R4, [R6] ;[R6] :新任务的栈顶指针;指针指向的内容为(& OsEnterSum )…(28)
ADD SP, R4, #68 ADD SP, R4, #68; 堆栈指向(PC+4 )处,这就是新任务CPU寄存器的SP值(R13),为入其他数据做堆栈指针调整 …………………………………………(29)
LDR LR, [SP, #-8] ;把任务堆栈的LR给CPU寄存器R14 ……………………(30)
MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式 ……………………………………(31)
MOV SP, R4 ;设置堆栈指针,SP为R13_SVC下的,SP指向(& OsEnterSum )……(32)
LDMFD SP!, {R4, R5} ;取任务下的CPSR,OsEnterSum 到CPU寄存器R5,R4……… (33)
LDR R3, =OsEnterSum ;取CPU寄存器下的地址单元 ………………………………(34)
STR R4, [R3] ;恢复新任务的OsEnterSum……………………………… (35)
MSR SPSR_cxsf, R5 ;恢复CPSR ,异常下的SPSR返回后给新任务的CPSR…………(36)
LDMFD SP!, {R0-R12, LR, PC LDMFD SP!, {R0-R12, LR, PC }^ ;异常返回,这里的PC在堆栈初始化时已经设置好了,运行新 任 务…………………………………………………………………………………………………………………(37)