開局分割線(理解了以后的一番感概):一個(gè)很偶然的機(jī)會(huì)接觸到了一個(gè)全部用C語言寫的任務(wù)調(diào)度框架,剛開始覺得有點(diǎn)奇怪,因?yàn)楫?dāng)你用它創(chuàng)建任務(wù)的時(shí)候,不能用switch 不能用return,局部變量還得變成static,想想寫代碼就有點(diǎn)別扭,深入學(xué)習(xí)了一下,的確很有意思,對(duì)RTOS的機(jī)制實(shí)現(xiàn)的理解,忽而有種推門而入的感覺。
好多童鞋剛?cè)腴T單片機(jī)變成,還在裸跑階段的時(shí)候,對(duì)調(diào)度器三個(gè)字可能一頭霧水,這玩意兒到底干嘛用的,舉個(gè)例子,寫個(gè)led點(diǎn)燈的函數(shù):
int func(void)
{
led1_on; //實(shí)現(xiàn)led1燈亮
delayms(500); //延時(shí)500ms,阻塞的方式
led1_off; //實(shí)現(xiàn)led1燈滅
}
這里引入了一個(gè)阻塞的概念,對(duì)應(yīng)的還有一個(gè)詞叫做非阻塞,關(guān)鍵就是延時(shí)500ms,那么執(zhí)行到這個(gè)函數(shù)的時(shí)候,程序不會(huì)繼續(xù)執(zhí)行,需要等待500ms:
非阻塞的方式,程序一直等在這,等到500ms實(shí)現(xiàn)到了,繼續(xù)執(zhí)行關(guān)燈。
阻塞的方式,程序會(huì)記錄下當(dāng)前函數(shù)的運(yùn)行位置,然后跳走去執(zhí)行別的函數(shù),當(dāng)500ms時(shí)間到時(shí),再從別的位置調(diào)到該函數(shù)led1_off這一條繼續(xù)執(zhí)行,而不是從函數(shù)頭部位置開始執(zhí)行。
裸機(jī)也能解決這個(gè)問題,但是要引入不少復(fù)雜的語法操作,這僅僅是一個(gè)函數(shù)的操作,一個(gè)程序不可能只有一個(gè)點(diǎn)燈的任務(wù),還會(huì)有多個(gè)任務(wù),當(dāng)我們把裸機(jī)執(zhí)行的操作抽象出來解決大部分問題的時(shí)候,小小調(diào)度器就出現(xiàn)了(他并不是很復(fù)雜的東東,其實(shí)最初的它只是為了解決一個(gè)阻塞式的delayms)。
unsigned short task0()
{
_SS //宏定義
while(1)
{
WaitX(50); //宏定義
LED0=!LED0;
}
_EE //宏定義
}
要實(shí)現(xiàn)阻塞,執(zhí)行WaitX(50);時(shí)跳出task0任務(wù),當(dāng)50個(gè)ticket到了以后,進(jìn)入函數(shù)從LED0=!LED0開始執(zhí)行,最關(guān)關(guān)關(guān)鍵(重要的事兒重復(fù)三遍)的就是這三個(gè)宏:
//任務(wù)頭
#define _SS static U8 _lc=0; switch(_lc){default:
//等待X個(gè)時(shí)鐘周期
#define WaitX(tickets) do { _lc=(__LINE__%255)+1; return (tickets) ;case (__LINE__%255)+1:;} while(0);
//任務(wù)尾
#define _EE ;}; _lc=0; return TICKET_MAX;
單看每一行都無法讀懂,不要擔(dān)心,只要記住三行注釋就可以了,接下來把他帶進(jìn)去展開后我們?cè)賮砜催@個(gè)函數(shù):
unsigned short task0()
{
static unsigned char _lc=0;
switch(_lc)
{
default://注意,其實(shí)這里用 switch(_lc){case 0: 也是一樣的。
while(1)//死循環(huán)
{
do
{
_lc=(__LINE__%255)+1; //記憶當(dāng)前行號(hào)
return 50 ; //函數(shù)返回,級(jí)任務(wù)0退出,任務(wù)主動(dòng)釋放 CPU。50是WaitX(50)的參數(shù)50。
case (__LINE__%255)+1://當(dāng) 500 毫秒過去后,會(huì)再次進(jìn)入 task0 任務(wù)函數(shù),執(zhí)行task0的switch(_lc),那么 switch 會(huì)去找 case101 的代碼。即此行。
;
} while(0);//只執(zhí)行一次的循環(huán)
LED0=!LED0;
} ;
};
_lc=0;
return 65535;
}
這看起來,總算像是一個(gè)完整的函數(shù)了,但是看到switch條件判斷被打散分布在程序里,第一感覺還是難以接受,default里面居然套上了case!!!,我們自己寫代碼如果這么寫,估計(jì)會(huì)被領(lǐng)導(dǎo)打,但是站在編譯器的角度來看,還是符合規(guī)則的,就是能完整執(zhí)行。
接下來看他的執(zhí)行流程,假如任務(wù)被調(diào)用,則從頭開始執(zhí)行,_lc為0,所以執(zhí)行default,這里把(__LINE__%255)+1當(dāng)作一個(gè)常數(shù),例如用100代替,那么接著執(zhí)行return 50;函數(shù)執(zhí)行結(jié)束了,這個(gè)50會(huì)返回給一個(gè)很特殊的定時(shí)器任務(wù),執(zhí)行50個(gè)tickets以后,再次主動(dòng)調(diào)用task0,從case 100開始執(zhí)行,led燈翻轉(zhuǎn),然后回到while(1) 執(zhí)行return50 跳走,整兒流程就是間隔50個(gè)tickets 翻轉(zhuǎn)LED輸出。
最后不得不感嘆雖然有點(diǎn)亂,但是的確神奇的完成了想要的工作,好像是程序里面寫了兩個(gè)bug,然后來了個(gè)負(fù)負(fù)得正,居然跑起來了~!??!