μCOS的任務切換時間:1.任務創建時 2.任務掛起時 3.任務恢復 4.任務延時時 6.任務釋放信號量時 7.任務釋放互斥信號量時 8.任務請求消息郵箱時 9.任務釋放消息隊列時 10 中斷退出時(OSINTEXIT()函數中)
任務之前的切換應該就是利用時鐘中斷來實現,當OS運行完一個時鐘片後會產生一個中斷(定時器的中斷)異常,PC指針立即跳轉到異常向量表執行處理異常的代碼,隨後會導致OS執行一次任務調度。整個過程分析如下:
產生異常後執行的第一條指令
b HandlerIRQ
跳轉到HandlerIRQ,而HandlerIRQ是一條宏命令,定義如下:
HandlerIRQ HANDLER HandleIRQ
宏體如下:
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4
stmfd sp!,{r0} //後面代碼需要使用R0寄存器,所以先將其入棧。
ldr r0,=$HandleLabel //此處是將HandeIRQ的地址賦給寄存器R0,Handler中存放是中斷服務函數匯編部分的入口地址,此匯編用來計算中斷服務函數的入口地址,所有IRQ中斷通用。HandeIRQ的賦值操作在後面。
ldr r0,[r0] //此處讀取R0中的內容,即中斷服務函數匯編部分的入口地址
str r0,[sp,#4]
ldmfd sp!,{r0,pc} //出棧,棧中R0的值重新賦給R0,中斷服務函數匯編入口地址復制給PC。相當於跳轉到匯編入口地址處。
MEND
HandeIRQ中存放匯編入口地址的代碼如下。
ldr r0,=HandleIRQ //取HandeIRQ地址
ldr r1, =OS_CPU_IRQ_ISR //取OS_CPU_IRQ_ISR地址
str r1,[r0] //將OS_CPU_IRQ_ISR地址賦值給HandeIRQ
執行完宏體的結果應該是跳轉到OS_CPU_IRQ_ISR
OS_CPU_IRQ_ISR
STMFD SP!, {R1-R3} //後面會皆用這三個寄存器,所以先將其入棧
MOV R1, SP //把IRQ模式下的當前SP指針保存到R1
ADD SP, SP, #12 //調整SP,使其指向R1~R3入棧前的地址
SUB R2, LR, #4 //LR保存的是返回值的地址,但是不同的異常產生的LR不同。IRQ異常時,LR保存的是下一條將被執行的指令+4。所以此處需要LR-4。來調整。
MRS R3, SPSR ; Copy SPSR (Task CPSR)
MSR CPSR_cxsf, #SVCMODE|NOINT //切換處理器運行狀態,SP也會更改為特權模式的堆棧指針
下面代碼的作用是將被中斷的任務的相關寄存器保存到特權模式的堆棧下。
STMFD SP!, {R2} //R2中存放的是被中斷任務的下一條將被執行的指令,將其入棧。
STMFD SP!, {R4-R12, LR} ; Push task''s LR,R12-R4
LDMFD R1!, {R4-R6} //R1此時是IRQ堆棧的棧頂指針,和下一條指令一起將被中斷任務的R1~R3寄存器保存到特權模式的堆棧中
STMFD SP!, {R4-R6} ; Push Task''s R3-R1 to SVC stack
STMFD SP!, {R0} ; Push Task''s R0 to SVC stack
STMFD SP!, {R3} ; Push task''s CPSR
此時的特權模式堆棧結構應該是:
LR
R12
R11
R10
R9
R8
R7
R6
R5
R4
R3
R2
R1
R0
CPSR
LDR R0,=OSIntNesting ;OSIntNesting++
LDRB R1,[R0]
ADD R1,R1,#1
STRB R1,[R0] //μC/OS中OSInrNesting變量+1
CMP R1,#1 ;if(OSIntNesting==1){
BNE %F1
LDR R4,=OSTCBCur // ;OSTCBHighRdy->OSTCBStkPtr=SP;
LDR R5,[R4] //將當前的TCB指針賦值R5
STR SP,[R5] //將被特權模式的棧頂指針保存到北中斷任務的OSTCBStkPtr中
1
MSR CPSR_c,#IRQMODE|NOINT ;Change to IRQ mode to use IRQ stack to handle interrupt
LDR R0, =INTOFFSET
LDR R0, [R0]
LDR R1, IRQIsrVect //IRQ已經通過指令
//IRQIsrVect DCD HandleEINT0被賦值為HandleEINT0的地址
MOV LR, PC //此時的PC應該指向MSR那條指令,LDR這條恰好是跳轉指令,這樣跳轉之後剛好返回MSR。
LDR PC, [R1, R0, LSL #2] //此處計算C語言的中斷服務函數入口地址,然後跳轉執行。
MSR CPSR_c,#SVCMODE|NOINT //中斷服務函數執行完後返回
BL OSIntExit //跳轉OSIntExit執行,先插入分析OSIntExit
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr = 0;
#endif
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) {
OSIntNesting--;
}
if (OSIntNesting == 0) {
if (OSLockNesting == 0) {
OS_SchedNew(); //計算處於最高優先級的任務
if (OSPrioHighRdy != OSPrioCur) { //假如就緒態的任務優先級高於當前運行的任務優先級著將其調度執行
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++;
OSIntCtxSw(); //任務切換
}
}
}
OS_EXIT_CRITICAL();
}
}
LDMFD SP!,{R4} //將當前堆棧保存的數據彈出一個到R4,即將被中斷的任務的CPSR保存到R4
MSR SPSR_cxsf,R4
LDMFD SP!,{R0-R12,LR,PC}^ 出棧,繼續執行被中斷的任務。
OSInt和OS_Sched類似但是不能互換使用。OSIntExit含有中斷嵌套數的計算。在OSIntExit做任務切換使用的是OSIntCtxSw,而OS_Sched中使用的是OS_TASK_SW。根據如上分析,每個時鐘片刻結束時候OS都會根據優先級重新調度一遍,所以說在μC/OS,只要高優先級的任務處於就緒態,就會立即搶占CPU。以為在這一個時鐘片結束後,OS會執行一次調度。