其实,是采用异常中断的方式,运行中断服务函数,然后再中断服务函数完成任务之间的转换。
从上图中可以看出,在使用异常中断来进行任务转换时,首先先要保存现场,即:将原本在执行的任务进行压栈保存,用户按照下面的顺序进行压栈保存,之后各寄存器将会按照上图(1.)的顺序被找到正确的位置。
然后接下来就是在中断服务程序中究竟如何完成任务转换?
在完成保存原本正在执行的任务之后,那么接下来要实现的效果当然就是在中断服务函数结束时自动跳转到另一个任务去执行。这里我们可以自己猜想一下,肯定是有某一个寄存器中存放着一个指针,使得程序在执行完中断之后,到这个寄存器去看一下接下来要去哪里执行,就算我们原本只有一个任务(经典的main程序),在中断触发时,去执行中断服务函数,执行完之后接着在main程序中继续执行接下来的指令,那么系统怎么知道中断前执行到哪个位置,其实,就是在运行中断服务函数之前我们会保存现场,其中就把后续程序的地址保存在某个寄存器中。
按照上面的推理,我们可以推断出“某个寄存器”中存放的地址决定了中断结束后该去哪里运行程序。既然如此,那么我们是不是就可以擅自修改“某个寄存器”的值,让他不是指向中断前的位置,而是指向另一个任务的位置呢?
其实上面说到的“某个寄存器” 就是指这里的堆栈指针R13这个特殊寄存器,然后其实在只有单任务时,使用的是主堆栈指针(MSP),这个就不能随意更改,其实意思就是不适用于多任务跳转,那么进程堆栈指针(PSP)就是可以拿来更改的寄存器。
系统怎么判断此时正在使用的是哪一个(MSP还是PSP)呢?
从上图可以看出,系统是根据控制寄存器(CONTROL)来判断使用的是哪一个寄存器,也可以通过修改LR的位2来实现,而这里我们可以发现LR其实就是保存现场时需要压栈保存的八个寄存器中的LR。
所以在中断中实现任务跳转的代码如下:
;********************************************************************************************************
; PendSVHandler异常
;********************************************************************************************************
PendSV_Handler
; 任务的保存,即把CPU寄存器的值存储到任务的堆栈中
CPSID I ; 关中断,NMI和HardFault除外,防止上下文切换被中断
MRS R0, PSP ; 将psp的值加载到R0
CBZ R0, OS_CPU_PendSVHandler_nosave ; 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave
; 进行第一次任务切换的时候,R0肯定为0
; 在进入PendSV异常的时候,当前CPU的xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前任务堆栈,同时递减PSP的值
STMDB R0!, {R4-R11} ; 手动存储CPU寄存器R4-R11的值到当前任务的堆栈
LDR R1, = OSTCBCurPtr ; 加载 OSTCBCurPtr 指针的地址到R1,这里LDR属于伪指令
LDR R1, [R1] ; 加载 OSTCBCurPtr 指针到R1,这里LDR属于ARM指令
STR R0, [R1] ; 存储R0的值到 OSTCBCurPtr->OSTCBStkPtr,这个时候R0存的是任务空闲栈的栈顶
; 任务的切换,即把下一个要运行的任务的堆栈内容加载到CPU寄存器中
OS_CPU_PendSVHandler_nosave
; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R0, = OSTCBCurPtr ; 加载 OSTCBCurPtr 指针的地址到R0,这里LDR属于伪指令
LDR R1, = OSTCBHighRdyPtr ; 加载 OSTCBHighRdyPtr 指针的地址到R1,这里LDR属于伪指令
LDR R2, [R1] ; 加载 OSTCBHighRdyPtr 指针到R2,这里LDR属于ARM指令
STR R2, [R0] ; 存储 OSTCBHighRdyPtr 到 OSTCBCurPtr
LDR R0, [R2] ; 加载 OSTCBHighRdyPtr 到 R0
LDMIA R0!, {R4-R11} ; 加载需要手动保存的信息到CPU寄存器R4-R11
MSR PSP, R0 ; 更新PSP的值,这个时候PSP指向下一个要执行的任务的堆栈的栈底(这个栈底已经加上刚刚手动加载到CPU寄存器R4-R11的偏移)
ORR LR, LR, #0x04 ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
CPSIE I ; 开中断
BX LR ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在STM32中,堆栈是由高地址向低地址生长的。
NOP ; 为了汇编指令对齐,不然会有警告
END ; 汇编文件结束