王宜結(jié)
(淮南師范學(xué)院 電氣信息工程學(xué)院,安徽 淮南 232038)
隨著應(yīng)用系統(tǒng)復(fù)雜程度的不斷提高,程序編制也變得越來(lái)越難控制。解決復(fù)雜問(wèn)題的最好辦法就把它分解成一個(gè)個(gè)相對(duì)簡(jiǎn)單的問(wèn)題,即一個(gè)個(gè)單獨(dú)的任務(wù),分而治之。UC/OS-II是一個(gè)實(shí)時(shí)多任務(wù)操作系統(tǒng),因其短小精悍又源代碼開(kāi)放,在一些小型系統(tǒng)中得到了較廣泛的應(yīng)用。在MCU上加載uC/OS-II操作系統(tǒng),再對(duì)每個(gè)問(wèn)題編寫相應(yīng)的任務(wù)代碼,就可以實(shí)現(xiàn)復(fù)雜的控制和應(yīng)用。
uC/OS-II是基于優(yōu)先級(jí)的搶占式實(shí)時(shí)多任務(wù)操作系統(tǒng),最多可管理64個(gè)任務(wù),可固化,可剪裁,具體高穩(wěn)定性和可靠性。它包含了實(shí)時(shí)內(nèi)核、任務(wù)管理 、時(shí)間管理、任務(wù)間通信同步(信號(hào)量 ,郵箱,消息隊(duì)列)和內(nèi)存管理等功能。絕大部分代碼用C語(yǔ)言寫成,與硬件相關(guān)部分用匯編語(yǔ)言編寫,最鮮明的特點(diǎn)是源代碼是公開(kāi)免費(fèi)的,便于移植和維護(hù)。uC/OS-II是面向中小型嵌入式系統(tǒng)的,包含全部功能模塊的內(nèi)核大約為10KB,如果經(jīng)過(guò)裁減只保留核心代碼,則可壓縮到3KB左右。RAM的占用量與系統(tǒng)中的任務(wù)數(shù)及堆??臻g大小有關(guān),堆棧的大小取決于任務(wù)的局部變量、緩沖區(qū)大小及可能的中斷嵌套層數(shù)。應(yīng)用程序的時(shí)間精度由系統(tǒng)時(shí)鐘節(jié)拍決定,uC/OS-II需要用戶提供周期性的時(shí)鐘信號(hào)源,用于實(shí)現(xiàn)時(shí)間延時(shí)和確認(rèn)超時(shí),一般時(shí)鐘節(jié)拍在10到100Hz之間,因?yàn)閡C/OS-II在每一個(gè)節(jié)拍都要檢查有沒(méi)有更高優(yōu)先級(jí)的就緒任務(wù)在等待執(zhí)行 ,若有 ,就要進(jìn)行任務(wù)切換。所以時(shí)鐘節(jié)拍率越高,系統(tǒng)的額外負(fù)荷就越重。
移植就是要修改與處理器有關(guān)部分的代碼,也就是要修改以下三個(gè)文件:OS_CPU.H、OS_CPU.C及OS_CPU_A.ASM。其中OS_CPU.H文件包括了用#define語(yǔ)句定義的與處理器相關(guān)的常數(shù)、宏以及數(shù)據(jù)類型。對(duì)于Cortex-M3,用于開(kāi)中斷和關(guān)中斷的兩個(gè)宏可定義如下:
#define OS_ENTER_CRITICAL() {cpu_sr=OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL()
{OS_CPU_SR_Restore(cpu_sr);}
這兩個(gè)宏用匯編代碼實(shí)現(xiàn)如下:
OS_CPU_SR_Save;這個(gè)函數(shù)用于關(guān)中斷
MRS R0,PRIMASK
CPSID I;關(guān)閉除硬fault以處的全部可屏蔽中斷
BX LR;函數(shù)返回
OS_CPU_SR_Restore;這個(gè)函數(shù)用于開(kāi)中斷
MSR PRIMASK,R0;回到關(guān)中斷之前的狀態(tài)
BX LR;函數(shù)返回
uC/OS-II用宏“OS_STK_GROWTH”來(lái)設(shè)置堆棧的增長(zhǎng)方向,值為0時(shí)表示堆棧從低地址向高地址增長(zhǎng),值為1則相反。由于Cortex-M3內(nèi)核的堆棧是向下生長(zhǎng)的滿棧,故應(yīng)把宏定義成“#define OS_STK_GROWTH 1”。定義數(shù)據(jù)類型宏比較簡(jiǎn)單,這里就不介紹了。
在OS_CPU.C文件中要求我們必須編寫10個(gè)簡(jiǎn)單的C函數(shù),它們是:
OSTaskStkInit();OSInitHookBegin();OSInitHook-End();OSTaskCreateHook();OSTaskDelHook();OSTask-IdleHook();OSTaskStatHook();OSTaskSwHook();OSTCBInitHook();OSTimeTickHook()。在這10個(gè)函數(shù)中唯一必要的是OsTaskStklnt()函數(shù)。其他9個(gè)函數(shù)是為了擴(kuò)展用戶功能而定義的鉤子函數(shù),這些鉤子函數(shù)必須聲明,但可以都為空函數(shù),也可以加上一些用戶需要的擴(kuò)展功能。OsTaskStklnt()被任務(wù)創(chuàng)建函數(shù)OSTaskCreate()或OSTaskCreateEXT()調(diào)用,用來(lái)初始化任務(wù)的堆棧,任務(wù)堆棧通常用數(shù)組來(lái)定義。OsTaskStklnt()函數(shù)首先將用戶為任務(wù)分配的堆棧棧底地址賦值給一個(gè)堆棧型指針變量,然后再通過(guò)這個(gè)堆棧指針向任務(wù)的??臻g寫入初值。初始化后任務(wù)堆棧圖1所示(堆棧空間大小為SIZE),其中task為要?jiǎng)?chuàng)建的任務(wù)代碼首地址,其他15個(gè)寄存器的值可為任意值,通常初始化為0。OsTaskStklnt()返回任務(wù)堆棧指針,這個(gè)指針在任務(wù)創(chuàng)建函數(shù)調(diào)用任務(wù)控制塊初始化函數(shù)OS_TCBInit()后存入任務(wù)控制塊中。
圖1 初始化后的堆棧
OS_CPU_A.ASM文件:在此文件中需改寫 4個(gè)簡(jiǎn)單匯編語(yǔ)言函數(shù):OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()、OSTickISR()。
下面詳細(xì)分析這幾個(gè)函數(shù)的實(shí)現(xiàn)過(guò)程:
OSStartHighRdy():這個(gè)函數(shù)被 OSStart()調(diào)用,并且只執(zhí)行一次,它的主要功能是觸發(fā)PendSV異常,PendSV異常的核心工作是任務(wù)的切換,對(duì)Cortex-M3內(nèi)核,其代碼可以寫成:
PendSV_Handler
CPSID I;關(guān)中斷
MRS R0,PSP;當(dāng)前堆棧指針?biāo)徒oR0,首次運(yùn)行任務(wù)時(shí),PSP此前被置為0了。
CBZ R0,OSPendSV_nosave;首次運(yùn)行任務(wù)時(shí),不保存運(yùn)行環(huán)境
SUBS R0,R0,#0x20;保存R4-R11到當(dāng)前任務(wù)的堆棧
STM R0,{R4-R11};R0為當(dāng)前任務(wù)的堆棧指針,要把它存到當(dāng)前任務(wù)控制塊中去
LDR R1,=OS_TCBCur;OSTCBCur-〉OSTCBStkPtr〈=當(dāng)前任務(wù)SP;
LDR R1,[R1];任務(wù)控制塊的第一個(gè)單元存放的是堆棧指針
STR R0,[R1];R0 isSP ofprocessbeing switched out;
至此,當(dāng)前任務(wù)的上下文都保存起來(lái)了OSPendSV_nosave
PUSH {R14};需要保護(hù) LR exc_return值
LDR R0,__OS_TaskSwHook;調(diào) 用 OSTaskSwHook();
BLX R0
POP {R14}
LDR R0,__OS_PrioCur;OSPrioCur=OSPrio-HighRdy;
LDR R1,__OS_PrioHighRdy
LDRB R2,[R1]
STRB R2,[R0]
LDR R0,__OS_TCBCur;OSTCBCur =OSTCBHighRdy;
LDR R1,__OS_TCBHighRdy
LDR R2,[R1]
STR R2,[R0]
LDR R0,[R2];R0是新的進(jìn)程堆棧指針SP:SP 〈=OSTCBHighRdy-〉OSTCBStkPtr;
LDM R0,{R4-R11};從待運(yùn)行任務(wù)的堆棧中恢復(fù)R4-R11
ADDS R0,R0,#0x20
MSR PSP,R0;PSP〈=新的進(jìn)程堆棧指針SP
ORR LR,LR,#0x04;確保異常返回后使用進(jìn)程堆棧
CPSIE I;開(kāi)中斷
BX LR;異常返回后將恢復(fù)待運(yùn)行任務(wù)的上下文
當(dāng)主函數(shù)調(diào)用OS_START()首次運(yùn)行多任務(wù)時(shí),先查找優(yōu)先級(jí)最高的就緒任務(wù),然后調(diào)用OS-StartRdy()觸發(fā)PendSV異常(剛進(jìn)入異常時(shí),硬件自動(dòng)保存8個(gè)寄存器,使用的是原來(lái)的堆棧指針),然后取中斷向量進(jìn)入中斷服務(wù),再將堆棧指針切換為主堆棧指針MSP。PendSV異常服務(wù)程序中,首先判斷是不是第一次運(yùn)行任務(wù),如果是,則不需要保存任務(wù)的運(yùn)行環(huán)境(因?yàn)樯袩o(wú)任務(wù)在運(yùn)行),而只要恢復(fù)待運(yùn)行任務(wù)的運(yùn)行環(huán)境,即把任務(wù)堆棧中的16個(gè)寄存器(如圖1)的值恢復(fù)到相應(yīng)的寄存器中(這時(shí)這個(gè)待運(yùn)行任務(wù)的堆棧指針又指向棧底),這其中包含了待運(yùn)行任務(wù)代碼的入口地址,它會(huì)被恢復(fù)到程序計(jì)數(shù)器PC中,PC得到待運(yùn)行任務(wù)入口地址后就開(kāi)始運(yùn)行該任務(wù)。
當(dāng)前運(yùn)行的任務(wù)可能調(diào)用void OSTimeDly(INT16U ticks)函數(shù)來(lái)主動(dòng)延時(shí),這個(gè)函數(shù)會(huì)使任務(wù)延時(shí)ticks個(gè)時(shí)鐘節(jié)拍,并通過(guò)調(diào)用OS_Sched();函數(shù)引發(fā)一次任務(wù)調(diào)度。通常把這種情況下的調(diào)度稱作任務(wù)級(jí)調(diào)度,另一種調(diào)度是在中斷返回時(shí)進(jìn)行的,叫做中斷級(jí)調(diào)度。OS_Sched()函數(shù)的功能是先查找任務(wù)就緒表中優(yōu)先級(jí)最高的就緒任務(wù),然后調(diào)用OS_TASK_SW()函數(shù)(這個(gè)函數(shù)在os_cpu.h中用宏定義成:“#define OS_TASK_SW()OSCtxSw()”)觸發(fā)PendSV異常完成一次任務(wù)切換。PendSV異常從第二次被調(diào)用開(kāi)始,就要先保存當(dāng)前正在運(yùn)行任務(wù)的運(yùn)行環(huán)境 (即壓入R4-R11共8個(gè)寄存器,另外8個(gè)寄存器(PSR,PC,LR,R12,R3-R0)在進(jìn)入PendSV異常時(shí)由硬件自動(dòng)壓入待切換任務(wù)的堆棧保護(hù))。PendSV異常最后4行的功能是:把待運(yùn)行任務(wù)的堆棧指針賦給進(jìn)程堆棧指針PSP,然后調(diào)整LR的值,確保返回后使用進(jìn)程堆棧。由于LR在出入ISR的時(shí)候,其值得到了重新的詮釋,這種特殊的值稱為“EXC_RETURN”,EXC_RETURN 的D0位對(duì)于Cortex-M3核必須為1(D0=1表示返回Thumb狀態(tài)),D1位保留,D2位非常重要:當(dāng)D2=0時(shí),從主堆棧中做出棧操作,返回后使用MSP;當(dāng)D2=1時(shí),從進(jìn)程堆棧中做出棧操作,返回后使用進(jìn)程堆棧。所以指令 “ORR LR,LR,#0x04”的功能就是控制從PendSV異常返回時(shí),待運(yùn)行任務(wù)保存在堆棧中的“PSR,PC,LR,R3-R0”這 8 個(gè)寄存器能正確地彈出到對(duì)應(yīng)的寄存器中,從而實(shí)現(xiàn)恢復(fù)現(xiàn)場(chǎng)的目的。EXC_RETURN的D3位的功能是控制返回后進(jìn)入Handler模式(=0時(shí))還是進(jìn)入線程模式(=1時(shí))。這里不修改此值是為了返回時(shí)仍然進(jìn)入原來(lái)的模式,31:4位必須全為1。
OSIntCtxSw()函數(shù)是中斷級(jí)任務(wù)切換函數(shù),即在中斷返回時(shí)通過(guò)調(diào)用該函數(shù)觸發(fā)PendSV異常實(shí)現(xiàn)任務(wù)切換,功能與OSCtxSw()幾乎一樣。
OSTickISR()函數(shù)即節(jié)拍中斷服務(wù),其代碼如下:
{OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();//保存全局中斷標(biāo)志,關(guān)總中斷,通知uC/OS-II正在開(kāi)始進(jìn)行ISR服務(wù)
OSIntNesting++;//統(tǒng)計(jì)中斷嵌套次數(shù)
OS_EXIT_CRITICAL();//恢復(fù)全局中斷標(biāo)志
OSTimeTick();/*在os_core.c文件里定義,主要判斷延時(shí)的任務(wù)是否計(jì)時(shí)到,或正在等待事件的任務(wù)是否超時(shí)。將到時(shí)或超時(shí)的任務(wù)由原來(lái)的掛起狀態(tài)置為就緒狀態(tài)*/
OSIntExit();/*在os_core.c文件里定義,如果有更高優(yōu)先級(jí)的任務(wù)就緒了,則執(zhí)行一次任務(wù)切換。*/}
本文詳細(xì)分析了uC/OS的移植過(guò)程。因?yàn)椴煌幚砥髯珠L(zhǎng)、使用的開(kāi)關(guān)中斷指令、堆棧組織方式、寄存器的數(shù)量、進(jìn)出中斷時(shí)的具體行為不同,所以要針對(duì)具體使用的處理器來(lái)寫這部分代碼,這就是移植的本質(zhì)。本文著重分析了任務(wù)切換的詳細(xì)過(guò)程以及與之關(guān)聯(lián)的部分,這也是移植過(guò)程的難點(diǎn)所在。實(shí)踐表明,上述方法移植后的操作系統(tǒng)工作正常,能長(zhǎng)時(shí)間穩(wěn)定運(yùn)行。
[1]陳啟軍.嵌入式系統(tǒng)及其應(yīng)用[M].上海:同濟(jì)大學(xué)出版社,2011
[2][美]Jean J.Labrosse.嵌入式實(shí)時(shí)操作系統(tǒng)UC/OS-II(第2版)[M].邵貝貝等譯.北京:北京航空航天大學(xué)出版社,2003