1. 程式人生 > >uc/os-ii任務排程(二)

uc/os-ii任務排程(二)

任務排程關鍵是任務執行環境的切換。任務執行環境包括以下:
1. 程式的斷點地址(PC
2. 程式狀態字暫存器(xPSR
3. 通用暫存器內容
4. 任務堆疊指標(SP
其中1、2、3儲存在任務堆疊中,4儲存在任務的任務控制塊中。
程式切換的關鍵是把程式的私有堆疊指標賦予處理器的堆疊指標PSP。

  • 這裡主要分析中斷級的排程OSIntExt()

當一箇中斷處理函式退出時,OSIntExit()會被呼叫來決定是否有優先順序更高的任務需要執行。如果有則呼叫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(); } }

彙編函式OSIntCtxSw()原始碼如下,其作用是觸發PendSV中斷

OSIntCtxSw
    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

看到這裡可能奇怪怎麼OSCtxSw()OSIntCtxSw()完全一樣,事實上,這兩個函式的意義是不一樣的,OSCtxSw()做的是任務之間的切換,如任務A因為等待某個資源或是做延時切換到任務B,而OSIntCtxSw()則是中斷退出時,由中斷狀態切換到另一個任務。由中斷切換到任務時,CPU暫存器入棧的工作已經做完了,所以無需做第二次了(參考邵老師書的3.10節)。這裡只不過由於CM3的特殊機制導致了在這兩個函式中只要做觸發PendSV中斷即可,具體切換由PendSV中斷來處理。
前面已經說過真正的任務切換是在PendSV中斷處理函式裡做的,由於CM3在中斷時會有一半的暫存器自動儲存到任務堆疊裡,所以在PendSV中斷處理函式中只需儲存R4-R11並調節堆疊指標即可。

=========中斷級的任務排程就分析這麼多==========

  • 之前關於任務排程有過以下疑惑:在任務級排程函式中OS_TASK_SW()函式是在臨界段中執行的,即已經通過將PRIMASK寫入1關閉所有可遮蔽的中斷。那麼在OS_TASK_SW()中觸發的PendSV異常還能夠得到響應嗎?雖然知道結果是一定可以響應的,否則任務怎麼切換呢。可是不明白為什麼中斷關閉了還能響應PendSV異常呢?
void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3                            /* Allocate storage for CPU status register     */
    OS_CPU_SR  cpu_sr = 0;
#endif
    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {                           /* Schedule only if all ISRs done and ...       */
        if (OSLockNesting == 0) {                      /* ... scheduler is 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++;                          /* Increment context switch counter             */
                OS_TASK_SW();                          /* Perform a context switch                     */
            }
        }
    }
    OS_EXIT_CRITICAL();
}

其實OS_Sched()執行順序應該是這樣的:
1. 關中斷OS_ENTER_CRITICAL();
2. 獲取最高優先順序的就緒任務OS_SchedNew();
3. 觸發PendSV異常OSCtxSw();注意這裡觸發的異常只是被掛起,並沒有立即得到響應。
4. 開中斷並推出函式OS_EXIT_CRITICAL();
5. 開中斷後立即進入OS_CPU_PendSVHandler()PendSV異常服務程式進行任務切換。
中斷被遮蔽後被掛起的異常會一直掛起,知道開啟中斷才會進入異常服務程式。

同理,在中斷級的任務排程中執行OSIntCtxSw()後掛起PendSV異常,要等退出中斷服務函式後在進入OS_CPU_PendSVHandler()PendSV異常服務程式進行任務切換。