UCOS-III学习笔记——OS任务切换深入理解

μC/ OS III Version:v3.03.01

μC/ CPU Version:v1.29.01

μC/ LIB Version:v1.37.00

FOR:NXP LPC1768 CPU

UCOS-III学习笔记——LPC1768移植

UCOS-III学习笔记——主函数中的配置与初始化

UCOS-III学习笔记——主函数中的配置与初始化一文中我们了解了在main()函数中的ucos系统的初始化过程,在main()函数的末尾通过OSStart()函数中的OSStartHighRdy()函数,将CPU的控制权交给操作系统,启动多任务处理。

这一过程主要通过控制PendSV的中断,保存上文恢复下文,以实现任务的转换,大多使用的是底层的汇编语言,所以跟内核关系密切,这里我们使用的是cotex-M3内核进行实验,适用于STM32,LPC1768等微控制器。下面我们将分析代码深入理解这一过程的具体步骤和原理,以下函数均在os_cpu_a.s文件内。

1. OSStartHighRdy()

OSStartHighRdy
    LDR     R0, =NVIC_SYSPRI14                                  ; 设置PendSV中断的优先级
    LDR     R1, =NVIC_PENDSV_PRI
    STRB    R1, [R0]

    MOVS    R0, #0                                              ; 将PSP指针设置为0
    MSR     PSP, R0

    LDR     R0, =OS_CPU_ExceptStkBase                           ; 初始化MSP指针
    LDR     R1, [R0]
    MSR     MSP, R1    

    LDR     R0, =NVIC_INT_CTRL                                  ; 触发PendSV中断
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    
    CPSIE   I                                                   ; 开中断

OSStartHang
    B       OSStartHang                                         ; 程序永远不会运行到这里
  • 首先,配置PendSV的优先级为0xff的最低。PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3权威指南》里有介绍。

  • 然后,将PSP指针设置为0(为后面的内容做准备),并将MSP赋值。其中,PSP为任务堆栈指针,MSP为主堆栈指针。在任务切换过程中主要使用的是PSP。
  • 然后触发PendSV的中断,但由于在主函数的初始化过程中关闭了所有中断,所以此时还需要手动打开所有中断。

2. OS_CPU_PendSVHandler()

OS_CPU_PendSVHandler
    CPSID   I                                                   ; 关中断,防止切换过程中发生中断
    MRS     R0, PSP                                             ; 赋予r0为PSP的值
    CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; 如果为0则跳转(第一次必跳转)

    SUBS    R0, R0, #0x20                                       ; 保存 r4-11 寄存器
    STM     R0, {R4-R11}

    LDR     R1, =OSTCBCurPtr                                    ; OSTCBCurPtr->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                                            ; r0是当前任务的堆栈指针
  • 首先,为了避免切换任务被打断,需要关闭总中断。
  • 然后,读取PSP的值,第一次进入中断时,由于之前已经给PSP赋值为0,所以一定会直接跳转到下面的OS_CPU_PendSVHandler_nosave()函数开始执行。
  • 若不是第一次进入中断,则需要做保存上文的工作。则在进入中断时,CPU将自动把当前任务的寄存器xPSR, PC, LR, R12, R0-R3存到当前任务堆栈中,PSP指向R0;剩下的R4-R11寄存器则需要手动保存到任务堆栈中,STM指令即为入栈的意思,PSP指针不变,改变的是R0寄存器,PSP依旧指向的是自动保存后的地址。
  • 寄存器进栈完毕后,读取OSTCBCurPtr(当前任务TCB指针,即堆栈指针OSTCBStkPtr)的值,将其赋予R0,因为OS_TCB结构体第一个成员为任务的堆栈指针OSTCBStkPtr,OSTCBStkPtr的地址与任务TCB地址相同。
  • 每个OS_TCB的OSTCBStkPtr的值在创建任务时被赋值,指向的是每个任务堆栈的R4的位置。所以当前OSTCBCurPtr的值为图中StkPtr所示。

3.  OS_CPU_PendSVHandler_nosave()

