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來進行上下文切換。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM