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。