UCOSIII之STM32上下文切换理解

UCOSIII之STM32上下文切换理解

程序上下文(context)

上下文(context),指的是什么呢,个人可以理解为一个任务或者线程控制的一些变量及CPU的寄存器状态,就是说任务被打断执行以后还可以还原回来。所以上下文就指的是两个操作,被打断任务状态的保存及就绪作务的还原。如果说一个任务的状态完全恢复就指的是,其CPU寄存器的值都被还原,以及其操作的内存及其他外设的状态是一致的,如果说其操作的内存被其他作务或线程改变,那这个就是多线程的一个资源共享及锁定的问题。

STM32 CPU寄存器


CORTEX-M3总共19个寄存器
R0-R15,MSP,PSP,特殊寄存器(三个状态寄存器合用一个32位的寄存器)
那接下来就看下UCOSIII中STM32是如何实现上下文切换的。

PENDSV中断

在发生任务切换时,是由任务激活PENDSV中断(cortex-m3专业用于OS的中断),在中断函数中进行上下文切换。同时这个PENDSV的中断优先级设置的是最低优先级。

在发生中断时,CORTEX-M3自动做了如下操作

1.入栈,把8个寄存器压入栈,R0~R3 R12 LR PC xPSR按一定的顺序入栈保存

自动入栈只是入了部分寄存器,并没将所有的寄存器入栈,应该是考虑到效率跟STACK的使用效率,不过只要在编译的时候,只用到入栈的几个寄存器就不影响使用了,如果要使用到其他寄存器,那么由编译器来负责PUSH与POP,就是C=>汇编时,编译器要做的事情。

2.取向量:即取得中断响应函数地址的PC

3.更新当前的寄存器,更新SP,LR,PC

CORTEX-M3在中断中使用的都是MSP,因此如果任务代码使用的PSP那么,SP内里会被更新为MSP,LR更新为EXC_RETRUN(系统自动生成),而不是函数的返回地址,不过中断返回时也会用到这个被更新的LR,PC改为之前取向量所得到的值。

具体的PENDSV中断操作

OS_CPU_PendSVHandler
    ;进入中断,此时已保存8个寄存器到SP,任务切换时是使用的PSP,因此自动保存至PSP
    CPSID   I                                                   ; Prevent interruption during context switch
    ;关中断
    MRS     R0, PSP                                             ; PSP is process stack pointer
    ;PSP存入R0
    CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time
    ;第一次开始使用的时候直接跳转,之后都不会跳转

    SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
    STM     R0, {R4-R11}
    ;保存R4~R11的寄存器至当前PSP

    LDR     R1, =OSTCBCurPtr                                    ; OSTCBCurPtr->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                                            ; R0 is SP of process being switched out
    ;更新PSP的地址为保存寄存器之后的值至当前OSTCBCurPtr

                                                                ; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave
    PUSH    {R14}                                               ; Save LR exc_return value
    ;保存R14(LR),防止调用函数时被覆盖
    LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();
    ;LDR的伪指令
    BLX     R0
    ;调用OSTaskSwHook,钩子函数,当然这里去掉也是不影响系统正常使用,只是少了一个功能
    ;调用OSTaskSwHook函数时,肯定也会用到一些寄存器,只是这些寄存器的PUSH与POP都由OSTaskSwHook自动来完成
    POP     {R14}
    ;还原R14,用于中断返回

    LDR     R0, =OSPrioCur                                      ; OSPrioCur   = OSPrioHighRdy;
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]
    ;更新当前READY的高优先级

    LDR     R0, =OSTCBCurPtr                                    ; OSTCBCurPtr = OSTCBHighRdyPtr;
    LDR     R1, =OSTCBHighRdyPtr
    LDR     R2, [R1]
    STR     R2, [R0]
    ;更新当前READY的高优先级任务块指针

    LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
    ;R2还是之前的OSTCBCurPtr(更新之后的),不过这里的使用跟OS_TCB的定义有关,OS_TCB的定义是把StkPtr放在最前面,因此这里可以直接把任务的栈地址,直接以这种方式调进来。
    LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stack
    ;还原R4~R11
    ADDS    R0, R0, #0x20
    ;更新栈地址
    MSR     PSP, R0                                             ; Load PSP with new process SP
    ;更新PSP地址
    ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack
    ;将LR相应置位,保证返回时使用的是PSP,不过这个话,只对第一次的任务调用有效
    ;因为默认栈用的是MSP,而从PSP因中断切换为MSP是,中断返回时会自动跳转到PSP
    CPSIE   I
    ;开中断
    BX      LR                                                  ; Exception return will restore remaining context
    ;中断返回,返回根据当前的PSP来还原起始被PUSH的8个寄存器
    END

