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異常服務程式進行任務切換。