μCOS-II中斷處理(基於ARM處理器)

1、任務上下文

μCOS-II V2.83的任務上下文保存在任務的棧裏面,任務創建時,將任務的參數、運行地址(任務入口)、任務棧等信息保存在任務棧裏面,OS_TCB->OSTCBStkPtr指向任務棧頂(任務創建及掛起時,OS_TCB->OSTCBStkPtr即指向任務上下文)。

ARM棧是遞減棧(棧底地址高、棧頂地址低,地址由低到高爲:棧頂->棧數據->棧底)!

1.1、 任務創建時任務棧設置

以下爲os_cpu_c.c任務創建時任務棧初始化函數。

OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
    OS_STK *stk;


    opt      = opt;                         /* 'opt' is not used, prevent warning                      */
    stk      = ptos;                        /* Load stack pointer                                      */
    *(stk)   = (OS_STK)task;                /* Entry Point                                             */
    *(--stk) = (INT32U)0x14141414L;         /* R14 (LR) (init value will cause fault if ever used)     */
    *(--stk) = (INT32U)0x12121212L;         /* R12                                                     */
    *(--stk) = (INT32U)0x11111111L;         /* R11                                                     */
    *(--stk) = (INT32U)0x10101010L;         /* R10                                                     */
    *(--stk) = (INT32U)0x09090909L;         /* R9                                                      */
    *(--stk) = (INT32U)0x08080808L;         /* R8                                                      */
    *(--stk) = (INT32U)0x07070707L;         /* R7                                                      */
    *(--stk) = (INT32U)0x06060606L;         /* R6                                                      */
    *(--stk) = (INT32U)0x05050505L;         /* R5                                                      */
    *(--stk) = (INT32U)0x04040404L;         /* R4                                                      */
    *(--stk) = (INT32U)0x03030303L;         /* R3                                                      */
    *(--stk) = (INT32U)0x02020202L;         /* R2                                                      */
    *(--stk) = (INT32U)0x01010101L;         /* R1                                                      */
    *(--stk) = (INT32U)p_arg;               /* R0 : argument                                           */
    *(--stk) = (INT32U)0x00000013L;         /* CPSR  (Enable both IRQ and FIQ interrupts)              */

    return (stk);
}

從第一個*(--stk)看,是先將棧地址減一在存儲數據,因此該棧是個滿棧(即棧指針指向的地址有數據,前置遞減可以看出棧是個遞減棧,棧的增長是從高地址往低地址增長);入棧順序依次是{task, r14-r0, cpsr},task在內存高地址,cpsr在內存低地址。

棧中幾個關鍵的寄存器:r14(lr),這裏設置的是一個無意義的值,因此這種情況下程序結束時返回地址會異常(其他嵌入式實時系統會設置返回地址指向一個任務退出函數);r0任務運行參數指針;cpsr任務運行時的cpsr,這裏設置任務運行在SVC模式,另外任務運行時默認必須允許中斷,否則中斷處理器沒法處理任務調度定時器中斷信號。

寄存器在棧中的順序主要與上下文保存及恢復有關。

1.2、 第一個任務的運行

第一個任務是由main函數執行的,main函數沒有運行在任務上下文,操作系統運行之後也不會再返回到被中斷的main函數繼續執行,因此,第一個任務執行時沒有保存main函數上下文的必要。

        .text

OSStartHighRdy:

#if (OS_TASK_SW_HOOK >= 1)
        BL      OS_TaskSwHook                                   // OSTaskSwHook();
#endif

        MSR     CPSR_c, #(I_BIT | F_BIT | ARM_MODE_SVC)         // Switch to SVC mode with IRQ and FIQ disabled

        LDR     R4, =OSRunning                                  // OSRunning = TRUE;
        MOV     R5, #1
        STRB    R5, [R4]

        LDR     R0, =OSTCBCur                                   // SWITCH TO HIGHEST PRIORITY TASK:
        LDR     R0, [R0]                                        //    Get stack pointer,
        LDR     SP, [R0]                                        //    Switch to the new stack,

        LDMFD   SP!, {R0}                                       //    Pop new task's CPSR,
        MSR     SPSR_cxsf, R0
        LDMFD   SP!, {R0-R12, LR, PC}^                          //    Pop new task's context.

OSStartHighRdy函數主要步驟是:

1、禁止中斷;

2、OSRunning = TRUE設置操作系統運行狀態(只有操作系統運行時,調度定時器纔有必要調用);

3、恢復任務棧;

獲取任務棧頂

        LDR     R0, =OSTCBCur // 獲取任務控制塊地址(彙編語言 "=變量名稱"用於獲取該變量的地址,等價於c語言的 r0 = &OSTCBCur)
        LDR     R0, [R0] // 獲取OSTCBStkPtr的地址(r0 = OSTCBCur)
        LDR     SP, [R0] // 獲取OSTCBStkPtr的值(即任務棧頂)(sp = OSTCBStkPtr->OSTCBStkPtr

任務棧恢復

        LDMFD   SP!, {R0} // 出棧(棧頂元素pop到r0,棧頂此時是任務創建時的cpsr)
        MSR     SPSR_cxsf, R0 // 任務的cpsr保存到spsr
        LDMFD   SP!, {R0-R12, LR, PC}^ // 寄存器出棧(1、ldmfd帶有pc及^標誌,指令會將spsr備份程序狀態寄存器的值恢復到cpsr程序狀態寄存器;2、r0-r12, lr, pc恢復,此次主要時將任務入口的參數恢復並將程序寄存器pc設置爲task入口函數指針;3、ldmfd之前的cpu已經切換到SVC模式,任務也是運行在SVC模式,因此sp寄存器並沒有在ldmfd恢復寄存器列表裏面,ldmfd sp!, ... 本身就是在操作任務的棧,任務切換前後的sp是同一個sp)

 

2、中斷處理

中斷處理主要保存任務/中斷上下文,恢復任務/中斷上下文。中斷處理彙編代碼如下:

/*
 * IRQ interrupt
 */
        .align  5
irq:
        stmfd   sp!, {r1-r3}                                    // We will use r1-r3 as temporary registers

        mov    r1, sp                                           // sp -> r1
        add    sp, sp, #12                                      // Adjust IRQ stack pointer
        sub    r2, lr, #4                                       // Adjust pc for return address to task
        mrs    r3, spsr                                         // Copy spsr (task's cpsr)

        /*
         * save task's context onto old task's stack
         */
        msr    cpsr_cxsf, #(I_BIT | F_BIT | ARM_MODE_SVC)       // Change to SVC mode

        stmfd  sp!, {r2}                                        // Push task's pc
        stmfd  sp!, {r4-r12, lr}                                // Push task's lr, r12-r4

        ldmfd  r1!, {r4-r6}                                     // Load task's r1-r3 from IRQ stack
        stmfd  sp!, {r4-r6}                                     // Push task's r1-r3 to SVC stack
        stmfd  sp!, {r0}                                        // Push task's r0 to SVC stack
        stmfd  sp!, {r3}                                        // Push task's cpsr

        ldr    r0, =OSIntNesting                                // OSIntNesting++
        ldrb   r1, [r0]
        add    r1, r1, #1
        strb   r1, [r0]

        cmp    r1, #1                                           // if (OSIntNesting == 1) {
        bne    1f
        ldr    r4, =OSTCBCur                                    //     OSTCBCur->OSTCBStkPtr = sp;
        ldr    r5, [r4]
        str    sp, [r5]                                         // }

1:
        msr    cpsr_c, #(I_BIT | F_BIT | ARM_MODE_IRQ)          // Change to IRQ mode to use IRQ stack to handle interrupt
        bl     do_IRQ
        msr    cpsr_c, #(I_BIT | F_BIT | ARM_MODE_SVC)          // Change to SVC mode
        bl     OSIntExit                                        // Call OSIntExit

        ldmfd  sp!, {r4}                                        // Pop the task's cpsr
        msr    spsr_cxsf, r4
        ldmfd  sp!, {r0-r12, lr, pc}^                           // Pop new task's context

2.1、中斷上下文保存

        stmfd   sp!, {r1-r3} // 通用寄存器r1-r3保存到IRQ棧裏面(r1-r3將作爲臨時寄存器使用,即寄存器將被改變,sp = sp - 4 * 3)

        mov    r1, sp // r1保存IRQ模式的棧頂(中斷前任務/中斷處理程序r1-r3保存的地址)
        add    sp, sp, #12 // 恢復IRQ的棧指針(sp = sp + 4 * 3),保存的寄存器仍在內存中,只是移動了IRQ的棧指針而已,後續代碼不會操作IRQ的sp值,此處如果不恢復的話將會造成IRQ模式棧內存的泄露!
        sub    r2, lr, #4 // 中斷返回地址保存到r2中(中斷時,硬件會將中斷前的pc寄存器的值減去4然後保存到IRQ的lr寄存器裏面,中斷前的pc指向的是正在取指的指令地址,pc之前有正在指向的指令、正在譯碼的指令,pc - 8纔是正在執行指令的地址,正在執行的指令被中斷了,因此需要重新執行)
        mrs    r3, spsr // 中斷前的cpsr寄存器的值保存在spsr裏面,中斷前的cpsr保存到r3

目前已經保存的寄存器有,r1指向的內存(r1-r3),IRQ寄存器r2(pc)、r3(cpsr)。

2.2、任務/中斷處理上下文保存到任務棧

前一小結中的中斷前的部分寄存器保存到了IRQ的棧裏面,再中斷重入的情況下,會造成保存的上下文被覆蓋,因此IRQ棧裏面只是臨時保存了部分上下文數據而已,在再次允許中斷前,需要將IRQ棧裏面的數據保存到其他地方(任務的棧)。

        msr    cpsr_cxsf, #(I_BIT | F_BIT | ARM_MODE_SVC)  // 切換到SVC模式並禁止中斷

        stmfd  sp!, {r2} // r2(pc)入棧(如同任務創建時一樣,棧底保存任務運行地址),此處已經是SVC模式了,因爲任務也運行在SVC模式,因此,此處操作的是任務的棧! 棧的內容爲: pc(低地址到高地址)
        stmfd  sp!, {r4-r12, lr} // {r4-r12, lr}入棧,棧的內容爲:r4-r12,lr,pc(低地址到高地址)

        ldmfd  r1!, {r4-r6} // r1指向IRQ棧保存上下文的地址,IRQ保存的棧數據(r1-r3)恢復到寄存器r4-r6
        stmfd  sp!, {r4-r6} // r4-r6(r1-r3)入棧,棧的內容爲:r1-r12,lr,pc(低地址到高地址)
        stmfd  sp!, {r0} // r0入棧(r0爲通用寄存器,在這之前的中斷處理一直沒有改變r0的值),棧的內容爲:r0-r12,lr,pc(低地址到高地址)
        stmfd  sp!, {r3} // cpsr入棧(r3爲通用寄存器,此前已經保存了中斷前的cpsr),棧的內容爲:cpsr,r0-r12,lr,pc(低地址到高地址)

至此,中斷前任務/中斷處理函數的所有寄存器都保存到了任務棧裏面了!

2.3、任務棧設置

中斷可能導致任務被重新調度,因此需要將任務上下文的地址保存到任務控制塊裏面,任務重新執行的時候纔可能找到上下文。

        ldr    r0, =OSIntNesting // 中斷嵌套計數器OSIntNesting加1
        ldrb   r1, [r0]
        add    r1, r1, #1
        strb   r1, [r0]

        cmp    r1, #1 // 判斷中斷嵌套計數器OSIntNesting是否爲1,如果爲1表示當前是第一次進入中斷,即中斷前是在執行操作系統的任務,需要更新被中斷任務的棧指針OSTCBStkPtr
        bne    1f
        ldr    r4, =OSTCBCur // r4 = &OSTCBCur
        ldr    r5, [r4] // r5 = OSTCBCur
        str    sp, [r5] // OSTCBCur->OSTCBStkPtr = sp

