uC/OSIII在Cortex-M3的任务切换和中断退出分析

    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.





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值