OS_CPU_PendSVHandler_nosave
    PUSH    {R14}                                               ; 保存LR
    LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();
    BLX     R0
    POP     {R14}

    LDR     R0, =OSPrioCur                                      ; OSPrioCur   = OSPrioHighRdy;
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]

    LDR     R0, =OSTCBCurPtr                                    ; OSTCBCurPtr = OSTCBHighRdyPtr;
    LDR     R1, =OSTCBHighRdyPtr
    LDR     R2, [R1]
    STR     R2, [R0]

    LDR     R0, [R2]                                            ; R0 新任务的堆栈指针 
    LDM     R0, {R4-R11}                                        ; POP出 r4-11 
    ADDS    R0, R0, #0x20
    MSR     PSP, R0                                             ; 给PSP赋值
    ORR     LR, LR, #0x04                                       ; 确保使用的是PSP不是MSP
    CPSIE   I
    BX      LR                                                  ; 跳转LR的地址

    END
  • 首先,函数首先利用保存上文与恢复下文的间隙,调用执行OSTaskSwHook()用户定义的任务切换对外接口函数,之后返回。
  • 然后,将OSPrioCur(当前的优先级)和OSTCBCurPtr(当前的任务TCB指针)更新(在进入中断前,已经将OSPrioHighRdy和OSTCBHighRdyPtr通过相应的函数赋好了新的值,指向了新的高优先级的任务,例如第一次赋值是在OSStart()函数中进行的)。
  • 通过LDR指令,获取任务的堆栈指针OSTCBStkPtr,将R4-R11手动POP出堆栈,存入CPU的寄存器中,因为退出中断时剩余寄存器将自动POP出堆栈,只需将R0当前的值赋予PSP即可,所以剩余的寄存器我们无需操作。当剩余寄存器自动POP出时,PSP将指向堆底。整个过程正好与保存上文的过程相反。

  •  ORR     LR, LR, #0x04 这一步确保在跳转后使用的是PSP,《Cortex-M3 权威指南》里有介绍。

  • 之后开启中断,跳转到LR的地址继续执行新的任务。

总结来说:

1.  OSStartHighRdy()函数完成的是OS开始前的准备工作;

2.  OS_CPU_PendSVHandler()函数完成了上文保存工作;

3.  OS_CPU_PendSVHandler_nosave()函数完成的是切换任务恢复下文并跳转执行的工作;

引用:Cortex-M3 PSP寄存器作用之我的理解

涉及到任务切换时,无论是任务级别的切换还是中断级别的切换,实际的切换操作都是在PendSV异常中断函数中实现的,实现过程如下:

1.如果系统是刚开机,准备运行第一个任务,此时PSP还为0,则调用切换函数之前已经得到OSTCBPrioRdy和OSPrioHighRdy的值,此时该任务的cpu各寄存器内容已经保存在任务堆栈中,此时我们所做的就是将压入堆栈的内容弹回cpu的各个寄存器,恢复运行现场(确切点说第一次不能用恢复),恢复现场之后将此时的OSTCBCur->OSTCBStkPtr给予cpu的sp,此时cpu的sp寄存器就保存了现在运行任务的堆栈栈顶地址。

2.如果此次任务切换不是第一次,则此时PSP保存的是切换之前任务的堆栈栈顶地址,此时我们可以用PUSH指令将R4-R11的内容根据PSP所提供的堆栈栈顶地址压入堆栈。然后根据步骤1我们可以知道,在调用切换函数之前我们已经知道了OSTCBPrioRdy和OSPrioHighRdy,此时我们可以我们可以知道即将运行的任务的堆栈地址,根据堆栈地址我们用POP命令弹出堆栈保存的值,恢复cpu的运行现场,弹出之后将OSTCBCur->OSTCBStkPtr的值给予cpu的sp寄存器,此时cpu的sp寄存器就保存了现在运行任务的堆栈栈顶地址。

参考:http://www.firebbs.cn/forum.php?mod=viewthread&tid=23554&highlight=%A1%B6%B4%D30%B5%BD1%BD%CC%C4%E3%D0%B4uC%2FOS-III%A1%B7

QQ:956599264

欢迎交流
 

猜你喜欢

转载自blog.csdn.net/sinat_27066063/article/details/84027497