2.4、中斷退出

(中斷處理的c函數略過,中斷處理c函數不會切換上下文,執行完成後會回到彙編代碼!)

        bl     OSIntExit // 調用中斷退出函數(操作系統調度定時器更新可能會喚醒某些等待超時的任務,當前任務優先級可能不再是最高優先級了)

// OSIntExit沒有執行任務切換(中斷嵌套/當前任務優先級最高),此處代碼將繼續執行,後續代碼僅恢復被中斷的任務/中斷處理函數(do_IRQ)

        ldmfd  sp!, {r4} // r4 = cpsr
        msr    spsr_cxsf, r4 // spsr = cpsr
        ldmfd  sp!, {r0-r12, lr, pc}^ // 從SVC棧恢復寄存器

3、任務搶佔

中斷處理函數除了調度定時器計數導致任務時間片用完、等到超時任務定時器超時外,其他硬件中斷處理也可能使某個睡眠任務被喚醒,因此當前任務可能被高優先級任務搶佔。OSIntExit在中斷退出時進行搶佔檢查,判斷是否有更高優先級任務。

3.1、OSIntExit中斷退出

主要是對中斷嵌套計數器OSIntNesting減1,如果是中斷嵌套,則返回中斷彙編代碼繼續處理,直到所有中斷退出,如果中斷都處理完成(OSIntNesting爲0),則調用OS_SchedNew更新就緒任務的最高優先級OSPrioHighRdy。

如果被中斷的任務的優先級最高,則返回中斷彙編代碼,中斷彙編代碼負責恢復中斷上下文;

如果有更高優先級的任務就緒,則更新OSTCBHighRdy、OSTCBHighRdy->OSTCBCtxSwCtr、OSCtxSwCtr,並調用OSIntCtxSw執行任務切換,OSIntCtxSw不同於任務主動睡眠的切換,OSIntCtxSw函數調用前已經保存了被切換出去的任務的上下文,因此OSIntCtxSw僅恢復下一任務的上下文即可。

void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif



    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0) {                            /* Prevent OSIntNesting from wrapping       */
            OSIntNesting--;
        }
        if (OSIntNesting == 0) {                           /* Reschedule only if all ISRs complete ... */
            if (OSLockNesting == 0) {                      /* ... and not locked.                      */
                OS_SchedNew();
                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy */
                    OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                    OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task  */
#endif
                    OSCtxSwCtr++;                          /* Keep track of the number of ctx switches */
                    OSIntCtxSw();                          /* Perform interrupt level ctx switch       */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
}

3.2、OSIntCtxSw任務上下文恢復

前面已經講了,中斷上下文已經保存,OSIntCtxSw僅恢復下一任務上下文。

OSIntCtxSw主要執行以下任務:

1、設置OSPrioCur、OSTCBCur,這兩個值用在不同的地方,OSPrioCur主要用於與最高就緒任務優先級比較,OSTCBCur主要用於任務上下文的切換及任務統計;

2、獲取下一任務的sp(棧頂保存了任務的上下文),恢復任務上下文。

OSIntCtxSw:
        LDR     R0, =OSPrioHighRdy                              // OSPrioCur = OSPrioHighRdy;
        LDR     R1, =OSPrioCur
        LDRB    R0, [R0]
        STRB    R0, [R1]

        LDR     R0, =OSTCBHighRdy                               // OSTCBCur  = OSTCBHighRdy;
        LDR     R1, =OSTCBCur
        LDR     R0, [R0]
        STR     R0, [R1]

        LDR     R0, =OSTCBHighRdy                               // SP = OSTCBHighRdy->task_stack;
        LDR     R0, [R0]
        LDR     SP, [R0]

        LDMFD   SP!, {R0}                                       // RESTORE NEW TASK'S CONTEXT:
        MSR     SPSR_cxsf, R0                                   //    Pop new task's CPSR,
        LDMFD   SP!, {R0-R12, LR, PC}^                          //    Pop new task's context.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章