zoukankan      html  css  js  c++  java
  • 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来进行上下文切换。

  • 相关阅读:
    Python类的继承(进阶5)
    面向对象编程基础(进阶4)
    Python模块(进阶3)
    Python函数式编程(进阶2)
    python多线程
    Ternary Search Tree Java实现
    Trie和Ternary Search Tree介绍
    索引时利用K-邻近算法过滤重复歌曲
    Sql排名和分组排名
    Lucene和jackson冲突
  • 原文地址:https://www.cnblogs.com/stupidpeng/p/13650281.html
Copyright © 2011-2022 走看看