下面是UCOS启动时的代码

手动将PSP置0,才会有上面OS_CPU_PendSVHandler_nosave跳转只发生一次的情形

OSStartHighRdy
    LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority
    LDR     R1, =NVIC_PENDSV_PRI
    STRB    R1, [R0]

    MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call
    MSR     PSP, R0

    LDR     R0, =OS_CPU_ExceptStkBase                           ; Initialize the MSP to the OS_CPU_ExceptStkBase
    LDR     R1, [R0]
    MSR     MSP, R1    

    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    
    CPSIE   I                                                   ; Enable interrupts at processor level

任务栈初始化

任务栈初始化时,必须将相应的CPU寄存器预留相应的位置,这里面的一些值是起始的任务函数地址,及传入的函数,还有一些寄存器的初始化,只是必须要把栈的地址给空出来,不然任务第一恢复运行的时候,就不会正常工作。

CPU_STK  *OSTaskStkInit (OS_TASK_PTR    p_task,
                         void          *p_arg,
                         CPU_STK       *p_stk_base,
                         CPU_STK       *p_stk_limit,
                         CPU_STK_SIZE   stk_size,
                         OS_OPT         opt)
{
    CPU_STK  *p_stk;


    (void)opt;                                              /* Prevent compiler warning                               */

    p_stk = &p_stk_base[stk_size];                          /* Load stack pointer                                     */
                                                            /* Registers stacked as if auto-saved on exception        */
    *--p_stk = (CPU_STK)0x01000000u;                        /* xPSR                                                   */
    *--p_stk = (CPU_STK)p_task;                             /* Entry Point                                            */
    *--p_stk = (CPU_STK)OS_TaskReturn;                      /* R14 (LR)                                               */
    *--p_stk = (CPU_STK)0x12121212u;                        /* R12                                                    */
    *--p_stk = (CPU_STK)0x03030303u;                        /* R3                                                     */
    *--p_stk = (CPU_STK)0x02020202u;                        /* R2                                                     */
    *--p_stk = (CPU_STK)p_stk_limit;                        /* R1                                                     */
    *--p_stk = (CPU_STK)p_arg;                              /* R0 : argument                                          */
                                                            /* Remaining registers saved on process stack             */
    *--p_stk = (CPU_STK)0x11111111u;                        /* R11                                                    */
    *--p_stk = (CPU_STK)0x10101010u;                        /* R10                                                    */
    *--p_stk = (CPU_STK)0x09090909u;                        /* R9                                                     */
    *--p_stk = (CPU_STK)0x08080808u;                        /* R8                                                     */
    *--p_stk = (CPU_STK)0x07070707u;                        /* R7                                                     */
    *--p_stk = (CPU_STK)0x06060606u;                        /* R6                                                     */
    *--p_stk = (CPU_STK)0x05050505u;                        /* R5                                                     */
    *--p_stk = (CPU_STK)0x04040404u;                        /* R4                                                     */

    return (p_stk);
}

个别汇编指令备注

EXC_RETURN详解


这是为什么要使用ORR LR,LR,#0x04这个指令的原因,将EXC_RETURN的第2bit置1,使其返回线程模式,返回后使用PSP,而FREERTOS中并没有使用这个语句,是因为FREERTOS的第一个任务调用跟UCOS的实现方式是不一样,因此可以不使用这个置位操作

中断返回

一般使用BX LR这个指令来返回,但这种返回方式并不是唯一。

上面这个过程就实现了任务的上下文切换,不过关于的任务的调度是没有在PENDSV这个中断中实现的,先找到高优先级的任务再使用PENDSV来进行上下文切换。

原文地址:https://www.cnblogs.com/stupidpeng/p/13650281.html