根据STM32F103上uCOS-II代码中OS_CPU_PendSVHandler的函数代码整理。
uCOS-II是非剥夺式的操作系统,任务切换只发生在以下几种情况:
1> 外部中断引起变化,导致高优先级任务就绪。
例如串口中断收到数据,在中断例程中接收数据后调用OSSemPost(),互斥量的Post使uCOS-II知道相应的串口接收任务转变到就绪状态。
2> SysTick的定时中断,导致之前处于OSTimeDly挂起的高优先级任务就绪。
3> 当前任务执行如OSSemPend、OSTimeDly等操作导致当前任务挂起,需要切换到其他次优先级的任务。
前两种都是在中断退出后引起任务切换。
外部中断退出时调用OSIntExit函数激活PendSV中断,真正的task切换在PendSV中断例程中实现。
SysTick中断优先级较高,也不直接做任务切换,其同样在中断退出时调用OSIntExit函数激活PendSV中断,真正的task切换在PendSV中断例程中实现。
第3种是在需要任务切换时调用OS_Sched,后者会调用OSCtxSw,而OSCtxSw也是激活PendSV中断,再由PendSV中断例程完成真正的任务切换。
所以所有的任务切换最终都是由PendSV中断例程完成的。
PendSV的中断优先级必须是系统所有中断中最低的。
PendSV中断由OS_CPU_PendSVHandler函数处理。
OS_CPU_PendSVHandler函数的代码:
OS_CPU_PendSVHandler
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer
CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
假设系统中有两个任务,Task1和Task2,Task1是当前正在运行的任务(由OSTCBCur指出),Task2处于挂起状态。
那么进入OS_CPU_PendSVHandler中断前,堆栈状态如下图所示。
CPU处于线程状态,使用PSP堆栈工作,PSP指向Task1的堆栈。
CPU中的各寄存器是Task1当前任务的寄存器值。
Task2处于挂起状态,Task2的堆栈指针由TCB2的SP变量保存着。在Task2的堆栈底部,保存有两部分数据,一部分是CPU中断时自动保存到堆栈的寄存器变量(包括xPSR,PC,LR,R12,R0~R3),另一部分是uCOS额外保存的寄存器变量(R4~R11),这些寄存器保存了Task2挂起前的所有数据。
当条件变化导致Task1需要切换到Task2时(OSTCBHighRdy会指向Task2的TCB2),PendSV中断被激发。
进入OS_CPU_PendSVHandler中断时,根据Cortex-M3的中断流程,一部分动作由CPU自动执行:
进入OS_CPU_PendSVHandler中断时,堆栈状态如下图所示:
进入OS_CPU_PendSVHandler后,由于CPU只自动保存了部分寄存器值,uCOS需要将其余寄存器也保存下来,以便切回任务时能完整恢复现场。
OS_CPU_PendSVHandler会根据PSP的值得到Task1的堆栈底部,然后将额外的寄存器R4~R11保存到Task1的堆栈底部。
并且将更新后的Task1的堆栈值保存到TCB1的SP变量中。
OS_CPU_PendSVHandler保存完当前任务数据后的堆栈状态如下图所示:
之后OS_CPU_PendSVHandler需要恢复Task2任务的现场数据。
OS_CPU_PendSVHandler从OSTCBHighRdy获取需要切换到的任务块(此时其等于TCB2),然后从TCB2的SP变量获取该任务的堆栈指针。
此时CPU的R4~R11寄存器已恢复为Task2挂起前的值,但R0~R3、R12、LR、PC、xPSR这些尚未恢复,后面这些寄存器将在中断返回时由CPU自动恢复。
最后OS_CPU_PendSVHandler调用BX LR执行中断返回(LR中的值是EXC_RETURN值,以通知CPU做中断返回动作)。
OS_CPU_PendSVHandler在中断返回前的堆栈状态如下: