久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> MCU設(shè)計(jì)實(shí)例 >> 瀏覽文章

DIY:給單片機(jī)寫個(gè)實(shí)時(shí)操作系統(tǒng)內(nèi)核!

作者:佚名   來源:本站原創(chuàng)   點(diǎn)擊數(shù):  更新時(shí)間:2012年11月06日   【字體:
  為了進(jìn)一步把單片機(jī)的潛能發(fā)揮到極限,我一直想寫個(gè)程序把單片機(jī)的所有資源都用光,但是如果依照單道程序順序執(zhí)行的方式,很難把MCU的CPU時(shí)間都充分利用,比如使用軟件延時(shí)函數(shù)實(shí)際上就是在無謂地消耗著CPU的時(shí)間什么事情都不做,因?yàn)镃PU一直在循環(huán)等待著條件結(jié)束,這相當(dāng)于函數(shù)被阻塞了。

 
為了更明顯地驗(yàn)證這一點(diǎn),你可以在WINDOWS下打開VC6.0或其他的C語言編譯器,寫段代碼如下:

 
#include <stdio.h>
void main(void) 
{while(1) ;}

 
意思是讓CPU不做事情在等待,你猜,這句代碼會(huì)消耗掉多少CPU時(shí)間?

 
答案會(huì)根據(jù)不同機(jī)型而不同,如果是單核CPU的話,這句話會(huì)消耗掉CPU接近100%的時(shí)間!如果是雙核CPU,則只消耗掉50%左右,因?yàn)檫@段代碼只運(yùn)行在其中一個(gè)核,另外一個(gè)核還可以做別的事情,截圖如下:


 

 然后你可以測試下面這幾句代碼:

#include <stdio.h>

#include <windows.h>

void main(void)
{while(1)
Sleep(100);
}

 
這段代碼實(shí)際上也是什么都不做,它不斷地調(diào)用Sleep()函數(shù),讓它延時(shí)100毫秒再醒來,然后繼續(xù)睡覺。現(xiàn)在你可以再打開任務(wù)管理器看一下CPU時(shí)間用了多少,答案是基本不用CPU時(shí)間!!

 
為什么同樣地什么事情都不做,差別咋就這么大呢?這是因?yàn)槭褂昧薙leep()這個(gè)函數(shù)是WINDOWS操作系統(tǒng)為你提供的,調(diào)用Sleep()之后 WINDOWS操作系統(tǒng)自動(dòng)把你這個(gè)程序掛起了(就是暫時(shí)扔到一邊不管),然后讓CPU去執(zhí)行其他程序,等到時(shí)間到了,操作系統(tǒng)再把這段程序恢復(fù)繼續(xù)執(zhí)行,這樣的話CPU就可以得到充分地利用了,也就是說你可以在一塊CPU里面“同時(shí)”執(zhí)行多個(gè)任務(wù)而互不影響!(這里所說的“同時(shí)”并不是同時(shí)執(zhí)行,CPU每一時(shí)刻只能做一件事,但如果速度足夠快的話就可以讓人感到它是在同時(shí)執(zhí)行多項(xiàng)任務(wù)了)。是的,操作系統(tǒng)就是為了解決多任務(wù)執(zhí)行而生的。既然操作系統(tǒng)這么神奇,可不可以讓單片機(jī)也來爽一把呢?答案是肯定的。下面就介紹如何給單片機(jī)寫個(gè)操作系統(tǒng)!!

 
/************************************************************************************/
工欲善其事,必先利其器,為了寫出操作系統(tǒng),必須得有一定的理論以及技術(shù)基礎(chǔ),
單片機(jī)方面的可以http://www.zg4o1577.cn 了解到,從下面是所需的材料:
//1   C語言編程基礎(chǔ)        :三斤
//2   數(shù)據(jù)結(jié)構(gòu)理論           :一斤八兩
//3   操作系統(tǒng)原理           :兩斤三兩八錢
//4   計(jì)算機(jī)組成原理以及單片機(jī)原理及應(yīng)用  :兩斤半
//5   匯編語言編程基礎(chǔ)    :一斤四兩
//6   一份堅(jiān)持的心            :多少斤自己掂量掂量,呵呵
/*************************************************************************************/

 
這么多怎么學(xué)?去哪學(xué)?下面是我個(gè)人推薦的書單,僅供參考:

 
1. C語言是必須要會(huì)的,而且要熟練,諸如”預(yù)編譯命令“你必須要懂,模塊化編程必須要熟悉,指針是C語言的一大精髓,在操作系統(tǒng)源碼里面指針是滿天飛的,所以得有足夠的理論基礎(chǔ),推薦國外的《C Primer Plus》 美國 Stephen Prata著,里面講的內(nèi)容由淺到深,語言引人入勝,大二開始看,現(xiàn)在還時(shí)不時(shí)地要回頭看,確實(shí)是一本不錯(cuò)的好書:

 

 

另外,學(xué)會(huì)了C的基本語法之后你還得要會(huì)一點(diǎn)點(diǎn)編程技巧以及編程要注意的問題之類的,推薦有空的話多看看《C專家編程》和《C陷阱與缺陷》,這兩本書是C編程領(lǐng)域里面的經(jīng)典之作,相信看完你的功力會(huì)大有長進(jìn),但是還是要以經(jīng)常敲代碼為主:

 

 

 

2. 操作系統(tǒng)里面的數(shù)據(jù)組織形式都是以數(shù)據(jù)結(jié)構(gòu)的理論為基礎(chǔ)的,所以你得懂得數(shù)據(jù)結(jié)構(gòu)才能看懂里面的含義,但也不要求把數(shù)據(jù)結(jié)構(gòu)全精通,推薦嚴(yán)蔚敏版本的《數(shù)據(jù)結(jié)構(gòu)》,不過里面的算法都是用偽代碼寫出來的:

 

 

3. 有了編程基礎(chǔ)之后你還必須要懂得操作系統(tǒng)的基本原理,比如任務(wù)之間是怎么切換的,內(nèi)存是怎么管理的都得懂,推薦《操作系統(tǒng)-精髓與設(shè)計(jì)原理》

 

 

 

4. 匯編語言。為什么要學(xué)匯編?可能有些人會(huì)學(xué)得匯編難理解,而且現(xiàn)在C語言已經(jīng)可以很方便地編程了,所以不想學(xué)匯編,其實(shí)C語言再怎么方便強(qiáng)大,最后還是要通過編譯器轉(zhuǎn)換為匯編語言再由匯編轉(zhuǎn)換為機(jī)器碼,才能在機(jī)器中執(zhí)行。可以說,掌握了匯編之后你一定會(huì)對(duì)”代碼是怎么在CPU里面執(zhí)行的“這個(gè)哲學(xué)命題有進(jìn)一步的了解。另外,不學(xué)匯編你還真寫不出一個(gè)操作系統(tǒng)內(nèi)核來,因?yàn)椴僮飨到y(tǒng)的最低層是要直接操作CPU寄存器的,C語言無法實(shí)現(xiàn),只能用匯編寫出來,再由上層的C語言調(diào)用。匯編的書很多,這里就不介紹了,找一本去狂敲上個(gè)把星期就大概掌握了。

 


 

5. 另外你還要懂得計(jì)算機(jī)原理以及單片機(jī),其實(shí)單片機(jī)就是一臺(tái)閹割版的計(jì)算機(jī),你得對(duì)CPU寄存器,數(shù)據(jù)總線,地址總線,以及執(zhí)行方式這些有一定的了解才行,這方面的書也挺多的,不過介紹兩本個(gè)人覺得寫得挺好的書供課外閑讀,《編程卓越之道》1、2卷,這本書大體上介紹了高級(jí)語言是怎么樣在CPU里面執(zhí)行的,另外也對(duì)CPU內(nèi)部結(jié)構(gòu)做了一些介紹,比那些課內(nèi)教材寫得好,有空可以去看一下。

 

 

 

 


 

 

最后介紹一本《嵌入式實(shí)時(shí)操作系統(tǒng)UCOS II》,這本書介紹了UCOS II這個(gè)操作系統(tǒng)的內(nèi)部源代碼以及實(shí)現(xiàn)原理,我就是從這本書中學(xué)到了怎樣寫一個(gè)可以用的操作系統(tǒng)內(nèi)核。

 

 

