这里以STM32处理器为例,也就是Cortex-M3内核。
所谓的上下文切换呢,就是当 uC/OS转向执行另一个任务的时候,它保存了当前任务的CPU 寄存器到堆栈。并从新任务的的堆栈中 CPU 寄存器载入 CPU。
在这里上下文切换分为两种:一个是任务级的,一个是中断级的。
(1) 开始执行切换,保存状态寄存器和程序指针寄存器到当前的任务堆栈。保存的顺序与中断发生时 CPU 保存寄存器的顺序相同。假定 SR 先入栈,然后其它寄存器入栈。
(2) 当任务被停止时,保存 CPU 的 TSP 到该任务的OS_TCB 中,换句话说,OSTCBCurPtr->StkPtr=R14。
(3) 然后将新任务 OS_TCB 的顶部地址存入 CPU 的TSP 中。换句话说,R14=OSTCBCurPtr->StkPtr。
(4) 最后, 将新任务堆栈中 R13~~R0 关内容载入 CPU寄存器。再然后(此时的会 TSP 指向 PC,如图),通过一个中断返回指令{比如汇编中的 IRET},程序指针寄存器和状态寄存器被恢复到 CPU 的寄存器中。
以上是一个大概的流程分析,下面来看看其具体的代码实现过程,当然,这里是用汇编来实现。
PendSV_Handler
CPSID I ; Prevent interruption during context switch关所以中断
MRS R0, PSP ; PSP is process stack pointer PSP是当前进程堆栈的指针,将PSP赋值给R0
CBZ R0, PendSV_Handler_Nosave ; Skip register save the first time如果R0为0时跳转到PendSV_Handler_Nosave
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack偏移0x20的位置用来保存R4至R11
STM R0, {R4-R11} ;将剩下的R4至R11寄存器保存在此进程的堆栈中
LDR R1, =p_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP; 即OSTCBCur->OSTCBStkPtr这个保存当前的栈尾,以便下次弹出
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved此时,整个上下文的过程已经被保存
PendSV_Handler_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, =p_OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =p_OSTCBHighRdy ;TCB表也一样
LDR R2, [R1]
STR R2, [R0]
;到这里,[R2]保存的是新的进程的堆栈指针SP
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr; 将堆栈指针赋值给SP
LDM R0, {R4-R11} ; Restore r4-11 from new process stack 弹出其它寄存器,和前面的是一个逆过程
ADDS R0, R0, #0x20 ;和前面的逆过程对比可知
MSR PSP, R0 ; Load PSP with new process SP将R0中的SP赋值给PSP寄存器
ORR LR, LR, #0x04 ; Ensure exception return uses process stack确保异常返回时使用进程堆栈
CPSIE I ;开中断
BX LR ; Exception return will restore remaining context异常返回将恢复那些自动出栈的剩余寄存器
晕,排版有点乱,勉强凑合着看吧。。。
可以看出这其实是一个PendSV中断来的,也就是M3的可编程挂起中断,为什么用这个中断来进行上下文切换呢?后面将会进行说明下。
CBZ R0, PendSV_Handler_Nosave
这句是用来判断是否第一次进入切换,也就是系统刚启动的时候,则直接跳到PendSV_Handler_Nosave这个标号来执行了,因为系统刚启动还没有当前任务嘛。
当不是刚启动的,进行上下文切换,首先是要将当前任务的寄存器保存下来,保存到当前任务的堆栈里,R4-R11手动保存到堆栈,其余会自动保存。
然后
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy ;将当前优先级变量指向最高优先级
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =p_OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =p_OSTCBHighRdy ;TCB表也一样
LDR R2, [R1]
STR R2, [R0]
这里是将优先级最高的,也就是需要切换到的那个任务替换为当前任务。
然后让PSP指向任务堆栈,再然后是一个逆过程,弹出堆栈里的数据到对应的寄存器。
上下文切换就差不多这样。。。
关于在M3内核,为什么使用PendSV这个中断来进行上下文切换呢?其原因是这个中断拥有最低优先级的软件中断。
又问,为什么要在拥有最低优先级的软件中断中进行切换呢?这个答案在《Cortex-M3权威指南》第7章 异常 里有详细的答案。