ucos系統的精華提煉
第一節:程序代碼運行條件
回想一下:
1.一個(gè)鏈接過(guò)的程序由以下組成:代碼段,只讀數據段,可讀寫(xiě)數據段。
2.單片機上常使用的兩個(gè)資源:Flash(只讀),RAM(可讀寫(xiě))
3.對于單片機,我們習慣于一種模式,代碼段和只讀數據放在FLASH上,可讀寫(xiě)數據放在RAM的起始地址,棧從RAM中最高地址向下開(kāi)始運行。
4.處理器從代碼段提取代碼,有三種方式,順序,跳轉,調用。所有代碼段必須放在正確的位置上。
5.處理器上數據段是通過(guò)地址來(lái)處理數據,所以數據也必須放在正確的位置上。
6.處理上臨時(shí)變量通過(guò)棧指針的向下偏移來(lái)提取變量,所以臨時(shí)變量的地址是不固定的。
7.至于堆,那是C語(yǔ)言的技巧,不列入條件范圍內。
總結:1.程序運行需要代碼段,數據段和棧區,而代碼段和數據段都必須放在編輯時(shí)對應的地址上,只有棧區是可以設置的。那么在不使用臨時(shí)變量地址作為計算因數的情況下,就算改變棧頂的位置,程序的運行結果相同。
第二節:多程序運行原理
多線(xiàn)程的原理其實(shí)很簡(jiǎn)單,系統為每個(gè)線(xiàn)程提供一片內存作為棧區。然后選取FLASH中一點(diǎn)作為線(xiàn)程代碼的起始地址,最后調轉到線(xiàn)程代碼的起始地址開(kāi)始執行。線(xiàn)程代碼可以隨意操作被分配的棧中的臨時(shí)變量而不會(huì )干擾到任何其他線(xiàn)程。除非你的臨時(shí)變量過(guò)大超過(guò)了分配的棧區,這個(gè)就要你使用線(xiàn)程的經(jīng)驗和感覺(jué)有關(guān)了,一般人都不會(huì )去仔細的計算使用多大臨時(shí)變量空間。線(xiàn)程也有個(gè)缺點(diǎn),就是線(xiàn)程在訪(fǎng)問(wèn)棧區以外的地址時(shí),包括數據區,都會(huì )存在這樣一種可能,多個(gè)線(xiàn)程同時(shí)讀取和修改一個(gè)數據區中的數據時(shí),就會(huì )發(fā)生邊界現象,即多線(xiàn)程共管的數據。當然同時(shí)是相對的,相對我們的感覺(jué),現在舉例說(shuō)明:A線(xiàn)程在從Addr地址處讀取出數據data后,恰巧被另外一線(xiàn)程B交接,而B(niǎo)線(xiàn)程也讀取了Addr地址處的data數據并修改了data的一位,然后...,當再次運行到A線(xiàn)程時(shí),A線(xiàn)程也修改了data并寫(xiě)回到Addr地址處,這樣就存在了一個(gè)bug,B線(xiàn)程修改的位就被A線(xiàn)程的寫(xiě)回覆蓋了。這點(diǎn)其實(shí)我們不擔心,因為系統一般都會(huì )提供很多避免這種現象的機制。
如果你對多線(xiàn)程還是不了解的話(huà),我估計你應該就是對棧的認識不夠準確,可參考相關(guān)資料。
第三節:ucos系統介紹
關(guān)于ucos的廣告部分我已經(jīng)屏蔽,我直接進(jìn)入正題,ucos是一個(gè)搶占式系統,搶占式是指任務(wù)以搶占式的方式來(lái)運行。把Ucos中的任務(wù)當成進(jìn)程來(lái)理解是不恰當的,這會(huì )影響我們對Windows進(jìn)程和Linux進(jìn)程的理解。Ucos中的任務(wù)只能相當于線(xiàn)程的角色。Ucos內容包括兩大部分,一個(gè)是系統部分:包括任務(wù)操作,時(shí)間操作,事件操作,內存操作,這些不隨處理器的不同而不同。另外一個(gè)是接口部分:由匯編和C語(yǔ)言組成。提供任務(wù)切換函數,定時(shí)器接口,CPU寄存器保存和讀取,及中斷處理等硬件相關(guān)操作函數。
第四節:創(chuàng )建ucos任務(wù)
使用下面函數創(chuàng )建一個(gè)任務(wù):
INT8UOSTaskCreate(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT8Uprio);
創(chuàng )建函數設置任務(wù)函數(任務(wù)代碼首地址)和任務(wù)參數,分配棧頂(ptos),和優(yōu)先級。Ptos的傳入做法在可讀寫(xiě)數據區分配一個(gè)數據OS_STKStk【size】.然后把stk最高地址傳送給ptos。而stk數組就是默認的分配給任務(wù)的棧區,任務(wù)task運行后使用stk存儲臨時(shí)變量。
另外,stk還有個(gè)縮水就是系統需要從stk最高位減去一部分空間用來(lái)存儲寄存器信息,對于A(yíng)RM是16個(gè)unsignedlong長(cháng)度,用來(lái)存儲該任務(wù)的r0-r15,CPSR.
系統也會(huì )為每一個(gè)創(chuàng )建的任務(wù)分配一個(gè)任務(wù)控制塊(TCB)。TCB管理者任務(wù)的狀態(tài)和信息。另外還有一個(gè)TCB指針指向當前正在運行的任務(wù)。TCB控制著(zhù)當前進(jìn)程是否在運行,如果不是在運行是否是因為事件阻塞,當任務(wù)運行時(shí),從什么地方找到上次運行時(shí)保存的信息等等。
對于每個(gè)創(chuàng )建后或運行的任務(wù),都有兩個(gè)重要的部分,一個(gè)是TCB,一個(gè)是棧頭(棧區頂上保留的空間)。任務(wù)開(kāi)始調度的第一步就是找到該任務(wù)的TCB,然后從TCB中找到棧頭地址,然后使用棧頭保存的數據復制到CPU寄存器上和CPSR上,最后跳轉到棧頭上上次運行保存的地址處開(kāi)始執行。當運行的任務(wù)被調度時(shí),一樣是首先找到TCB所指向的棧頭,然后把CPU所有寄存器內容和CPSR及當前地址全部復制過(guò)去,再去找到另外一個(gè)被任務(wù)是應該運行的進(jìn)程,然后調度那個(gè)進(jìn)程。
多任務(wù)的背景來(lái)自一點(diǎn),其實(shí)我們寫(xiě)的大部分程序其實(shí)都有太多的延遲,對于沒(méi)有系統的程序,真正執行效率(即不做無(wú)效循環(huán))的時(shí)間可能只占到處理器運行的5%都不到。插入一句,如果你善于處理器編程的話(huà),你看代碼不應該只看到代碼的長(cháng)度,而是這段代碼運行占用了多長(cháng)時(shí)間,和占用哪些資源和多大空間。系統的引入會(huì )讓我們重新認識任務(wù)運行時(shí)間,我們不希望程序長(cháng)時(shí)間做無(wú)效循環(huán),我們要利用這段時(shí)間去做其他的事,從而提高處理器的效率。所以不讓認為系統會(huì )占用你的資源,系統會(huì )幫助你努力收回那95%以上效率。實(shí)際上收回全部資源是不可能的。這就看你如何使用架構,和你的任務(wù)級別了。每個(gè)項目可能都會(huì )不同。
第四節:搶占式調度(ucos的經(jīng)典)
調度的意思就是從所有的任務(wù)隊列中找到最應該運行的任務(wù),然后運行該任務(wù)。而調度的方式?jīng)Q定了系統的性能。Ucos的經(jīng)典就來(lái)自于它只用了一個(gè)數組采取了最單純的行為來(lái)進(jìn)行任務(wù)調度,同時(shí)也占用了最小的資源。所以,即使是8位處理器,很多人也會(huì )使用ucos系統。
上面說(shuō)過(guò),ucos是搶占式調度。搶占式調度的概念就是,只有一個(gè)CPU,所有線(xiàn)程以搶占的方式占有CPU,然后運行任務(wù),除非他主動(dòng)讓出,或他被其他任務(wù)搶占,否則,他會(huì )一直占用CPU.UCOS的搶占方式是比較優(yōu)先級,每個(gè)任務(wù)都需要分配一個(gè)且唯一的優(yōu)先級。每次調度就是比較所有任務(wù)的優(yōu)先級,找到優(yōu)先級最高的任務(wù)(這點(diǎn)其實(shí)不復雜,下段介紹),然后調度該任務(wù)并運行,最高優(yōu)先級的任務(wù)需要自己主動(dòng)退出,否則,永遠是這一個(gè)在運行。當這個(gè)任務(wù)運行到延遲或等待事件時(shí),系統函數就會(huì )把這個(gè)任務(wù)從運行隊列屏蔽掉,然后重新調度,再次搜索最高優(yōu)先級的任務(wù),這樣就找到了另外一個(gè)優(yōu)先級的任務(wù),然后運行該任務(wù),到這個(gè)任務(wù)睡眠或等待事件時(shí),也會(huì )睡眠,然后再次調度,這時(shí),如果前面睡眠的最高優(yōu)先級的任務(wù)被喚醒,那么他將也會(huì )被放到優(yōu)先級隊列中。否則,再進(jìn)入下一個(gè)優(yōu)先級。
關(guān)于任務(wù)的隊列,睡眠,運行等概念都是一種理解概念,實(shí)際上ucos在這點(diǎn)是很簡(jiǎn)單的,也是很經(jīng)典的?,F在說(shuō)明下ucos的調度隊列。首先,我們需要知道下面個(gè)數組是干什么用的。
INT8UconstOSUnMapTbl[256]={
0,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
6,0,1,0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
隨意給一個(gè)8位數data,這個(gè)數組的作用就是以查表的方式最快的速度找到data數據中從低位到高位中為第一個(gè)為1的數的位置(bit0為0)。即代替下面的函數的作用。使用方法:prto= OSUnMapTbl[data]
for (i=0; i<8; i++)
{
}
理解了那個(gè)數組后我們再引入兩個(gè)變量,
OS_EXTINT8U OSRdyGrp;
OS_EXTINT8U OSRdyTbl[OS_RDY_TBL_SIZE];
每個(gè)任務(wù)的優(yōu)先級對應于OSRdyTbl數組中的一個(gè)位。對應關(guān)系是OSRdyTbl[prio/8]中的的第(prio%8)位,(prio/8)和(prio%8)使用任務(wù)控制塊TCB中的->OSTCBX和->OSTCBY表示。將OSRdyTbl數組中任務(wù)對應的位置置一表示該任務(wù)準備妥當,可參加搶占運行, OSRdyTbl數組中任務(wù)對應的位置為0表示該任務(wù)不存在,或該任務(wù)當前被阻塞無(wú)法運行。OSRdyGrp變量的作用是使用8個(gè)位依次對應OSRdyTbl數組的前8個(gè)字節,對第n位為0,代表 OSRdyTbl[n]全部為0,如果第n位為1,代表對應的OSRdyTbl[n]至少有一個(gè)為1;
然后調度工具就開(kāi)始使用下面機制來(lái)得到最高優(yōu)先級的任務(wù),即優(yōu)先級號最低的那個(gè)任務(wù)
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
第一行代碼,通過(guò)查表找到OSRdyTbl數組中不為0的最低的一個(gè)數組。然后通過(guò)第二條代碼的OSUnMapTbl[OSRdyTbl[y]]);找到對應的OSRdyTbl[y]中的最低的一個(gè)是1的位置。然后與(y << 3)相加,就得到了OSRdyTbl數組中從低到高的最低的為1的1位的位置。即最高優(yōu)先級任務(wù)的優(yōu)先級,然后根據優(yōu)先級找到對應的TCB進(jìn)行調度。
每次調度時(shí)都是關(guān)閉了前一個(gè)進(jìn)程,因此ucos需要隊列中至少有一個(gè)可運行的程序,為此UCOS制作了一個(gè)IDLE任務(wù),這個(gè)任務(wù)優(yōu)先級最低,在最高位,目的是所有任務(wù)進(jìn)程都睡眠時(shí),讓系統仍然有任務(wù)可調,不至于崩潰。另外一個(gè)用作是統計CPU使用率。如果IDLE任務(wù)從沒(méi)調用過(guò),那就說(shuō)明的任務(wù)搶占度過(guò)高,優(yōu)先級高的任務(wù)有需要釋放一些空間讓優(yōu)先級低的任務(wù)運行。
第五節:UCOS的實(shí)時(shí)性能
按我理解,UCOS的實(shí)時(shí)性能是一種設想,讓所有的任務(wù)等處于等待信號階段,當有中斷觸發(fā)時(shí),執行中斷處理函數,通過(guò)信號喚醒進(jìn)程,來(lái)完成任務(wù),完成后可以繼續睡眠。即使有多個(gè)中斷響應,只要中斷函數能及時(shí)響應,那么任務(wù)就排著(zhù)隊來(lái)完成后續工作。這就是我的UCOS設想。
所以,我們需要在兩個(gè)地方調度,一個(gè)是中斷,每次進(jìn)入中斷后關(guān)閉調度,但是允許信號喚醒任務(wù),然后在最后一個(gè)中斷嵌套完成后退出時(shí)進(jìn)行調度,檢測有沒(méi)有被喚醒的可執行任務(wù)。另外一個(gè)就是定時(shí)中斷。每次tick完成后都進(jìn)行一次重調度。目的是當低優(yōu)先級執行時(shí)沒(méi)有釋放資源,而高優(yōu)先級的任務(wù)已被喚醒。特別是IDLE任務(wù),除非你問(wèn)它要,否則他不會(huì )給你釋放資源的。
第六節:事件處理
UCOS的事件主要包括SEMAPHORE,Mutex,和Mbox,Q,使用起來(lái)都很簡(jiǎn)單,一般都只適用三個(gè)函數創(chuàng )建,掛起等待,釋放。
SEMAPHORE作用是當某個(gè)任務(wù)運行到必須得到某種資源時(shí)進(jìn)行掛起等待資源滿(mǎn)足,其他的任務(wù)或中斷發(fā)送SEMAPHORE表示資源已經(jīng)建立,你可以運行了,如果是任務(wù)在發(fā)送SEMAPHORE時(shí)發(fā)現有任務(wù)因此被掛起,會(huì )喚醒并調度到該任務(wù)上執行。
Mutex有一種鎖的概念,當得到一個(gè)東西后,就立馬對其上鎖,其他的任務(wù)就只能等待該任務(wù)完成后打開(kāi)鎖才能運行。這里有一個(gè)問(wèn)題就是一旦低優(yōu)先級的任務(wù)占用鎖后而高優(yōu)先級的就必須等待,而恰巧低優(yōu)先級的又被中優(yōu)先級的任務(wù)搶去執行就會(huì )發(fā)生,高優(yōu)先級等低優(yōu)先級,低優(yōu)先級等中優(yōu)先級的現象稱(chēng)為優(yōu)先級翻轉,所有Mutex有一個(gè)機制就是高優(yōu)先級想得到鎖的話(huà)就臨時(shí)提高低優(yōu)先級的優(yōu)先級,使低優(yōu)先級盡快完成完成釋放鎖。
郵箱MBox基本和SEMPAPHORE相同,只是SEMPAPHORE被當做一個(gè)信號標志來(lái)傳送,Mbox也可以被當做SEMPAPHORE使用,但是會(huì )返回一個(gè)地址指針。
Q消息隊列沒(méi)有用過(guò),看樣子是首先初始化一個(gè)數組,然后對數組使用FIFO的方式發(fā)送和接受信件。
我一般還會(huì )再加上一些原子讀寫(xiě)函數atom_read/wirte,主要針對邊界變量。其實(shí)很簡(jiǎn)單就是讀取前關(guān)中斷,讀取后開(kāi)中斷而已。
第七節:tick
Tick是系統時(shí)間,他和定時(shí)的概念是不同的,如OSTimeDly (OS_TICKS_PER_SEC/100),實(shí)際上不是嚴格的延遲了OS_TICKS_PER_SEC/100秒,存在0-1/OS_TICKS_PER_SEC之間的誤差。Tick相當于鐘表在不停的跑,秒表變化的瞬間被稱(chēng)為tick,而我們是不可能從tick那一瞬間開(kāi)始計時(shí)的。所以這是一個(gè)概念是要分清的。
第八節:ucos的缺陷
UCOS畢竟是一個(gè)小系統,甚至可以在8位處理器上運行,所以對于我們完成更復雜的任務(wù)和對系統效率更高的要求的話(huà),它是存在一定的局限性的。如:
1. 系統和應用,中斷等關(guān)系密切,開(kāi)發(fā)人員需要熟悉系統特性,如。任務(wù)被創(chuàng )建后是不能直接退出的,必須使用API函數銷(xiāo)毀它。
2. 調度方式過(guò)于單一,任務(wù)較少時(shí)可以達到平衡,任務(wù)較多時(shí),高優(yōu)先級的和低優(yōu)先級的運行時(shí)間就會(huì )存在嚴重不平衡,并且會(huì )增加考慮調度問(wèn)題。
3. 缺少異步讀取機制,如我想向串口發(fā)送數據,而此時(shí)串口緩存已滿(mǎn),我們就需要放棄資源調度其他任務(wù)。串口可以通過(guò)多開(kāi)緩存來(lái)彌補,但是對于TCP,退出就需要至少等待下一個(gè)Tick,時(shí)間就顯得有些長(cháng)久了,這個(gè)機制其實(shí)我一直在考慮?!?/p>
第九節:寫(xiě)后
不喜歡LPC21xx和周立功的UCOS系統還有個(gè)原因就是LPC21xx的中斷機制看起來(lái)不錯,但實(shí)際上已經(jīng)能夠影響了我們代碼的發(fā)揮。也可能我自己懶惰的原因,沒(méi)有來(lái)及在LPC上改造ucos。周立功的中斷函數使用__irq聲明,這一點(diǎn)已經(jīng)和上面第五節所說(shuō)內容想違背。
兩外,周立功的關(guān)中斷函數和開(kāi)中斷函數使用swi中斷,我覺(jué)得是不如原版的較好。原版的函數是保存寄存器關(guān)中斷函數和恢復寄存器內容。我本來(lái)考慮著(zhù)周立功可能是考慮軟中斷可直接進(jìn)入中斷來(lái)避免中斷干擾,而原版的在關(guān)中斷函數中間仍有可能被中斷,如下
MRSR0, CPSR;//復制CPSR,執行后可能被中斷
ORR R1, R0, #0xC0;//計算,也有可能被中斷
MSRCPSR_c, R1;//這個(gè)代碼完成才真正關(guān)閉中斷
但后來(lái)相通之后,覺(jué)得周立功是多此一舉,即使關(guān)中斷前被中斷也沒(méi)有什么的,因為中斷后它會(huì )原模原樣的返回給你。還是不喜歡周立功的UCOS和LPC
后來(lái)在三星的s3c2440上也架構了一個(gè)ucos,并且搭配了TFTP傳輸和TCP對話(huà),感覺(jué)用起來(lái)要比LPC的好用很多。
當然,這只是個(gè)人用法和感覺(jué),每個(gè)芯片只要寫(xiě)好了軟件應該也是不錯的。下面稍微提下個(gè)人用法,我一般如下定義main函數
int main(void)
{
OSInit();
OSTaskCreate(MainTask,(void *)1,&MainTaskStk[MainTaskStkLengh-1], MainTaskPrio);
OSStart();
return 0;
}
直接創(chuàng )建一個(gè)MainTask任務(wù),然后在MainTask中進(jìn)行初始化硬件和創(chuàng )建任務(wù),事件
void MainTask(void *pdata)
{
}
評論