書單推薦完畢,下面進(jìn)入重點(diǎn)~~~~~~~~~~~~~~~~~

 
/**************************************************************************************/
什么是操作系統(tǒng)?其實(shí)就是一個(gè)程序, 這個(gè)程序可以控制計(jì)算機(jī)的所有資源,對(duì)資源進(jìn)行分配,包括CPU時(shí)間,內(nèi)存,IO端口等,按一定規(guī)則分配給所需要的進(jìn)程(進(jìn)程?也就是一個(gè)程序,可以單獨(dú)執(zhí)行),并且自動(dòng)控制讓CPU可以執(zhí)行多個(gè)互不相關(guān)的任務(wù),按照書中的介紹,一個(gè)操作系統(tǒng)需要具備四個(gè)要素:進(jìn)程調(diào)度、內(nèi)存管理、IO管理、文件管理。

 
那怎么樣可以讓CPU同時(shí)執(zhí)行多個(gè)任務(wù)呢?首先想象一下如果讓CPU執(zhí)行單道程序,它會(huì)從MAIN函數(shù)開始一直順序地執(zhí)行下去,CPU里面有一個(gè)叫PC的寄存器,也就是程序計(jì)數(shù)器,它永遠(yuǎn)指向下一條要執(zhí)行的指令的存放地址,因?yàn)榇蠖鄶?shù)情況下指令都是逐條執(zhí)行的,所以PC寄存器也只是簡單地加一,所以大家都叫它”程序計(jì)數(shù)器“,從PC寄存器的特點(diǎn)也許我們可以做點(diǎn)文章?比如人為地讓PC寄存器指到另外一段程序的入口地址,那CPU不就自動(dòng)地跑到另一段程序了么?哈哈。假如我們可以這樣做,那沒錯(cuò),CPU確定是跑到別人的領(lǐng)地去執(zhí)行代碼了,問題是:怎么樣讓它回來繼續(xù)執(zhí)行?換句話說,PC寄存器改變之后CPU 已經(jīng)不知道剛剛這段程序執(zhí)行到哪里了,亦即跑不回來了,就像斷了線的風(fēng)箏。呃。。這問題麻煩。。解決了這個(gè)問題就似乎有點(diǎn)苗頭了。。

 
好吧,我們來看看有一個(gè)很相似的問題,就是單片機(jī)在執(zhí)行代碼的時(shí)候,突然有一個(gè)中斷信號(hào)過來了,單片機(jī)馬上就屁顛屁顛地跑到中斷服務(wù)程序里面去執(zhí)行了,執(zhí)行完畢之后,奇怪!!它怎么還記得跑回來原來的地方!!??OH NO .它是怎么辦到的。其實(shí)這里還要介紹另外一個(gè)寄存器叫SP的,即:STACK POINTER堆棧指針,這個(gè)指針指向一個(gè)內(nèi)存的地址,里面存放了一些數(shù)據(jù)。首先,單片機(jī)遇到中斷信號(hào)的時(shí)候,它就把當(dāng)前的PC寄存器的值保存到SP所指的地址,這就相當(dāng)于它記住了當(dāng)前執(zhí)行的地方,叫做斷點(diǎn)保護(hù),然后PC寄存器就指向中斷服務(wù)程序的地址,下一個(gè)時(shí)刻CPU就自動(dòng)執(zhí)行中斷服務(wù)程序里面的代碼了,執(zhí)行完畢之后中斷服務(wù)程序調(diào)用了一個(gè)指令:RETI,這條指令叫返回指令,在函數(shù)結(jié)束之后調(diào)用,它會(huì)自動(dòng)從SP指針指向的地址把值取出來放到PC寄存器里面,然后CPU就會(huì)自動(dòng)回到之前斷掉的地方繼續(xù)執(zhí)行了!基于這個(gè)原理,我們可以回到上面的問題:首先,讓CPU把當(dāng)前的PC保存起來,然后把PC指向別段程序地址,CPU就跑到別人的領(lǐng)地去執(zhí)行了,執(zhí)行完了之后我們可以把SP指向的內(nèi)容放回PC,這樣調(diào)用RET指令之后,CPU就會(huì)回到原來的地方繼續(xù)執(zhí)行了!!貌似這個(gè)問題完美地解決了!!

 
可是還有一個(gè)關(guān)鍵的問題:CPU在執(zhí)行當(dāng)前代碼的時(shí)候 CPU里面所有的寄存器都保存的當(dāng)前這個(gè)程序所用到的值,比如做加法的時(shí)候用到PSW寄存器的進(jìn)位標(biāo)志位,如果此時(shí)切換到別的任務(wù),那再回到當(dāng)前程序的時(shí)候,這些值都會(huì)被改變,CPU會(huì)陷入混亂然后直接跑飛!!解決這問題同樣要靠SP同學(xué),在切換任務(wù)的時(shí)候我們把所有寄存器依次入到SP指向的地址,稱為入棧操作,每次入棧SP指針的值都會(huì)加一或者減一,視不同CPU而定。而要恢復(fù)的時(shí)候,就從SP指向的地址依次把值取出來放回原來的地方,稱為彈棧操作。最后才彈出地址到PC寄存器,下一時(shí)刻,CPU自動(dòng)跑到原來的地址繼續(xù)執(zhí)行,從CPU的角度看就像沒有發(fā)生任務(wù)切換一樣,一切依舊,繼續(xù)工作。如果CPU的執(zhí)行速度夠快,切換速度也夠快,這樣就可以給人感覺CPU同時(shí)在執(zhí)行很多任務(wù),這就是操作系統(tǒng)里面最基本的原理。

 
SO,解釋完原理,我們首先來就來實(shí)現(xiàn)簡單的任務(wù)切換,這里的難點(diǎn)就在于:執(zhí)行這一動(dòng)作必須要操作CPU的寄存器,而C語言是無法實(shí)現(xiàn)的,這就是為什么要用到匯編的原因了,所有操作系統(tǒng)的最底層代碼都是用匯編語言實(shí)現(xiàn)的,否則根本無法實(shí)現(xiàn)任務(wù)切換。下面要介紹匯編里面的幾條相關(guān)指令。PS:雖然每種CPU的匯編都不同,但是基本原理還是相通的。
第一條:CALL。函數(shù)調(diào)用指令,當(dāng)我們要調(diào)用一個(gè)函數(shù)的時(shí)候,就會(huì)用到CALL這條指令,它執(zhí)行再從個(gè)動(dòng)作,第一,先把當(dāng)前的PC值保存起來,即現(xiàn)場保護(hù),第二,把要調(diào)用的函數(shù)的入口地址送到PC,這樣,在下一時(shí)刻到來的時(shí)候,CPU就自動(dòng)跳轉(zhuǎn)到特定的函數(shù)入口地址開始執(zhí)行了。
第二條:RET/RETI。當(dāng)一個(gè)函數(shù)執(zhí)行完畢的時(shí)候,需要返回到原來執(zhí)行的地方,這時(shí)候就要調(diào)用 RET指令(在中斷函數(shù)中返回的時(shí)候調(diào)用RETI指令)。它把SP指向的數(shù)據(jù),即上一次調(diào)用CALL時(shí)保存的那個(gè)地址原來到PC,這樣,當(dāng)下一時(shí)刻到來的時(shí)候,CPU就會(huì)跳回到原來的地方了。實(shí)際上函數(shù)調(diào)用過程就是這樣的,所以有時(shí)候一些簡單簡短的函數(shù)寧愿用#define宏定義寫出來,因?yàn)檫@樣寫出來就不用使用調(diào)用/返回過程,節(jié)省了時(shí)間。
第三/四條:PUSH/POP。這兩個(gè)指令是兩兄弟,即入棧及出棧。關(guān)于堆棧的特性說明一下:堆棧這種結(jié)構(gòu)的特性就是后進(jìn)先出,就像疊盤子一樣,最后疊上去的盤子會(huì)被最先取出,這種原理非常好用,想象一下函數(shù)嵌套的時(shí)候發(fā)生的一切,就是利用到這種思路。PUSH指令用到把寄存器的值保存起來,它會(huì)把值到保存到SP指針?biāo)傅牡胤健OP指令則把數(shù)據(jù)從SP所指的地址恢復(fù)到原來的寄存器中。

 
用這幾條指令,我們就可以寫出一個(gè)任務(wù)切換函數(shù)了,不過寫之前還要說明一下什么叫人工堆棧。其實(shí)上,一個(gè)程序在執(zhí)行的時(shí)候,它會(huì)用到一塊內(nèi)存空間用于保存各種變量,比如調(diào)用函數(shù)的時(shí)候這塊地方會(huì)用于保存地址以及寄存器,而在執(zhí)行一些復(fù)雜算法的時(shí)候,如果CPU的寄存器已經(jīng)用完了,這塊地方也會(huì)作為臨時(shí)中間變量的存放區(qū),另外,在向一個(gè)函數(shù)傳遞參數(shù)的時(shí)候,比如:printf(a,b,c,d,e....),如果參數(shù)過多,多余的參數(shù)也會(huì)先存放到這塊地方。所以說,這塊地方就像是這個(gè)程序的倉庫一樣,存放著要用的東西。如果是在單道程序中,顯然這樣用沒問題,但是如果是多道程序的話,問題就來了,因?yàn)槿绻腥蝿?wù)共用那塊區(qū)域,那舊任務(wù)保存的東西就會(huì)被新任務(wù)所沖掉,CPU一下子就瘋掉了。。解決的辦法就是:每個(gè)任務(wù)都給它提供一塊專用的區(qū)域,這塊專用區(qū)域就叫人工堆棧,每個(gè)任務(wù)都不一樣,保證了不會(huì)相互沖突。

 
PS:因?yàn)?1單片機(jī)的內(nèi)存太小,基本無法實(shí)現(xiàn)多任務(wù),實(shí)現(xiàn)了也不實(shí)用,所以硬件平臺(tái)我選用了AVR單片機(jī)ATMEGA16,有1KB內(nèi)存,應(yīng)該夠用了,花了兩天時(shí)間把AVR的匯編指令看了一遍

 

 

首先,當(dāng)需要切換任務(wù)的時(shí)候,要先把當(dāng)前的所有寄存器全部入棧,在AVR單片機(jī)中有32個(gè)通用寄存器R0-R31,還有PC指針,PSW程序狀態(tài)寄存器,這些都要入棧,所以需要的內(nèi)存挺多的。現(xiàn)在的編譯器都支持在線匯編,就是在C語言里面嵌入?yún)R編語言,方便得多,下面我宏定義了一組入棧操作:PUSH_REG(),里面是用PUSH指令把32個(gè)寄存器全部入棧
#define PUSH_REG() \
{_asm("PUSH R0\n\t"   "PUSH R1\n\t"   "PUSH R2\n\t"   "PUSH R3\n\t"  \
    "PUSH R4\n\t"   "PUSH R5\n\t"   "PUSH R6\n\t"   "PUSH R7\n\t"  \
"PUSH R8\n\t"   "PUSH R9\n\t"   "PUSH R10\n\t"  "PUSH R11\n\t" \
"PUSH R12\n\t"  "PUSH R13\n\t"  "PUSH R14\n\t"  "PUSH R15\n\t" \
         "PUSH R16\n\t"  "PUSH R17\n\t"  "PUSH R18\n\t"  "PUSH R19\n\t" \
"PUSH R20\n\t"  "PUSH R21\n\t"  "PUSH R22\n\t"  "PUSH R23\n\t" \
"PUSH R24\n\t" "PUSH R25\n\t" "PUSH R26\n\t" "PUSH R27\n\t"    \
"PUSH R28\n\t" "PUSH R29\n\t" "PUSH R30\n\t" "PUSH R31\n\t"    ); }
入完棧完接下來要保護(hù)當(dāng)前程序的SP指針,以便下次它要返回的時(shí)候能找到該人工堆棧的地址:
OS_LastThread->ThreadStackTop=(OS_DataType_ThreadStack *)SP;
 
這一句用C語言就可以實(shí)現(xiàn)了。
接下來關(guān)于當(dāng)前這段程序的現(xiàn)場算是保護(hù)好了,然后找到要切換到的任務(wù)的人工堆棧地址,把它賦給SP指針,如下:
SP=(uint16_t)OS_CurrentThread->ThreadStackTop;
 
出棧跟入棧的語法差不多,只是出棧順序要相反:
POP_REG();
接下來,要調(diào)用一條很重要的指令了!!!此令一出,CPU就乖乖地切換任務(wù)了!
_asm("RET\n\t");
 
