uC/OSIII在任务中执行OSSched相关的函数和在中断退出后都会开始执行调度,这是它的调度机制。而按uC/OSIII书中所讲,普通任务切换和从中断中退出后的任务切换应该是不同的函数,因为普通任务切换时要入栈出栈全部寄存器,而中断进入和退出时处理器会自动入栈出栈一部分寄存器(Cortex-M3 是自动保存xPSR, PC, LR, R12, R0-R3 )。
但是uC/OSIII在Cortex-M3平台中,这两种任务切换函数却是使用的同一函数,确切的说是使用了同一段代码,如下:
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
(这段代码的作用是写NVIC寄存器,调用PendSV中断。)
为什么和书上说的不一致呢?两种完全不同的场景居然能使用同一个处理过程?
机关就在调用PendSV中断这里。uC/OSIII在Cortex-M3平台下使用PendSV中断进行任务的调度切换。而Cortex-M3的中断进入前会自动保存8个寄存器 :xPSR, PC, LR, R12, R0-R3。所以即使是任务中执行的切换,该任务也不需要做寄存器的入栈操作,直接调用PendSV中断就好了。而在中断退出中执行的切换因为本来就在中断中,更不需要再做入栈操作了。
也就是说,无论是从任务中调用的任务切换还是从中断退出中调用的任务切换,都是调用的PendSV中断。这一点很重要,因此只要是任务切换,结果就是:在进入PendSV处理函数后,肯定有8个寄存器已经保存到原 thread的PSP中了(注意这个thread,uC/OSIII普通任务都是thread模式的,使用PSP。中断进程叫handler模式,使用MSP。这也是Cortex-M3架构决定的)。
那从任务中调用任务切换和从中断退出中调用的任务切换就没有一点区别吗?还是有一点的,从任务中调用任务切换是直接进入的PendSV中断;从中断退出中调用的任务切换是执行的咬尾中断,先退出原中断,然后进入PendSV中断,具体区别就是:从任务中进入PendSV处理函数,那8个寄存器是PendSV中断自己保存的;从中断退出后进入PendSV处理函数那8个寄存器是原中断保存的,不是PendSV保存的。但对PendSV处理函数而言,两者并无区别,反正给我保存了就行了,不管到底是我自己保存的还是你替我保存的。
所以uC/OSIII在PendSV处理函数中的过程就是:一进来就保存剩下的8个寄存器:R4-R11。当然,要保存到PSP下面,这是原任务使用的堆栈,保存后就保证了原任务自己堆栈内容的完整,注意千万别弄错了保存到了MSP下面,MSP是中断函数使用的。
然后就进行任务优先级查找,出栈全部寄存器,返回thread模式,执行新任务。
整个PendSv处理函数用汇编语言写成,在《os_cpu_a.asm》文件中:
OS_CPU_PendSVHandler
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer
STMFD R0!, {R4-R11} ; Save remaining regs r4-11 on process stack
MOV32 R5, OSTCBCurPtr ; OSTCBCurPtr->OSTCBStkPtr = SP;
LDR R6, [R5]
STR R0, [R6] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved
MOV R4, LR ; Save LR exc_return value
BL OSTaskSwHook ; OSTaskSwHook();
MOV32 R0, OSPrioCur ; OSPrioCur = OSPrioHighRdy;
MOV32 R1, OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
MOV32 R1, OSTCBHighRdyPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R2, [R1]
STR R2, [R5]
ORR LR, R4, #0xF4 ; Ensure exception return uses process stack
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
LDMFD R0!, {R4-R11} ; Restore r4-11 from new process stack
MSR PSP, R0 ; Load PSP with new process SP
CPSIE I
BX LR ; Exception return will restore remaining context
可以看到,它明显分为了前后两部分,它的切换过程也很简单,就是先把原来任务的R4-R11保存好,然后找到任务就绪表中的最高已就绪TCB的指针OSTCBHighRdyPtr,读取它的SP和入栈的寄存器,执行出栈,任务切换。
而值得说明的是,这个OSTCBHighRdyPtr是在调用PendSv进行切换之前准备好的。在任务中切换和在中断退出中切换所做的动作不同,但准备步骤相似。在任务中切换,最终要调用函数OSSched,它在《os_core.c》中,有如下语句:
OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority ready */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
……
OS_TASK_SW(); /* Perform a task level context switch */
这里最后一句OS_TASK_SW函数就是开始调用PendSv进行切换.
在中断退出中切换,是调用《os_core.c》中的函数OSIntExit,它也有以下语句:
OSPrioHighRdy = OS_PrioGetHighest(); /* Find highest priority */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* Get highest priority task ready-to-run */
……
OSIntCtxSw();
最后一句OSIntCtxSw也是开始调用PendSv进行切换。而且,其实它与任务切换的OS_TASK_SW函数最终是完全相同的汇编代码,就是文章一开始提到的那一段,在《os_cpu_a.asm》中:
OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
附PendSV处理函数的注释:
Note(s) : 1) PendSV is used to cause a context switch. This is a recommended method for performing context switches with Cortex-M3. This is because the Cortex-M3 auto-saves half of the processor context on any exception, and restores same on return from exception. So only saving of R4-R11 is required and fixing up the stack pointers. Using the PendSV exception this way means that context saving and restoring is identical whether it is initiated from a thread or occurs due to an interrupt or exception.