當前位置:首頁 > 單片機 > 單片機
[導(dǎo)讀]任務(wù):實現(xiàn)并行多任務(wù),沒有考慮中斷問題。我會在“51操作系統(tǒng)學(xué)習(xí)筆記(二)”考慮中斷問題,研究一個復(fù)雜一點的操作系統(tǒng)。51單片機上用操作系統(tǒng),雖說沒有太大的實際用處。但是,對于學(xué)習(xí)嵌入式操作系統(tǒng),畢竟是最

任務(wù):實現(xiàn)并行多任務(wù),沒有考慮中斷問題。我會在“51操作系統(tǒng)學(xué)習(xí)筆記(二)”考慮中斷問題,研究一個復(fù)雜一點的操作系統(tǒng)。

51單片機上用操作系統(tǒng),雖說沒有太大的實際用處。但是,對于學(xué)習(xí)嵌入式操作系統(tǒng),畢竟是最容易入門的。

開源的small rtos51和uc os,對初學(xué)者來說,還是有一定難度。

在網(wǎng)上找了一個最容易入手的,簡單的,51操作系統(tǒng)入門資料。

這個資料其實算不上完整的操作系統(tǒng),但它實現(xiàn)了簡單的并行多任務(wù)。

從這里,也可以理解堆棧的應(yīng)用。

該程序很巧妙的是用ret來實現(xiàn)程序的跳轉(zhuǎn)。

ret實現(xiàn)從子程序返回主程序,sp的內(nèi)容彈出到pc.

子程序結(jié)束時會調(diào)用ret,本文利用這一點進行跳轉(zhuǎn)。

程序的核心流程是:

在主程序的最后一句os_start(0)里,sp被賦值為任務(wù)一的程序地址,執(zhí)行return時實現(xiàn)ret,sp的內(nèi)容賦給pc,即pc的值就是任務(wù)一的程序地址,所以馬上執(zhí)行任務(wù)一。

任務(wù)一中的task_switch()會把sp的值賦給任務(wù)一的指針(task_sp[0]=sp,保留任務(wù)一的斷點,下次執(zhí)行任務(wù)一時從改斷點運行),然后把任務(wù)二的程序地址賦給sp,task_switch()結(jié)束時會調(diào)用ret,sp的值會賦給pc,即pc為任務(wù)二的程序地址,所以馬上執(zhí)行任務(wù)二。

同理執(zhí)行任務(wù)三。

然后又執(zhí)行任務(wù)一。

.....

如此不斷循環(huán)。

全部程序如下:詳細講解見http://www.ourdev.cn/bbs/bbs_content_all.jsp?bbs_sn=1398508

//下面是全部程序,大家可以把它拷貝到keil中,編譯鏈接.

//在proteus中仿真,p1,p2,p3分別接led燈(p1,p2,p3低電平燈亮),會發(fā)現(xiàn)三個led輪流亮滅,這表示三個任務(wù)都在模擬并行執(zhí)行