調(diào)用返回指令,它就從SP里面取出函數(shù)地址放到PC,注意他取出的是剛剛放入SP指向地址的函數(shù)入口,所以它會(huì)返回到新任務(wù)執(zhí)行。
就這樣,一個(gè)操作系統(tǒng)里面最核心的”任務(wù)調(diào)度器“的模型就這樣簡單地實(shí)現(xiàn)了,操作系統(tǒng)里面所作的跟任務(wù)切換有關(guān)的事情到最后都要調(diào)用到這個(gè)任務(wù)調(diào)度器,現(xiàn)在我們實(shí)現(xiàn)調(diào)度器了,相當(dāng)于成功了1/3,接下來的事情就是考慮在什么情況下調(diào)用這個(gè)調(diào)度器。

 
調(diào)度策略:實(shí)現(xiàn)了調(diào)度,還要繼續(xù)考慮調(diào)度策略,就是什么情況下需要調(diào)度哪些任務(wù)。調(diào)度策略分很多種,有興趣的可以去看那本《操作系統(tǒng)原理》,在我的源代碼里面使用了”搶占式優(yōu)先級(jí)調(diào)度+同一優(yōu)先級(jí)下時(shí)間片輪詢調(diào)度“的方法。
所謂搶占式優(yōu)先級(jí)調(diào)度是一種實(shí)時(shí)調(diào)度的方法,在實(shí)時(shí)操作系統(tǒng)中常用,這種方法的原理就是:操作系統(tǒng)在任何時(shí)候都要保證擁有最高優(yōu)先級(jí)的那個(gè)任務(wù)處于運(yùn)行態(tài),比如此記在運(yùn)行著優(yōu)先級(jí)為2的任務(wù),因?yàn)橐恍┬盘?hào)到達(dá),優(yōu)先級(jí)為1的那個(gè)任務(wù)解除了阻塞,處于就緒態(tài),這時(shí)操作系統(tǒng)就必須馬上停止任務(wù)2,切換到任務(wù)1,切換的這段時(shí)間需要越短越好。
而時(shí)間片輪詢即是讓每個(gè)任務(wù)都處于平等地位,然后給每個(gè)任務(wù)相同的時(shí)間片,當(dāng)一個(gè)任務(wù)的運(yùn)行時(shí)間用完了,操作系統(tǒng)就馬上切換給下一個(gè)需要執(zhí)行的任務(wù),這種方法的實(shí)時(shí)性不高,但它確保了每個(gè)任務(wù)都有相同的執(zhí)行時(shí)間。
我把這兩種方法結(jié)合起來,首先設(shè)定了8個(gè)優(yōu)先級(jí)組,每個(gè)優(yōu)先級(jí)組下面都用單向鏈表把具有相同優(yōu)先級(jí)的任務(wù)連接起來。這樣的話首先操作系統(tǒng)會(huì)查找最高優(yōu)先級(jí)的那組,然后在組里面輪流執(zhí)行所有任務(wù)(和UCOS II相比這種做法更具有靈活性,因?yàn)閁COS II只有搶占式調(diào)度,這是UCOS II的硬傷。。)。我聲明了一個(gè)任務(wù)結(jié)構(gòu)體稱為線程控制塊,把關(guān)于該任務(wù)的所有狀態(tài)都放在一起:
/** 
*  @結(jié)構(gòu)體聲明
*  @名稱        : OS_TCB , *pOS_TCB
*  @成員        : 1. OS_DataType_ThreadStack  *ThreadStackTop
*                                 線程人工堆棧棧頂指針
*                         2. OS_DataType_ThreadStack  *ThreadStackBottom
*               線程人工堆棧棧底指針
*          3. OS_DataType_ThreadStackSize  ThreadStackSize
*   線程人工堆棧大小
*          4. OS_DataType_ThreadID  ThreadID
*   線程ID號(hào)
*          5. OS_DataType_ThreadStatus  ThreadStatus
*   線程運(yùn)行狀態(tài)
*   6. OS_DataType_PSW  PSW
*   記錄線程的程序狀態(tài)寄存器
*          7. struct _OS_TCB  *Front
*   指向上一個(gè)線程控制塊的指針
*         8. struct _OS_TCB  *Next
*   指向下一人線程控制塊的指針
*                        9.struct _OS_TCB                  *CommWaitNext ;
*                                 指向線程通信控制塊的指針
*                        10.struct _OS_TCB                   *TimeWaitNext ;
*                                  指向延時(shí)等待鏈表的指針
*                         11.OS_DataType_PreemptionPriority  Priority ;
*                                   任務(wù)優(yōu)先級(jí)
*                          12.OS_DataType_TimeDelay           TimeDelay ;
*                                  任務(wù)延時(shí)時(shí)間
*  @描述        : 定義線程控制塊的成員
*  @建立時(shí)間    : 2011-11-15
*  @最近修改時(shí)間: 2011-11-17
*/
typedef struct _OS_TCB{
 
OS_DataType_ThreadStack         *ThreadStackTop ;      
 
OS_DataType_ThreadStack         *ThreadStackBottom ;
 
OS_DataType_ThreadStackSize     ThreadStackSize;
 
OS_DataType_ThreadID            ThreadID ;
 
OS_DataType_ThreadStatus        ThreadStatus ;
 
OS_DataType_PSW                 PSW ;
 
        struct _OS_TCB                  *Front ;
 
struct _OS_TCB                  *Next ;
 
#if OS_COMMUNICATION_EN == ON
struct _OS_TCB                  *CommWaitNext ;
#endif

 
       struct _OS_TCB                   *TimeWaitNext ;
   
OS_DataType_PreemptionPriority  Priority ;

 
OS_DataType_TimeDelay           TimeDelay ;
 
}OS_TCB,*pOS_TCB;

 
首先啟動(dòng)系統(tǒng)的時(shí)候需要先創(chuàng)建任務(wù),任務(wù)被創(chuàng)建之后才可以得到執(zhí)行,使用如下函數(shù):
/**
*  @名稱:線程創(chuàng)建函數(shù)
*  @輸入?yún)?shù):1.pOS_TCB ThreadControlBlock      線程控制塊結(jié)構(gòu)體指針
*          2.void (*Thread)(void*)                     線程函數(shù)入口地址,接受一個(gè)空指針形式的輸入?yún)?shù),無返回參數(shù)
*                        3.void *Argument                               需要傳遞給線程的參數(shù),空指針形式
*  @建立時(shí)間    : 2011-11-18
*  @最近修改時(shí)間: 2011-11-18
*/
void OS_ThreadCreate(pOS_TCB ThreadControlBlock,void (*Thread)(void *),void *Argument)
關(guān)于創(chuàng)建任務(wù)的大致描述就是:填定線程控制塊,把線程控制塊鏈到單向鏈表中,設(shè)置人工堆棧,細(xì)節(jié)很多,就不一一贅述了。

 
當(dāng)前版本只實(shí)現(xiàn)了輪詢調(diào)度,還沒加上搶占調(diào)度,使用下面的函數(shù)就可以啟動(dòng)操作系統(tǒng)開始多線程任務(wù)!
/** 
*  @名稱        : 實(shí)時(shí)內(nèi)核引發(fā)函數(shù)
*  @版本        : V 0.0
*  @輸入?yún)?shù)    : 無
*  @輸出參數(shù)    : 無
*  @描述        : 在主函數(shù)中用于啟動(dòng),調(diào)用該函數(shù)后不會(huì)返回,直接切換到最高優(yōu)先級(jí)任務(wù)開始執(zhí)行
*  @建立時(shí)間    : 2011-11-15
*  @最近修改時(shí)間: 2011-11-15
*/
void OS_KernelStart(void)
{
OS_Status = OS_RUNNING ;    //把內(nèi)核狀態(tài)設(shè)置為運(yùn)行態(tài)

 
                                                         //取得第一個(gè)需要運(yùn)行的任務(wù)
OS_CurrentThread = OS_TCB_PriorityGroup[pgm_read_byte(ThreadSearchTab + OS_PreemptionPriority)].OS_TCB_Current;
OS_LastThread = NULL ;         
                                                         //SP指針指向該任務(wù)的棧頂
SP = (uint16_t)OS_CurrentThread->ThreadStackTop ;
                                                        //使用出棧操作
POP_REG();
                                                        //調(diào)用RET,調(diào)用之后開始執(zhí)行任務(wù),不會(huì)再返回到這里
_asm("RET\n\t");
}

 
怎樣實(shí)現(xiàn)時(shí)間片?答案是用定時(shí)器定時(shí),每次定時(shí)器產(chǎn)生中斷的時(shí)候就轉(zhuǎn)換一次任務(wù),時(shí)基可以自己確定,一般來說時(shí)基越小的話會(huì)讓CPU花很多時(shí)間在切換任務(wù)上,降低了效率,時(shí)基大的話又使時(shí)間粒度變粗,會(huì)使一些程序得不到及時(shí)的執(zhí)行。我設(shè)定了每10MS中斷一次,就是說每一輪中每個(gè)線程都有10MS的執(zhí)行時(shí)間。具體算法不再贅述。

 
內(nèi)存管理策略
接下來要考慮怎樣管理內(nèi)存了!在PC里面編程的時(shí)候,如果需要開辟一個(gè)內(nèi)存空間,我們可以很容易地調(diào)用malloc()和free()來完成,但是在單片機(jī)里面卻行不通,因?yàn)橐獙?shí)現(xiàn)這兩個(gè)函數(shù)背后需要完成很多算法支持,從速度和空間上單片機(jī)都做不到。
在單片機(jī)里面如果你需要開辟內(nèi)存空間,你只有在編譯的時(shí)候就先定義好變量,無法動(dòng)態(tài)申請(qǐng),但是我們可以設(shè)計(jì)一個(gè)簡單的內(nèi)存管理策略來實(shí)現(xiàn)這種動(dòng)態(tài)申請(qǐng)!原理就是在編譯的時(shí)候先向編譯器要一塊足夠大的內(nèi)存并且聲明為靜態(tài),然后把這塊空間交給內(nèi)存管理模塊來調(diào)用,內(nèi)存管理模塊負(fù)責(zé)分配這塊內(nèi)存,當(dāng)有任務(wù)要向它申請(qǐng)內(nèi)存的時(shí)候它就從里面拿出一塊交給任務(wù),而任務(wù)要釋放的時(shí)候就把該內(nèi)存空間交給內(nèi)存管理模塊來實(shí)現(xiàn)。
關(guān)于內(nèi)存管理也有很多種策略,在這里就不一一述說了,我在源代碼里面使用了一種簡單的隨機(jī)分配的方法,即有線程申請(qǐng)的時(shí)候就從當(dāng)前內(nèi)存塊的可用空間里拿出一塊來,然后在內(nèi)存頭加上一個(gè)專用的結(jié)構(gòu)體,把每個(gè)內(nèi)存塊都鏈接起來,這樣便于管理。當(dāng)線程釋放內(nèi)存的時(shí)候,就把內(nèi)存返回到內(nèi)存空間并跟其他空間的內(nèi)存塊合并起來等待線程再次調(diào)用。
/** 
*  @名稱           : 內(nèi)存塊申請(qǐng)函數(shù)
*  @版本           : V 0.0
*  @輸入?yún)?shù)    : 1. OS_DataType_MemorySize MemorySize
                              需要申請(qǐng)內(nèi)存塊的大小
*  @輸出參數(shù)    : 1. void *
                             若申請(qǐng)成功,則返回可使用內(nèi)存塊首地址,否則返回NULL
*  @描述           : 
*  @建立時(shí)間    : 2011-11-16
*  @最近修改時(shí)間: 2011-11-16
*/
#if OS_MEMORY_EN
void *OS_MemoryMalloc(OS_DataType_MemorySize MemorySize)
{
pOS_MCB pmcb = OS_MCB_Head ;
pOS_MCB pmcb2 ;
MemorySize+=OS_MEMORY_BLOCK_SIZE ;
 
//進(jìn)入內(nèi)存搜索算法
while(1)
{
//檢測該內(nèi)存塊是否存在
if(pmcb==NULL)
{
return NULL ;
}
   
//如果存在則檢測該內(nèi)存塊的使用狀態(tài)
else if( (pmcb->Status==OS_MEMORY_STATUS_IDLE) && (pmcb->Size >= MemorySize) )
{
//如果可用內(nèi)存塊大小剛好等于需要申請(qǐng)的大小
//則立即分配
if(pmcb->Size == MemorySize)
{
pmcb->Status=OS_MEMORY_STATUS_USING ;
OS_MemoryIdleCount -= MemorySize ;
return (OS_DataType_Memory *)pmcb + OS_MEMORY_SIZE ;
}
 
//若可用內(nèi)存塊大小大于需要申請(qǐng)的大小
//則進(jìn)行分割操作
else
{
pmcb2=(pOS_MCB)( (OS_DataType_Memory *)pmcb + MemorySize  );
pmcb2->Front=pmcb ;
pmcb2->Next=pmcb->Next ;
pmcb2->Status=OS_MEMORY_STATUS_IDLE ;
pmcb2->Size = pmcb->Size - MemorySize ;
pmcb->Status = OS_MEMORY_STATUS_USING ;
pmcb->Size = MemorySize ;
pmcb->Next=pmcb2;
OS_MemoryIdleCount -= MemorySize ;
return (OS_DataType_Memory *)pmcb+OS_MEMORY_BLOCK_SIZE ;
}
}
 
else
{
pmcb=pmcb->Next;
}
}
}
#endif

 
內(nèi)存釋放函數(shù):
/** 
*  @名稱           : 內(nèi)存塊釋放函數(shù)
*  @版本           : V 0.0
*  @輸入?yún)?shù)    : 1. OS_DataType_MemorySize MemorySize
                              需要申請(qǐng)內(nèi)存塊的大小
*  @輸出參數(shù)    : 1. void *
                              若申請(qǐng)成功,則返回可使用內(nèi)存塊首地址,否則返回NULL
*  @描述           : 
*  @建立時(shí)間    : 2011-11-16
*  @最近修改時(shí)間: 2011-11-16
*/
#if OS_MEMORY_EN
void OS_MemoryFree(void *MCB)
{
pOS_MCB pmcb = (pOS_MCB)( (OS_DataType_Memory *)MCB - OS_MEMORY_BLOCK_SIZE );
 
//將當(dāng)前內(nèi)存塊設(shè)置為空閑狀態(tài)
pmcb->Status=OS_MEMORY_STATUS_IDLE ;
OS_MemoryIdleCount += pmcb->Size ;
 
//如果存在上一塊內(nèi)存塊,則進(jìn)入判斷
if(pmcb->Front!=NULL)
{
//如果上一塊內(nèi)存塊處于空閑狀態(tài),則進(jìn)行合并操作
if(pmcb->Front->Status == OS_MEMORY_STATUS_IDLE)
   {
pmcb->Front->Size += pmcb->Size ;
pmcb->Front->Next  = pmcb->Next ;
pmcb=pmcb->Front ;
OS_MemoryIdleCount += pmcb->Size ;
}
}
//如果存在下一塊內(nèi)存塊,則進(jìn)入判斷
if(pmcb->Next!=NULL)
{
//如果下一塊內(nèi)存塊處于空閑狀態(tài),則進(jìn)行合并操作
if(pmcb->Next->Status==OS_MEMORY_STATUS_IDLE)
{
pmcb->Size += pmcb->Next->Size ;
pmcb->Next = pmcb->Next->Next ;
OS_MemoryIdleCount += pmcb->Size ;
}
}
}
#endif

 
這種分配策略雖然實(shí)現(xiàn)簡單,但是缺點(diǎn)就是容易產(chǎn)生內(nèi)存碎片,即隨著時(shí)間推移,可用內(nèi)存會(huì)越來越碎片化,最后導(dǎo)致想要申請(qǐng)足夠大的內(nèi)存塊都沒辦法。。。

 
/********************************************************************************/
至此,一個(gè)簡單的單片機(jī)使用的操作系統(tǒng)模型就算完成了,應(yīng)用在AVR單片機(jī)中,下面進(jìn)入測試階段:
因?yàn)檫沒有完成線程通信模塊還搶占式算法,所以目前只能執(zhí)行輪詢多任務(wù)操作。我寫了一個(gè)測試程序,就是創(chuàng)建三個(gè)流水燈程序(是不是覺得寫個(gè)操作系統(tǒng)就用來跑流水燈太浪費(fèi)了,哈哈),讓它們同時(shí)閃,在PROTEUS中仿真查看
在AVR STUDIO5開發(fā)環(huán)境中編寫,代碼如下:
#include "includes.h"
#include "OS_core.h"

 
#define STACK_SIZE 80  //定義每個(gè)任務(wù)的人工堆棧大小

 
//定義三個(gè)任務(wù)各自的人工堆棧
uint8_t Test1Stack[STACK_SIZE];
uint8_t Test2Stack[STACK_SIZE];
uint8_t Test3Stack[STACK_SIZE];

 
//定義三個(gè)任務(wù)各自的線程控制塊
OS_TCB Task1;
OS_TCB Task2;
OS_TCB Task3;

 
//線程1讓PB口閃爍
void Test1(void *p)
{
uint8_t i;
DDRB=0XFF;
PORTB=0xff;
       SREG|=0X80;
while(1)
{
for(i=0;i<8;i++) PORTB=1<<i;
}
}

 
//線程2讓PC口閃爍
void Test2(void *p)
{
uint8_t i;
DDRC=0xff;
PORTC=0XFF;
SREG|=0X80 ;
while(1)
{
for(i=0;i<8;i++) PORTC=1<<i;
}
}

 
//線程3讓PD口閃爍
void Test3(void *p)
{
uint8_t i;
DDRD=0XFF;
PORTD=0xff;
      SREG|=0X80;
while(1)
{
for(i=0;i<8;i++) PORTD=1<<i;
}
}

 
//MAIN函數(shù)
int main(void)
{
uint8_t i = 0x77;
       //初始化操作系統(tǒng)
OS_Init();

 
        //初始化線程控制塊并創(chuàng)建任務(wù)
OS_ThreadInit(&Task1,Test1Stack,STACK_SIZE,5,0);
OS_ThreadCreate(&Task1,Test1,&i);
 
OS_ThreadInit(&Task3,Test3Stack,STACK_SIZE,5,0);
OS_ThreadCreate(&Task3,Test3,&i);
 
OS_ThreadInit(&Task2,Test2Stack,STACK_SIZE,5,0);
OS_ThreadCreate(&Task2,Test2,&i);
 
       //初始化定時(shí)器
OS_TimerInit();

 
      //啟動(dòng)內(nèi)核 
OS_KernelStart();

 
      //正常的話程序永遠(yuǎn)不會(huì)執(zhí)行到這里!!!
while(1);
}

 

 
OK,開始調(diào)試咯!打開PROTEUS連線,LOAD程序,然后運(yùn)行。。。。

 

 

