3.6 任务级任务切换
UCOS的用户调用一些系统服务时(比如,OSTimeDly、OSSemPend),就会产生任务级任务切换。其切换的实质是保存正在执行任务的执行现场,然后恢复应该运行的任务的运行现场。
本工程中使用软中断的方式实现任务级任务切换的目的。
任务级切换函数的底层接口是使用的软中断技术,用__swi来声明一个不存在的函数,则调用这个函数就在调用这个函数的地方插入一条SWI指令,并且可以指定功能号。定义如下:__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */
调用OS_TASK_SW()这个函数时就会产生一个软中断,产生软中断后执行软中断服务程序。服务程序主要代码分析如下:
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针
STMFD SP!, {R0-R3, R12, LR}
MOV R1, SP ; R1指向参数存储位置
MRS R3, SPSR
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}^
代码难点分析:
软中断指令使处理器进入管理模式,而用户程序处于系统/用户模式,其它异常也有自己的处理器模式,都有各自的堆栈指针,不会因为给堆栈指针赋值而破坏其它处理器模式的堆栈而影响其它程序的执行。返回的地址已经存储在连接寄存器LR中而不是存储在堆栈中。由于进人管理模式自动关中断,所以这段程序不会被其它程序同时调用。
因为ARM处理器核具有两个指令集,在执行Thumb指令的状态时不是所有寄存器都可见(参考ARM的相关资料),而且任务又可能不在特权模式(不能改变CPSR)。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0实现OS_TASK_SW()的功能。
因任务级任务切换使用的是软中断技术,我们把osctxsw()与osintctxsw()合二为一了,统一采用osintctxsw()来实现。之所以这样搞的原因是任务进行切换的时候,都必须进入软中断的状态,而对于软中断的异常响应代码已经将任务的环境变量进行了保存,从而也不需要像osctxsw()里面规定的那样对将环境变量进行保存。osintctxsw()函数的移植分析见3.7中断级任务切换。
3.7 中断级任务切换
当系统任务延时时间到或者在中断服务程序里抛出信号量、邮箱等可以产生系统调度的操作时,会执行任务切换,但这种切换是在中断模式下进行的。但底层切换函数是一致的,只不过任务级任务切换时是在SVC模式下进行,中断级任务切换是在中断模式下进行。
下面我们分析中断级任务切换的主要流程和代码:
SUB LR, LR, #4 ; 计算返回地址
STMFD SP!, {R0-R3, R12, LR} ; 保存任务环境
MRS R3, SPSR ; 保存状态
STMFD SP, {R3,SP, LR}^; 保存用户状态的R3,SP,LR,注意不能回写
; 如果回写的是用户的SP,所以后面要调整SP
LDR R2, =OSIntNesting ; OSIntNesting++
LDRB R1, [R2]
ADD R1, R1, #1
STRB R1, [R2]
SUB SP, SP, #4*3
MSR CPSR_c, #(NoInt :OR: SYS32Mode) ; 切换到系统模式
CMP R1, #1
LDREQ SP, =StackUsr
BL $IRQ_Exception_Function ; 调用c语言的中断处理程序
MSR CPSR_c, #(NoInt :OR: SYS32Mode) ; 切换到系统模式
LDR R2, =OsEnterSum; OsEnterSum,使OSIntExit退出时中断关闭
MOV R1, #1
STR R1, [R2]
BL OSIntExit
LDR R2, =OsEnterSum; 因为中断服务程序要退出,
;所以OsEnterSum=0
MOV R1, #0
STR R1, [R2]
MSR CPSR_c, #(NoInt :OR: IRQ32Mode) ; 切换回irq模式
LDMFD SP, {R3,SP, LR }^ ; 恢复用户状态的R3,SP,LR,
;注意不能回写
; 如果回写的是用户的SP,所以后面要调整SP
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 ; 进行任务切换
代码主要功能分析:
实现在中断模式下保存系统模式下正在运行任务的各个寄存器到中断模式堆栈,然后执行相应的中断服务程序,中断退出时做任务切换。
下面我们分析实现任务切换的函数OSIntCtxSw。
OSIntCtxSw
;下面为保存任务环境
LDR R2, [SP, #20] ;获取PC
LDR R12, [SP, #16] ;获取R12
MRS R0, CPSR
MSR CPSR_c, #(NoInt :OR: SYS32Mode)
MOV R1, LR
STMFD SP!, {R1-R2} ;保存LR,PC
STMFD SP!, {R4-R12} ;保存R4-R12
MSR CPSR_c, R0
LDMFD SP!, {R4-R7} ;获取R0-R3
ADD SP, SP, #8 ;出栈R12,PC
MSR CPSR_c, #(NoInt :OR: SYS32Mode)
STMFD SP!, {R4-R7} ;保存R0-R3
LDR R1, =OsEnterSum ;获取OsEnterSum
LDR R2, [R1]
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum
;保存当前任务堆栈指针到当前任务的TCB
LDR R1, =OSTCBCur
LDR R1, [R1]
STR SP, [R1]
BL OSTaskSwHook ;调用钩子函数
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4]
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
上述函数实现了保存上一个被中断任务运行时各个寄存器到任务的堆栈空间里,然后将系统中优先级最高且就绪的任务堆栈里保存的各个寄存器内容恢复到系统模式的各个寄存器中,使任务正常运行。 |