/*

簡單的多任務(wù)操作系統(tǒng)

其實只有個任務(wù)調(diào)度切換,把說它是OS有點牽強,但它對于一些簡單的開發(fā)應(yīng)用來說,簡單也許就是最好的.盡情的擴展它吧.別忘了把你的成果分享給大家.

這是一個最簡單的OS,一切以運行效率為重,經(jīng)測試,切換一次任務(wù)僅20個機器周期,也就是在標準51(工作于12M晶振)上20uS.
而為速度作出的犧牲是,為了給每個任務(wù)都分配一個私有堆棧,而占用了較多的內(nèi)存.作為補償,多任務(wù)更容易安排程序邏輯,從而可以節(jié)省一些用于控制的變量.
任務(wù)槽越多,占用內(nèi)存越多,但任務(wù)也越好安排,以實際需求合理安排任務(wù)數(shù)目.一般來說,4個已足夠.況且可以拿一個槽出來作為活動槽,換入換入一些臨時任務(wù).

task_load(函數(shù)名,任務(wù)槽號)
裝載任務(wù)

os_start(任務(wù)槽號)
啟動任務(wù)表.參數(shù)必須指向一個裝載了的任務(wù),否則系統(tǒng)會崩潰.

task_switch()
切換到其它任務(wù)

.編寫任務(wù)函數(shù)注意事項:
KEIL C編譯器是假定用戶使用單任務(wù)環(huán)境,所以在變量的使用上都未對多任務(wù)進行處理,編寫任務(wù)時應(yīng)注意變量覆蓋和代碼重入問題.

1.覆蓋:編譯器為了節(jié)省內(nèi)存,會給兩個沒用調(diào)用關(guān)系的函數(shù)分配同一內(nèi)存地址作為變量空間.這在單任務(wù)下是很合理的,但對于多任務(wù)來說,兩個進程會互相干擾對方.
解決的方法是:凡作用域內(nèi)會跨越task_switch()的變量,都使用static前輟,保證其地址空間分配時的唯一性.

2.重入:重入并不是多任務(wù)下獨有的問題,在單任務(wù)時,函數(shù)遞歸同樣會導(dǎo)致重入,即,一個函數(shù)的不同實例(或者叫作"復(fù)本")之間的變量覆蓋問題.
解決的方法是:使用reentrant函數(shù)后輟(例如:void function1() reentrant{...}).當然,根本的辦法還是避免重入,因為重入會帶來巨大的目標代碼量,并極大降低運行效率.

3.額外提醒一句,在本例中,任務(wù)函數(shù)必須為一個死循環(huán).退出函數(shù)會導(dǎo)致系統(tǒng)崩潰.


.任務(wù)函數(shù)如果是用匯編寫成或內(nèi)嵌匯編,切換任務(wù)時應(yīng)該注意什么問題?

由于KEIL C編譯器在處理函數(shù)調(diào)用時的約定規(guī)則為"子函數(shù)有可能修改任務(wù)寄存器",因此編譯器在調(diào)用前已釋放所有寄存器,子函數(shù)無需考慮保護任何寄存器.
這對于寫慣匯編的人來說有點不習(xí)慣: 匯編習(xí)慣于在子程序中保護寄存器.
請注意一條原則:凡是需要跨越task_switch()的寄存器,全部需要保護(例如入棧).根本解決辦法還是,不要讓寄存器跨越任務(wù)切換函數(shù)task_switch()
事實上這里要補充一下,正如前所說,由于編譯器存在變量地址覆蓋優(yōu)化,因此凡是非靜態(tài)變量都不得跨越task_switch().


任務(wù)函數(shù)的書寫:
void 函數(shù)名(void){//任務(wù)函數(shù)必須定義為無參數(shù)型
while(1){//任務(wù)函數(shù)不得返回,必須為死循環(huán)
//....這里寫任務(wù)處理代碼

task_switch();//每執(zhí)行一段時間任務(wù),就釋放CPU一下,讓別的任務(wù)有機會運行.
}
}


任務(wù)裝載:
task_load(函數(shù)名,任務(wù)槽號)

裝載函數(shù)的動作可發(fā)生在任意時候,但通常是在main()中.要注意的是,在本例中由于沒考慮任務(wù)換出,
所以在執(zhí)行os_start()前必須將所有任務(wù)槽裝滿.之后可以隨意更換任務(wù)槽中的任務(wù).

啟動任務(wù)調(diào)度器:
os_start(任務(wù)槽號)

調(diào)用該宏后,將從參數(shù)指定的任務(wù)槽開始執(zhí)行任務(wù)調(diào)度.本例為每切換一次任務(wù)需額外開銷20個機器周期,用于遷移堆棧.
*/


#include


/*============================以下為任務(wù)管理器代碼============================*/

#define MAX_TASKS 3//任務(wù)槽個數(shù).在本例中并未考慮任務(wù)換入換出,所以實際運行的任務(wù)有多少個,就定義多少個任務(wù)槽,不可多定義或少定義

//任務(wù)的棧指針
unsigned char idata task_sp[MAX_TASKS];

#define MAX_TASK_DEP 12 //最大棧深.最低不得少于2個,保守值為12.
//預(yù)估方法:以2為基數(shù),每增加一層函數(shù)調(diào)用,加2字節(jié).如果其間可能發(fā)生中斷,則還要再加上中斷需要的棧深.
//減小棧深的方法:1.盡量少嵌套子程序 2.調(diào)子程序前關(guān)中斷.
unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任務(wù)堆棧.

unsigned char task_id;//當前活動任務(wù)號


//任務(wù)切換函數(shù)(任務(wù)調(diào)度器)
void task_switch(){
task_sp[task_id] = SP;

if(++task_id == MAX_TASKS)
task_id = 0;

SP = task_sp[task_id];
}