成功同時(shí)運(yùn)行三個(gè)流水燈程序!太棒了!接下來在這個(gè)內(nèi)核的支持下你就可以創(chuàng)作你的應(yīng)用程序了,使用內(nèi)核提供的線程創(chuàng)建函數(shù)你可以創(chuàng)建N多個(gè)線程,當(dāng)然了,必須在內(nèi)存可接受的范圍內(nèi)。利用內(nèi)存分配函數(shù)你可以動(dòng)態(tài)申請(qǐng)和釋放內(nèi)存了。再也不用為DELAY()這種浪費(fèi)CPU效率的作法郁悶很久了。
 
上面所說的所有代碼都開源,想看的同學(xué)發(fā)郵件到我EMAIL: wfm2012@126.com 索要
下次有空再作一些應(yīng)用范例來玩玩
關(guān)閉窗口

相關(guān)文章

主站蜘蛛池模板: 成人h视频| 亚洲欧美日韩在线不卡 | 欧美日韩福利视频 | 午夜视频在线观看一区二区 | 在线中文字幕亚洲 | 久久国产一区 | 亚洲a网 | 亚洲激情专区 | 97精品国产 | 国产传媒毛片精品视频第一次 | 亚洲国产精品一区 | 久久精品亚洲精品国产欧美 | 午夜视频在线免费观看 | 日韩av免费在线观看 | 国产高清视频一区二区 | 国产精品久久av | 日韩电影在线一区 | 欧美αv| 亚洲国产精品suv | 亚洲a在线视频 | 午夜影院在线观看版 | 精品国产一区二区三区久久久蜜月 | 久久久国产精品视频 | 99久久婷婷国产亚洲终合精品 | 精品国产91乱码一区二区三区 | 免费在线视频一区二区 | 成年人精品视频在线观看 | 国产欧美精品一区二区 | 999视频| 久久久久国产精品一区二区 | 在线āv视频 | 视频在线观看亚洲 | 日本人做爰大片免费观看一老师 | 亚洲欧洲激情 | 欧美成人高清 | 日韩av成人 | 综合久久av| 日本精品视频一区二区 | 一区二区三区中文字幕 | 999久久久 | 欧州一区 |