§4.3.3.2 OSIntCtxSw的編寫
在μC/OS-Ⅱ中,任務切換隻是簡單的將處理器寄存器儲存到將被掛起的任務的堆棧中,並且將更高優先順序的任務從堆棧中恢複出來。處於就緒狀態的任務的堆棧結構看起來就像剛發生過中斷並將所有的寄存器儲存到堆棧中的情形一樣。換句話說,μC/OS-Ⅱ要運行處於就緒狀態的任務必須要做的事就是將所有處理器寄存器從任務堆棧中恢複出來,並且執行中斷的返回。
在μC/OS-Ⅱ中,使用者級任務調度時會調用宏(或者函數)OS_TASK_SW(),它是在μC/OS-Ⅱ從低優先順序任務切換到最高優先順序任務時被調用的,μC/OS-Ⅱ建議OS_TASK_SW()通過某種途徑最終調用函數OSCtxSw()。函數OSCtxSw()是與系統相關的,μC/OS-Ⅱ提供的OSCtxSw()函數原型如下:
OSCtxSw()原型的程式清單
void OSCtxSw(void)
{
儲存處理器寄存器;
將當前任務的堆棧指標儲存到當前任務的OS_TCB中;
OSTCBCur->OSTCBStkPtr = Stack pointer;
調用使用者定義的OSTaskSwHook();
OSTCBCur = OSTCBHighRdy;
OSPrioCur = OSPrioHighRdy;
得到需要恢複的任務的堆棧指標;
Stack pointer = OSTCBHighRdy->OSTCBStkPtr;
將所有處理器寄存器從新任務的堆棧中恢複出來;
執行中斷返回指令;
}
在μC/OS-Ⅱ中,函數OSIntExit( )被用來在ISR使得更高優先順序任務處於就緒狀態時,執行任務切換功能。中斷退出函數通過調OSIntCtxSw()來從ISR中執行切換功能。函數OSIntCtxSw()是與系統相關的,μC/OS-Ⅱ提供的OSIntCtxSw()函數原型如下:
OSIntCtxSw( )原型的程式清單
void OSIntCtxSw(void)
{
調用使用者定義的OSTaskSwHook( ) ;
OSTCBCur = OSTCBHighRdy ;
OSPrioCur = OSPrioHighRdy ;
得到需要恢複的任務的堆棧指標 ;
堆棧指標 = OSTCBHighRdy->OSTCBStkPtr ;
將所有處理器寄存器從新任務的堆棧中恢複出來;
執行中斷返回指令;
}
對比兩個函數原型,除OSCtxSw()原型的程式清單比OSIntCtxSw( )原型的程式清單多了兩句外,其它都是一樣的。由異常處理代碼與C語言的介面程式可知,OS_TASK_SW()實質是軟體中斷的功能號0,在軟體中斷中已經把除變數OsEntersum外的所有寄存器儲存到管理員模式的堆棧中。而由程式可知,當程式執行函數 OSIntCtxsw()時,變數 OsEnterSum也沒有儲存到 IRQ模式的堆棧中。也就是說,兩種情況需要做的工作一樣,都可用同一段代碼實現。
OSIntCtxSw
;下面為儲存任務環境
LDR R2, [SP, #20] ;擷取PC (1)
LDR R12, [SP, #16] ;擷取R12 (2)
MRS R0, CPSR (3)
MSR CPSR_c, #(NoInt | SYS32Mode) (4)
MOV R1, LR (5)
STMFD SP!, {R1-R2} ;儲存LR,PC (6)
STMFD SP!, {R4-R12} ;儲存R4-R12 (7)
MSR CPSR_c, R0 (8)
LDMFD SP!, {R4-R7} ;擷取R0-R3 (9)
ADD SP, SP, #8 ;出棧R12,PC (10)
MSR CPSR_c, #(NoInt | SYS32Mode) (11)
STMFD SP!, {R4-R7} ;儲存R0-R3 (12)
LDR R1, =OsEnterSum ;擷取OsEnterSum (13)
LDR R2, [R1] (14)
STMFD SP!, {R2, R3} ;儲存CPSR,OsEnterSum (15)
;儲存當前任務堆棧指標到當前任務的TCB
LDR R1, =OSTCBCur (16)
LDR R1, [R1] (17)
STR SP, [R1] (18)
BL OSTaskSwHook ;調用子函數 (19)
LDR R4, =OSPrioCur (20)
LDR R5, =OSPrioHighRdy (21)
LDRB R6, [R5] (22)
STRB R6, [R4] (23)
LDR R6, =OSTCBHighRdy (24)
LDR R6, [R6] (25)
LDR R4, =OSTCBCur (26)
STR R6, [R4] (27)
OSIntCtxSw_1
;擷取新任務堆棧指標
LDR R4, [R6] (28)
ADD SP, R4, #68 (29)
LDR LR, [SP, #-8] (30)
MSR CPSR_c, #(NoInt | SVC32Mode) ;進入管理員模式(31)
MOV SP, R4 ;設定堆棧指標(32)
LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum (33)
;恢複新任務的OsEnterSum
LDR R3, =OsEnterSum (34)
STR R4, [R3] (35)
MSR SPSR_cxsf, R5 ;恢複CPSR (36)
LDMFD SP!, {R0-R12, LR, PC }^ ;運行新任務(37)
這部分代碼基本按照μC/OS-Ⅱ提供的函數原型編寫的,其中程式清單(1)—(18)部分與OSCtxSw()和OSIntCtxSw( )的原型是沒有對應語句的,寄存器應當儲存到任務的堆棧中,但為了節省CPU的時間和RAM的空間,僅在必要的時候才將寄存器儲存到任務堆棧。OSTCBCur->OSTCBStkPtr=SP也是在必要的時候才執行的。這部分正是在處理這兩件事情,其流程圖見圖4-4。
圖4-4 OSIntCtxSw部分代碼流程圖
由非強制中斷的彙編與C介面程式可知,在調用OS_TASK_SW( )(即軟體中斷的功能號0)時,寄存器R0—R3、R12、PC已經儲存到當前模式的堆棧中,任務的R4—R11、SP、LR沒有發生變化。也就是說寄存器已經儲存,但不是儲存到任務的堆棧中。同樣由異常處理代碼與C語言的介面程式可知,在調用OSIntCtxSw( )時,寄存器R0—R3、R12、PC已經儲存到當前模式的堆棧中,任務的R4—R11、SP、LR沒有發生變化。也就是說寄存器已經儲存,但不是儲存到任務的堆棧中。當前處理器模式的堆棧結構4-5所示。
圖4-5 當前處理器模式堆棧結構圖
在執行(1)—(18)這部分程式時的寄存器和儲存空間之間的具體關係4-6所示,圖中的標號為對應的程式段,這裡是以調用OS_TASK_SW( )為例子來說明,調用OSIntCtxSw( )的工作過程和調用OS_TASK_SW( ) 的工作過程是一樣的,只不過調用OS_TASK_SW( )時處理器當前模式為管理員模式,而調用OSIntCtxSw( )時處理器當前模式為IRQ模式。
圖4-6-1
此時處理器處於管理員模式,因為本移植是使用非強制中斷指令SWI使處理器進入管理員模式和ARM指令狀態,並使用功能0來實現OS_TASK_SW( )的功能。具體參見非強制中斷的彙編與C介面程式碼。
圖4-6-2
此時是通過執行程式段(4),利用MSR指令直接設定狀態寄存器CPSR的模式位,使處理器進入使用者/系統模式此時需要注意的是處理器的可見寄存器與管理員模式時是有區別的。在儲存之後同樣是通過執行程式段(8),利用MSR指令返回到管理員模式。
圖4-6-3
進入管理員模式以後繼續儲存資料,當資料儲存好以後,要注意調整當前模式堆棧指標因為當前模式的入棧比出棧的資料多,而在堆棧中剩餘的資料(R12、PC)已經沒有用處,所以要進行調整。完成指標調整的為程式段(10)。
圖4-6-4
此時又使處理器進入使用者/系統模式,繼續把未儲存的寄存器內容儲存到任務堆棧,這裡要注意的是R3儲存著任務的CPSR(既當前模式的SPSR),這部分在異常處理代碼與C語言的介面程式中完成的。至此,寄存器內容已經完全儲存到任務堆棧裡了。
從標號OSIntCtxSw_1處開始至程式的最後,是將所有處理器寄存器從新任務的堆棧中恢複出來。處理器執行這段代碼時處於ARM狀態,但使用者任務可能處於Thumb狀態。而由ARM的相關資料可知,程式不可以直接改變程式狀態寄存器的CPSR的T位來改變處理器的狀態。但“LDMFD SP!, {R0-R12, LR, PC }^ ” 異常返回指令是可以使用的(具體參見ARM的多寄存器載入指令),所以在恢複寄存器時,必須使處理器處於某種異常模式。由於FIQ模式和IRQ模式對應中斷,SP指標不能隨意改變,而未定義模式和中止模式以後可能會用到,所以本移植選擇為管理員模式。
在執行(28)—(37)這部分程式時的寄存器和儲存空間之間的具體關係4-7所示,圖中的標號為對應的程式段。
圖4-7-1
由於 OSTCBHirtRdy->OSTCBStkPtr儲存的是任務堆棧位置,而寄存器恢複後堆棧指標並不指向這裡,所以要調整新任務堆棧指標,這是由程式段(29)完成的。
圖4-7-2
然後通過執行程式段(31),利用MSR指令直接設定狀態寄存器CPSR的模式位,使處理器進入管理員模式,先恢複新任務OsEntersum,這是由程式段(34)(35)完成的,然後把新任務的 CPSR恢複到 SPSR這是由程式段(36)實現的。
圖4-7-3
最後通過中斷返回指令恢複R0—R12,把SPSR拷貝到CPSR(恢複使用者任務的處理器模式和指令集)和執行使用者任務(恢複PC指標),這是由程式段(37)實現的。這裡需要注意的是程式段(29)和(32)中的SP是不同的處理器寄存器分別為R13和R13_SVC。
§4.3.3.3 OSStartHighRdy的編寫
μC/OS-Ⅱ啟動多任務環境的函數叫做OSStart()。使用者在調用OSStart()之前,必須已經建立了一個或更多任務。OSStart()最終調用OSStartHighRdy()函數運行多任務啟動前優先順序最高的任務。由非強制中斷的彙編與C介面程式SoftwareInterrupt可知,這是調用非強制中斷的1號功能。這是因為ARM處理器核具有兩個指令集,在執行Thumb指令的狀態時不是所有寄存器都可見(參考ARM的相關資料),而且任務可能不在特權模式。為了相容任意一種模式,本移植使用非強制中斷指令SWI使處理器進入管理員模式和ARM指令狀態,並使用功能1實現OSStartHighRdy的功能。μC/OS-Ⅱ中調用函數 OSStartHighRdy()之前,OSTCBHighRdy指向的是優先順序最高的任務的任務控制塊。μC/OS-Ⅱ要求處於就緒狀態的任務的堆棧結構看起來就像剛發生過中斷,並將所有寄存器儲存到堆棧中的情形一樣。要想運行最高優先順序任務,使用者要做的是將所有處理器寄存器按順序從任務堆棧中恢複出來,並且執行中斷的返回。μC/OS-Ⅱ提供的OSStartHighRdy函數原型如下:
OSStartHighRdy原型的程式清單
void OSStartHighRdy (void)
{
調用使用者函數OSTaskSwHook();
OSRunning=True;
擷取堆棧指標SP=OSTCBHighRdy->OSTCBStkPtr:
從新任務堆棧中恢複所有寄存器;
執行中斷返回指令;
}
OSStartHighRdy的程式碼如下:
OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode)
;告訴uC/OS-II自身已經運行
LDR R4, =OSRunning
MOV R5, #1
STRB R5, [R4]
BL OSTaskSwHook ;調用子函數
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
B OSIntCtxSw_1
這部分代碼是比較嚴格的按照μC/OS-Ⅱ提供的原型編寫的,其中OSIntCtxSw_1的代碼在上一小節已經詳細介紹過。
這裡需要對“MSR CPSR_c, #(NoInt | SYS32Mode)”作以說明,在μC/OS -Ⅱ中,需要使用者提供周期性訊號源,用於實現時間延時和確認逾時。必須在多任務系統啟動以後再開啟時鐘節拍器,也就是在調用OSStart()之後。換句話說,在調用OSStart()之後做的第一件事是初始化定時器中斷。通常,容易犯的錯誤是將允許時鐘節拍器中斷放在系統初始化函數OSInit()之後,在調啟動多任務系統啟動函數OSStart()之前允許時鐘節拍中斷。μC/OS-Ⅱ的啟動多任務函數OSStart()會在最後調用OSStartHighRdy,而SStartHighRdy的第一條語句就允許中斷。這裡是將CPSR的第8位置1,允許IRQ中斷。
至此已經將μC/OS-Ⅱ移植到了LPC2106,下面可以進行瞬時電壓有效值檢測的程式的編寫。
此文轉自:http://hi.baidu.com/goby2004/blog/item/352b7d13dbac49c4c3fd78f3.html