//任務(wù)裝入函數(shù).將指定的函數(shù)(參數(shù)1)裝入指定(參數(shù)2)的任務(wù)槽中.如果該槽中原來就有任務(wù),則原任務(wù)丟失,但系統(tǒng)本身不會發(fā)生錯誤.
void task_load(unsigned int fn, unsigned char tid){
task_sp[tid] = task_stack[tid] + 1; //
task_stack[tid][0] = (unsigned int)fn & 0xff; //低8位
task_stack[tid][1] = (unsigned int)fn >> 8; //高8位
}

//從指定的任務(wù)開始運行任務(wù)調(diào)度.調(diào)用該宏后,將永不返回.
#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}


/*============================以下為測試代碼============================*/


unsigned char stra[3], strb[3];//用于內(nèi)存塊復(fù)制測試的數(shù)組.


//測試任務(wù):復(fù)制內(nèi)存塊.每復(fù)制一個字節(jié)釋放CPU一次
void task1(){
//每復(fù)制一個字節(jié)釋放CPU一次,控制循環(huán)的變量必須考慮覆蓋
static unsigned char i;//如果將這個變量前的static去掉,會發(fā)生什么事?
i = 0;

while(1){//任務(wù)必須為死循環(huán),不得退出函數(shù),否則系統(tǒng)會崩潰
P1=0xff;//p1的燈滅
P2=0x00;//p2的燈亮
stra[i] = strb[i];
if(++i == sizeof(stra))
i = 0;

//變量i在這里跨越了task_switch(),因此它必須定義為靜態(tài)(static),否則它將會被其它進程修改,因為在另一個進程里也會用到該變量所占用的地址.
task_switch();//釋放CPU一會兒,讓其它進程有機會運行.如果去掉該行,則別的進程永遠不會被調(diào)用到
}
}

//測試任務(wù):復(fù)制內(nèi)存塊.每復(fù)制一個字節(jié)釋放CPU一次.
void task2(){
//每復(fù)制一個字節(jié)釋放CPU一次,控制循環(huán)的變量必須考慮覆蓋
static unsigned char i;//如果將這個變量前的static去掉,將會發(fā)生覆蓋問題.task1()和task2()會被編譯器分配到同一個內(nèi)存地址上,當兩個任務(wù)同時運行時,i的值就會被兩個任務(wù)改來改去
i = 0;

while(1){//任務(wù)必須為死循環(huán),不得退出函數(shù),否則系統(tǒng)會崩潰
P2=0xff;//p2的燈滅
P3=0x00;//p3的燈亮
stra[i] = strb[i];
if(++i == sizeof(stra))
i = 0;

//變量i在這里跨越了task_switch(),因此它必須定義為靜態(tài)(static),否則它將會被其它進程修改,因為在另一個進程里也會用到該變量所占用的地址.
task_switch();//釋放CPU一會兒,讓其它進程有機會運行.如果去掉該行,則別的進程永遠不會被調(diào)用到
}
}

//測試任務(wù):復(fù)制內(nèi)存塊.復(fù)制完所有字節(jié)后釋放CPU一次.
void task3(){
//復(fù)制全部字節(jié)后才釋放CPU,控制循環(huán)的變量不須考慮覆蓋
unsigned char i;//這個變量前不需要加static,因為在它的作用域內(nèi)并沒有釋放過CPU

while(1){//任務(wù)必須為死循環(huán),不得退出函數(shù),否則系統(tǒng)會崩潰

P3=0xff;//p3的燈滅
P1=0x00;//p1的燈亮
i = sizeof(stra);
do{
stra[i-1] = strb[i-1];
}while(--i);

//變量i在這里已完成它的使命,所以無需定義為靜態(tài).你甚至可以定義為寄存器型(regiter)
task_switch();//釋放CPU一會兒,讓其它進程有機會運行.如果去掉該行,則別的進程永遠不會被調(diào)用到
}
}

void main(){
//在這個示例里并沒有考慮任務(wù)的換入換出,所以任務(wù)槽必須全部用完,否則系統(tǒng)會崩潰.
//這里裝載了三個任務(wù),因此在定義MAX_TASKS時也必須定義為3
task_load(task1, 0);//將task1函數(shù)裝入0號槽
task_load(task2, 1);//將task2函數(shù)裝入1號槽
task_load(task3, 2);//將task3函數(shù)裝入2號槽

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