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