時間如沖沖流水,一轉(zhuǎn)眼間都畢業(yè)快一年了。這一年里忙忙碌碌,卻又碌碌無為。有時又總想,生亦何苦,死亦何哀。
也好久沒更新博文了,之前做了個STM8的脫機(jī)編程器,使用了EMWIN,學(xué)習(xí)到了人家的消息機(jī)制。覺得自己編程在無法移植系統(tǒng)的情況下能不能自己設(shè)計個模塊呢?就有了標(biāo)題中的思想。
以下是我在論壇上發(fā)的。
今天我也來個湊熱鬧講講操作系統(tǒng),理論嘛,人人都懂,所以不講了。
首先想問大家,平時在8位機(jī)上會用到操作系統(tǒng)嗎?還是一直都是裸奔?當(dāng)然了我一直都是在祼奔。
背景:
為什么要裝B還說要設(shè)計個OS,這么簡單還不人人都去設(shè)計。事實(shí)也的確如此,難。不知道各位有沒有遇到這樣的情況:
給公司寫軟件的時候,其核心內(nèi)容設(shè)計好后公司總會有一些需要特別功能的產(chǎn)品,但仍基于設(shè)計好的核心,因此需要將此COPY兩份,在其中一份中增加功能。如果公司這樣的產(chǎn)品較多,那么你的文件夾中出現(xiàn)各種復(fù)件,如果核心軟件優(yōu)化了將對所有文件全部一一優(yōu)化。自然有人說可以有預(yù)處理指令來增加功能模塊,但這樣的代碼還是難于維護(hù)。。
重點(diǎn)來了,當(dāng)軟件初步完成后,維護(hù)時往往并沒有設(shè)計時的激情,一是并沒有設(shè)計時的思路,二總想換個思路重新寫,或者壓根就不想再碰!我是這樣的,你呢?
因此我一直在找基于8位機(jī)的RTOS。
再后來我使用了EMWIN設(shè)計了一個產(chǎn)品,它的消息機(jī)制真的很棒,我就想能不能通過消息來驅(qū)動任務(wù)。emwin的消息是針對窗口對象的。我只要將各個任務(wù)看作對象不也應(yīng)該可以設(shè)計么?針對這樣的想法就開始了設(shè)計之路。
===============================================================
____: 初步想法:
給每個任務(wù)建立一個消息池,任務(wù)等待消息,等到消息后執(zhí)行該任務(wù)。消息由其它任務(wù)或中斷中給出。
____: 又來了想法:
如果同時有任務(wù)等到消息到底誰先運(yùn)行,所以在任務(wù)中建立優(yōu)先級,以使同時等到消息的任務(wù)中優(yōu)先級高的得到CPU的使用權(quán)?
那么這樣就得有一個調(diào)度器,在任務(wù)間進(jìn)行切換。
為此我給每個函數(shù)統(tǒng)一名稱叫消息進(jìn)程,以MP_xxx開頭。
即然用到優(yōu)先級就會想到ucos中優(yōu)先級查找機(jī)制,翻開ucos的書,其中一段讓我的想法步入了正軌——不可剝奪型內(nèi)核。
____: 什么是非搶占式優(yōu)先級調(diào)度操作系統(tǒng)也叫不可剝奪型內(nèi)核(來自ucosii ……)
不可剝奪型內(nèi)核要求每個任務(wù)自我放棄 CPU 的所有權(quán)。不可剝奪型調(diào)度法也稱作合作型多任務(wù),各個任務(wù)彼此合作共享一個 CPU。異步事件還是由中斷服務(wù)來處理。中斷服務(wù)可以使一個高優(yōu)先級的任務(wù)由掛起狀態(tài)變?yōu)榫途w狀態(tài)。但中斷服務(wù)以后控制權(quán)還是回到原來被中斷了的那個任務(wù), 直到該任務(wù)主動放棄 CPU 的使用權(quán)時,那個高優(yōu)先級的任務(wù)才能獲得 CPU的使用權(quán)。
不可剝奪型內(nèi)核的一個優(yōu)點(diǎn)是響應(yīng)中斷快。在討論中斷響應(yīng)時會進(jìn)一步涉及這個問題。在任務(wù)級,不可剝奪型內(nèi)核允許使用不可重入函數(shù)。 函數(shù)的可重入性以后會討論。每個任務(wù)都可以調(diào)用非可重入性函數(shù),而不必?fù)?dān)心其它任務(wù)可能正在使用該函數(shù), 從而造成數(shù)據(jù)的破壞。 因為每個任務(wù)要運(yùn)行到完成時才釋放 CPU 的控制權(quán)。 當(dāng)然該不可重入型函數(shù)本身不得有放棄 CPU 控制權(quán)的企圖。使用不可剝奪型內(nèi)核時,任務(wù)級響應(yīng)時間比前后臺系統(tǒng)快得多。 此時的任務(wù)級響應(yīng)時間取決于最長的任務(wù)執(zhí)行時間。
(省略部分請參閱ucos系統(tǒng)概念部分)
好吧,我臉皮子就厚了,將MP_XXX命名修改成OS_XXX,畢竟這也叫操作系統(tǒng)嘛。重新整理了思緒如下:
整個設(shè)計為了節(jié)約內(nèi)存,畢竟不是實(shí)時系統(tǒng)要求的不是響應(yīng)速度。
1、既然是非搶占式OS,那么每個任務(wù)必須執(zhí)行完后才會交出CPU的使用權(quán)。所以任務(wù)不應(yīng)該是超級循環(huán),并盡量控制任務(wù)執(zhí)行時間。
2、任務(wù)有就緒態(tài)、掛起態(tài),運(yùn)行態(tài)。
3、每一個任務(wù)需要等待被執(zhí)行的消息,可以接收多個消息,消息間可使用邏輯運(yùn)算and or進(jìn)行操作。并給每個任務(wù)分配消息等待超時時長。任務(wù)在規(guī)定時間內(nèi)未等到消息也將進(jìn)入就緒狀態(tài)。如果建立任務(wù)時即未給出等待消息也未給出超時時長則任務(wù)立即進(jìn)入就緒狀態(tài),否則進(jìn)入掛起態(tài)。
4、任務(wù)的調(diào)度由任務(wù)查找就緒表中最高優(yōu)先級的任務(wù)并執(zhí)行任務(wù),等待該任務(wù)執(zhí)行完后再重新調(diào)用調(diào)度器查找更高優(yōu)先級的任務(wù)。
5、需要在一個定時器刷新每個任務(wù)超時時間。并且就緒已超時的高優(yōu)先級的任務(wù)。當(dāng)然中斷返回后返回到被中斷的任務(wù),在該任務(wù)結(jié)束后才會執(zhí)行更高優(yōu)先級任務(wù)。
首先想問大家,平時在8位機(jī)上會用到操作系統(tǒng)嗎?還是一直都是裸奔?當(dāng)然了我一直都是在祼奔。
背景:
為什么要裝B還說要設(shè)計個OS,這么簡單還不人人都去設(shè)計。事實(shí)也的確如此,難。不知道各位有沒有遇到這樣的情況:
給公司寫軟件的時候,其核心內(nèi)容設(shè)計好后公司總會有一些需要特別功能的產(chǎn)品,但仍基于設(shè)計好的核心,因此需要將此COPY兩份,在其中一份中增加功能。如果公司這樣的產(chǎn)品較多,那么你的文件夾中出現(xiàn)各種復(fù)件,如果核心軟件優(yōu)化了將對所有文件全部一一優(yōu)化。自然有人說可以有預(yù)處理指令來增加功能模塊,但這樣的代碼還是難于維護(hù)。。
重點(diǎn)來了,當(dāng)軟件初步完成后,維護(hù)時往往并沒有設(shè)計時的激情,一是并沒有設(shè)計時的思路,二總想換個思路重新寫,或者壓根就不想再碰!我是這樣的,你呢?
因此我一直在找基于8位機(jī)的RTOS。
再后來我使用了EMWIN設(shè)計了一個產(chǎn)品,它的消息機(jī)制真的很棒,我就想能不能通過消息來驅(qū)動任務(wù)。emwin的消息是針對窗口對象的。我只要將各個任務(wù)看作對象不也應(yīng)該可以設(shè)計么?針對這樣的想法就開始了設(shè)計之路。
===============================================================
____: 初步想法:
給每個任務(wù)建立一個消息池,任務(wù)等待消息,等到消息后執(zhí)行該任務(wù)。消息由其它任務(wù)或中斷中給出。
____: 又來了想法:
如果同時有任務(wù)等到消息到底誰先運(yùn)行,所以在任務(wù)中建立優(yōu)先級,以使同時等到消息的任務(wù)中優(yōu)先級高的得到CPU的使用權(quán)?
那么這樣就得有一個調(diào)度器,在任務(wù)間進(jìn)行切換。
為此我給每個函數(shù)統(tǒng)一名稱叫消息進(jìn)程,以MP_xxx開頭。
即然用到優(yōu)先級就會想到ucos中優(yōu)先級查找機(jī)制,翻開ucos的書,其中一段讓我的想法步入了正軌——不可剝奪型內(nèi)核。
____: 什么是非搶占式優(yōu)先級調(diào)度操作系統(tǒng)也叫不可剝奪型內(nèi)核(來自ucosii ……)
不可剝奪型內(nèi)核要求每個任務(wù)自我放棄 CPU 的所有權(quán)。不可剝奪型調(diào)度法也稱作合作型多任務(wù),各個任務(wù)彼此合作共享一個 CPU。異步事件還是由中斷服務(wù)來處理。中斷服務(wù)可以使一個高優(yōu)先級的任務(wù)由掛起狀態(tài)變?yōu)榫途w狀態(tài)。但中斷服務(wù)以后控制權(quán)還是回到原來被中斷了的那個任務(wù), 直到該任務(wù)主動放棄 CPU 的使用權(quán)時,那個高優(yōu)先級的任務(wù)才能獲得 CPU的使用權(quán)。
不可剝奪型內(nèi)核的一個優(yōu)點(diǎn)是響應(yīng)中斷快。在討論中斷響應(yīng)時會進(jìn)一步涉及這個問題。在任務(wù)級,不可剝奪型內(nèi)核允許使用不可重入函數(shù)。 函數(shù)的可重入性以后會討論。每個任務(wù)都可以調(diào)用非可重入性函數(shù),而不必?fù)?dān)心其它任務(wù)可能正在使用該函數(shù), 從而造成數(shù)據(jù)的破壞。 因為每個任務(wù)要運(yùn)行到完成時才釋放 CPU 的控制權(quán)。 當(dāng)然該不可重入型函數(shù)本身不得有放棄 CPU 控制權(quán)的企圖。使用不可剝奪型內(nèi)核時,任務(wù)級響應(yīng)時間比前后臺系統(tǒng)快得多。 此時的任務(wù)級響應(yīng)時間取決于最長的任務(wù)執(zhí)行時間。
(省略部分請參閱ucos系統(tǒng)概念部分)
好吧,我臉皮子就厚了,將MP_XXX命名修改成OS_XXX,畢竟這也叫操作系統(tǒng)嘛。重新整理了思緒如下:
整個設(shè)計為了節(jié)約內(nèi)存,畢竟不是實(shí)時系統(tǒng)要求的不是響應(yīng)速度。
1、既然是非搶占式OS,那么每個任務(wù)必須執(zhí)行完后才會交出CPU的使用權(quán)。所以任務(wù)不應(yīng)該是超級循環(huán),并盡量控制任務(wù)執(zhí)行時間。
2、任務(wù)有就緒態(tài)、掛起態(tài),運(yùn)行態(tài)。
3、每一個任務(wù)需要等待被執(zhí)行的消息,可以接收多個消息,消息間可使用邏輯運(yùn)算and or進(jìn)行操作。并給每個任務(wù)分配消息等待超時時長。任務(wù)在規(guī)定時間內(nèi)未等到消息也將進(jìn)入就緒狀態(tài)。如果建立任務(wù)時即未給出等待消息也未給出超時時長則任務(wù)立即進(jìn)入就緒狀態(tài),否則進(jìn)入掛起態(tài)。
4、任務(wù)的調(diào)度由任務(wù)查找就緒表中最高優(yōu)先級的任務(wù)并執(zhí)行任務(wù),等待該任務(wù)執(zhí)行完后再重新調(diào)用調(diào)度器查找更高優(yōu)先級的任務(wù)。
5、需要在一個定時器刷新每個任務(wù)超時時間。并且就緒已超時的高優(yōu)先級的任務(wù)。當(dāng)然中斷返回后返回到被中斷的任務(wù),在該任務(wù)結(jié)束后才會執(zhí)行更高優(yōu)先級任務(wù)。
即然發(fā)這個貼了自然是實(shí)現(xiàn)了,并不是連載,而是分開寫有層次感,看得不會累。系統(tǒng)的命名仿照UCOS,讓人不會那么陌生。
設(shè)計好的內(nèi)核有如下功能:
1、支持最多16個任務(wù),并且支持多少個任務(wù)就支持多少個優(yōu)先級。如果系統(tǒng)中規(guī)定只有5個任務(wù),那么只有0-4的優(yōu)先級,因為未使用鏈表,使用數(shù)組,由優(yōu)先級作為索引(只考慮節(jié)約內(nèi)存)。
2、系統(tǒng)在初始化時建立一個空閑任務(wù),優(yōu)先級最低,并且一直處于就緒態(tài),不得刪除,不然會出現(xiàn)沒有就緒任務(wù)時調(diào)試器會調(diào)用優(yōu)先級為0的任務(wù),不管該任務(wù)是否存在。后果無法預(yù)測。
3、系統(tǒng)可使能統(tǒng)計任務(wù),優(yōu)先級次低,用戶可設(shè)定調(diào)用周期,如果使用統(tǒng)計函數(shù),系統(tǒng)在初始化時會阻塞周期時長用于統(tǒng)計規(guī)定時間內(nèi)總空閑計數(shù)值。cpu使用率=1-本次采樣空閑計數(shù)器/周期內(nèi)總計數(shù)值(阻塞時獲得)
6、初次設(shè)計,沒有給任務(wù)分配參數(shù)。也沒有修改優(yōu)先級,有刪除任務(wù),但請不要在中斷中使用(如果刪除被中斷的任務(wù),中斷返回后將執(zhí)行一個不存在的任務(wù))。
即使是非搶占式操作系統(tǒng)也需要考慮重入問題,本文使用臨界來解決。
(待補(bǔ)充)
系統(tǒng)性能:
測試條件51單片機(jī)12MHZ仿真
如果在任務(wù)建立時就計算好就緒任務(wù)的位置則任務(wù)切換時間為固定時長48us。為了節(jié)約內(nèi)存并未計算好這些值,所以切換任務(wù)時長為52us。
更多測試請看下面更新。
思想大概就是這樣,接下來就是實(shí)現(xiàn)細(xì)節(jié):
========================================================================================
先看數(shù)據(jù)組成:
typedef struct
{
void (*Task)(void); //任務(wù)
OS_MSG OSTCBMsg; //任務(wù)所需要的消息 8位
OS_MSG OSTCBRecMsg; //任務(wù)接收到的消息
int OSTCBOverTime; //任務(wù)超時時長
int OSTCBCopyOverTime; //備份超時時長,或者叫影子
u8 OSTCBLogicl; //接收到的消息邏輯操作 and or
u8 OSTCBPrio; //任務(wù)優(yōu)先級
}OS_TCB;
任務(wù)控制塊,為了節(jié)約內(nèi)存未使用鏈表,結(jié)合之前描述的消息使任務(wù)就續(xù)再看一下各個數(shù)據(jù)的注釋大體能明白它們在做什么。
OS_TCB OSTCBTbl [OS_TASK_NUM]; //申請任務(wù)控制塊
u8 OSPrioTbl[OS_TASK_NUM]; //任務(wù)注冊池,為0/1分別代碼任務(wù)不存在和存在,刪除任務(wù)只是將該任務(wù)注冊池置0而已
========================================================================================
任務(wù)是如何建立的?初始化任務(wù)的各個參數(shù),并將任務(wù)地址傳到內(nèi)核,由內(nèi)核管理。
這里要注意的是哪些地方要進(jìn)入臨界,為什么?在注釋中也寫出了,
當(dāng)然也有可能有些未考慮到的。大家注重思想就好。
暫時沒有實(shí)現(xiàn)給任務(wù)傳遞參數(shù),和任務(wù)這間的通信,但不難實(shí)現(xiàn),就好像教科書上給出馬的框框就可以讓同學(xué)畫徐悲鴻的馬了。。。。
========================================================================================
OS_STA OS_CreateTask(void(*Task)(void),OS_MSG OSTCBMsg,char OSTCBLogicl,int OverTime,u8 OSTCBPrio)
{
u8 OS_ITSTATUS;
OS_TCB *PTcb=&OSTCBTbl[OSTCBPrio]; //使用指針,節(jié)省建立時間
OS_ENTER_CRITICAL(); //防止在任務(wù)在中斷中刪除了此任務(wù)(想實(shí)現(xiàn)中斷中不可刪除任務(wù))
if((OSPrioTbl[OSTCBPrio]==1)||(OSTCBPrio>=OS_TASK_NUM))
{
OS_EXIT_CRITICAL();
return OS_PRIO_INVALID; //該優(yōu)先級被注冊過,或者超過了任務(wù)數(shù)
}
OSPrioTbl[OSTCBPrio] = 1; //注冊任務(wù),進(jìn)入臨界,以防止被中斷函數(shù)注冊
PTcb->OSTCBMsg = OSTCBMsg; //需要接收到的消息
PTcb->OSTCBRecMsg = 0; //接收的消息
PTcb->OSTCBLogicl = OSTCBLogicl; //接收消息方式
PTcb->OSTCBOverTime = OverTime; //超時時間
PTcb->OSTCBCopyOverTime = OverTime; //備份超時時間
OS_EXIT_CRITICAL(); //以上參數(shù)在其它函數(shù)中都可能被修改,所以需要關(guān)中斷嘍
PTcb->Task = Task; //用戶任務(wù)
PTcb->OSTCBPrio = OSTCBPrio; //任務(wù)優(yōu)先級
if((PTcb->OSTCBMsg==0)&&(PTcb->OSTCBOverTime)==0)OS_TaskRdy(OSTCBPrio); //沒有要等待的事件或者時間則任務(wù)立即進(jìn)入就緒態(tài)
return OS_OK;
}
注釋詳盡(有嗎?),具體細(xì)節(jié)請下載代碼查看。
========================================================================================
既然稱這個為系統(tǒng),那么任務(wù)的調(diào)度自然是少不了,給它響亮的名字叫調(diào)度器,但這個函數(shù)叫任務(wù)切換。再給它包個皮就叫調(diào)度器了。
========================================================================================
OS_STA OS_TaskSw(void)
{
u8 OSTCBPrio,y;
u8 OS_ITSTATUS;
y=OSUnMapTbl[OSRdyGrp];
OSTCBPrio=(y<<2)+OSUnMapTbl[OSRdyTbl[y]]; //沒有任務(wù)時返回0,0是最高優(yōu)先級,使用同ucos,只是優(yōu)先級只有16個,節(jié)約內(nèi)存
OS_ENTER_CRITICAL(); //進(jìn)入臨界防止剛判斷任務(wù)存在就在中斷函數(shù)中被刪除
if((OSPrioTbl[OSTCBPrio]==0)||(OSTCBPrio>=OS_TASK_NUM))
{
OS_EXIT_CRITICAL();
return OS_PRIO_INVALID;
}
OSPrioSelf=OSTCBPrio; //當(dāng)前運(yùn)行的任務(wù)
OS_EXIT_CRITICAL();
OSTCBTbl[OSTCBPrio].Task(); //此處調(diào)用任務(wù)是開中斷的,如果此時來了中斷并將該任務(wù)刪除,返回到這里將執(zhí)行被刪除的任務(wù),
return OS_OK; //所以在中斷中不能掉用刪除任務(wù)函數(shù)
}
需要注意OSTCBTbl[OSTCBPrio].Task();調(diào)用任務(wù)后的中斷是打開的,當(dāng)運(yùn)行該任務(wù)時進(jìn)入中斷,在中斷中掛起該任務(wù),或者刪除該任務(wù),那么中斷返回后應(yīng)該去哪?所以在中斷中不允許使用掛起和刪除函數(shù),但未在軟件加入限制,也會在今后修改
========================================================================================
不管哪個系統(tǒng)都需要用戶編寫部分代碼。而非搶占式的不需要用戶修改寄存器SP PC LR等。但任務(wù)是需要心跳的,這部分是和硬件有關(guān)的,不同的處理器自然也不一樣。
這里是在51中結(jié)合protues仿真的,定時中斷為1ms1次,具體多少可以在OS_CONFIG.h中進(jìn)行設(shè)計每秒鐘的中斷次數(shù)。
========================================================================================
定時器的初始化函數(shù)則由用戶編寫。根據(jù)不同的MCU代碼自然是不一樣的。51如下:
void Timer_Init(void)
{
u16 TimerValue;
TimerValue=0xffff-1000000/OS_TICK_PRE_SEC; //使用定時方式1,最大時長65ms。所以用戶情況設(shè)置此值,在.h文件中
th=TimerValue>>8;
tl=TimerValue;
TMOD=0x01;
TH0=th;
TL0=tl;
ET0=1;
TR0=1;
EA=1;
}
OS_TICK_PRE_SEC 留給用戶設(shè)置的,即每秒應(yīng)該中斷的次數(shù)
void Timer_Exception(void) interrupt 1
{
TR0=0;
TH0=th;
TL0=tl;
OS_TimeMsgPost(); //刷新每個任務(wù)的超時時長,遞減的方式,并且就緒高優(yōu)先級任務(wù)
TR0=1;
}
void OS_TimeMsgPost(void)
{
u8 i;
u8 OS_ITSTATUS;
OS_TCB *Ptr=OSTCBTbl; //用指針可加快速度
for(i=0;i
{
OS_ENTER_CRITICAL();
if(Ptr->OSTCBOverTime>0) //超時值大于0時刷新變量
{
Ptr->OSTCBOverTime--; //防止其它任務(wù)將此值變成0后減成0xFF
OS_EXIT_CRITICAL();
if(Ptr->OSTCBOverTime==0)
{
OS_TaskRdy(i); //超時時間到,就緒對應(yīng)任務(wù)
}
}
OS_EXIT_CRITICAL();
Ptr++; //刷新到下一個任務(wù)的超時標(biāo)志
}
}
設(shè)計好的內(nèi)核有如下功能:
1、支持最多16個任務(wù),并且支持多少個任務(wù)就支持多少個優(yōu)先級。如果系統(tǒng)中規(guī)定只有5個任務(wù),那么只有0-4的優(yōu)先級,因為未使用鏈表,使用數(shù)組,由優(yōu)先級作為索引(只考慮節(jié)約內(nèi)存)。
2、系統(tǒng)在初始化時建立一個空閑任務(wù),優(yōu)先級最低,并且一直處于就緒態(tài),不得刪除,不然會出現(xiàn)沒有就緒任務(wù)時調(diào)試器會調(diào)用優(yōu)先級為0的任務(wù),不管該任務(wù)是否存在。后果無法預(yù)測。
3、系統(tǒng)可使能統(tǒng)計任務(wù),優(yōu)先級次低,用戶可設(shè)定調(diào)用周期,如果使用統(tǒng)計函數(shù),系統(tǒng)在初始化時會阻塞周期時長用于統(tǒng)計規(guī)定時間內(nèi)總空閑計數(shù)值。cpu使用率=1-本次采樣空閑計數(shù)器/周期內(nèi)總計數(shù)值(阻塞時獲得)
6、初次設(shè)計,沒有給任務(wù)分配參數(shù)。也沒有修改優(yōu)先級,有刪除任務(wù),但請不要在中斷中使用(如果刪除被中斷的任務(wù),中斷返回后將執(zhí)行一個不存在的任務(wù))。
即使是非搶占式操作系統(tǒng)也需要考慮重入問題,本文使用臨界來解決。
(待補(bǔ)充)
系統(tǒng)性能:
測試條件51單片機(jī)12MHZ仿真
如果在任務(wù)建立時就計算好就緒任務(wù)的位置則任務(wù)切換時間為固定時長48us。為了節(jié)約內(nèi)存并未計算好這些值,所以切換任務(wù)時長為52us。
更多測試請看下面更新。
思想大概就是這樣,接下來就是實(shí)現(xiàn)細(xì)節(jié):
========================================================================================
先看數(shù)據(jù)組成:
typedef struct
{
void (*Task)(void); //任務(wù)
OS_MSG OSTCBMsg; //任務(wù)所需要的消息 8位
OS_MSG OSTCBRecMsg; //任務(wù)接收到的消息
int OSTCBOverTime; //任務(wù)超時時長
int OSTCBCopyOverTime; //備份超時時長,或者叫影子
u8 OSTCBLogicl; //接收到的消息邏輯操作 and or
u8 OSTCBPrio; //任務(wù)優(yōu)先級
}OS_TCB;
任務(wù)控制塊,為了節(jié)約內(nèi)存未使用鏈表,結(jié)合之前描述的消息使任務(wù)就續(xù)再看一下各個數(shù)據(jù)的注釋大體能明白它們在做什么。
OS_TCB OSTCBTbl [OS_TASK_NUM]; //申請任務(wù)控制塊
u8 OSPrioTbl[OS_TASK_NUM]; //任務(wù)注冊池,為0/1分別代碼任務(wù)不存在和存在,刪除任務(wù)只是將該任務(wù)注冊池置0而已
========================================================================================
任務(wù)是如何建立的?初始化任務(wù)的各個參數(shù),并將任務(wù)地址傳到內(nèi)核,由內(nèi)核管理。
這里要注意的是哪些地方要進(jìn)入臨界,為什么?在注釋中也寫出了,
當(dāng)然也有可能有些未考慮到的。大家注重思想就好。
暫時沒有實(shí)現(xiàn)給任務(wù)傳遞參數(shù),和任務(wù)這間的通信,但不難實(shí)現(xiàn),就好像教科書上給出馬的框框就可以讓同學(xué)畫徐悲鴻的馬了。。。。
========================================================================================
OS_STA OS_CreateTask(void(*Task)(void),OS_MSG OSTCBMsg,char OSTCBLogicl,int OverTime,u8 OSTCBPrio)
{
u8 OS_ITSTATUS;
OS_TCB *PTcb=&OSTCBTbl[OSTCBPrio]; //使用指針,節(jié)省建立時間
OS_ENTER_CRITICAL(); //防止在任務(wù)在中斷中刪除了此任務(wù)(想實(shí)現(xiàn)中斷中不可刪除任務(wù))
if((OSPrioTbl[OSTCBPrio]==1)||(OSTCBPrio>=OS_TASK_NUM))
{
OS_EXIT_CRITICAL();
return OS_PRIO_INVALID; //該優(yōu)先級被注冊過,或者超過了任務(wù)數(shù)
}
OSPrioTbl[OSTCBPrio] = 1; //注冊任務(wù),進(jìn)入臨界,以防止被中斷函數(shù)注冊
PTcb->OSTCBMsg = OSTCBMsg; //需要接收到的消息
PTcb->OSTCBRecMsg = 0; //接收的消息
PTcb->OSTCBLogicl = OSTCBLogicl; //接收消息方式
PTcb->OSTCBOverTime = OverTime; //超時時間
PTcb->OSTCBCopyOverTime = OverTime; //備份超時時間
OS_EXIT_CRITICAL(); //以上參數(shù)在其它函數(shù)中都可能被修改,所以需要關(guān)中斷嘍
PTcb->Task = Task; //用戶任務(wù)
PTcb->OSTCBPrio = OSTCBPrio; //任務(wù)優(yōu)先級
if((PTcb->OSTCBMsg==0)&&(PTcb->OSTCBOverTime)==0)OS_TaskRdy(OSTCBPrio); //沒有要等待的事件或者時間則任務(wù)立即進(jìn)入就緒態(tài)
return OS_OK;
}
注釋詳盡(有嗎?),具體細(xì)節(jié)請下載代碼查看。
========================================================================================
既然稱這個為系統(tǒng),那么任務(wù)的調(diào)度自然是少不了,給它響亮的名字叫調(diào)度器,但這個函數(shù)叫任務(wù)切換。再給它包個皮就叫調(diào)度器了。
========================================================================================
OS_STA OS_TaskSw(void)
{
u8 OSTCBPrio,y;
u8 OS_ITSTATUS;
y=OSUnMapTbl[OSRdyGrp];
OSTCBPrio=(y<<2)+OSUnMapTbl[OSRdyTbl[y]]; //沒有任務(wù)時返回0,0是最高優(yōu)先級,使用同ucos,只是優(yōu)先級只有16個,節(jié)約內(nèi)存
OS_ENTER_CRITICAL(); //進(jìn)入臨界防止剛判斷任務(wù)存在就在中斷函數(shù)中被刪除
if((OSPrioTbl[OSTCBPrio]==0)||(OSTCBPrio>=OS_TASK_NUM))
{
OS_EXIT_CRITICAL();
return OS_PRIO_INVALID;
}
OSPrioSelf=OSTCBPrio; //當(dāng)前運(yùn)行的任務(wù)
OS_EXIT_CRITICAL();
OSTCBTbl[OSTCBPrio].Task(); //此處調(diào)用任務(wù)是開中斷的,如果此時來了中斷并將該任務(wù)刪除,返回到這里將執(zhí)行被刪除的任務(wù),
return OS_OK; //所以在中斷中不能掉用刪除任務(wù)函數(shù)
}
需要注意OSTCBTbl[OSTCBPrio].Task();調(diào)用任務(wù)后的中斷是打開的,當(dāng)運(yùn)行該任務(wù)時進(jìn)入中斷,在中斷中掛起該任務(wù),或者刪除該任務(wù),那么中斷返回后應(yīng)該去哪?所以在中斷中不允許使用掛起和刪除函數(shù),但未在軟件加入限制,也會在今后修改
========================================================================================
不管哪個系統(tǒng)都需要用戶編寫部分代碼。而非搶占式的不需要用戶修改寄存器SP PC LR等。但任務(wù)是需要心跳的,這部分是和硬件有關(guān)的,不同的處理器自然也不一樣。
這里是在51中結(jié)合protues仿真的,定時中斷為1ms1次,具體多少可以在OS_CONFIG.h中進(jìn)行設(shè)計每秒鐘的中斷次數(shù)。
========================================================================================
定時器的初始化函數(shù)則由用戶編寫。根據(jù)不同的MCU代碼自然是不一樣的。51如下:
void Timer_Init(void)
{
u16 TimerValue;
TimerValue=0xffff-1000000/OS_TICK_PRE_SEC; //使用定時方式1,最大時長65ms。所以用戶情況設(shè)置此值,在.h文件中
th=TimerValue>>8;
tl=TimerValue;
TMOD=0x01;
TH0=th;
TL0=tl;
ET0=1;
TR0=1;
EA=1;
}
OS_TICK_PRE_SEC 留給用戶設(shè)置的,即每秒應(yīng)該中斷的次數(shù)
void Timer_Exception(void) interrupt 1
{
TR0=0;
TH0=th;
TL0=tl;
OS_TimeMsgPost(); //刷新每個任務(wù)的超時時長,遞減的方式,并且就緒高優(yōu)先級任務(wù)
TR0=1;
}
void OS_TimeMsgPost(void)
{
u8 i;
u8 OS_ITSTATUS;
OS_TCB *Ptr=OSTCBTbl; //用指針可加快速度
for(i=0;i
{
OS_ENTER_CRITICAL();
if(Ptr->OSTCBOverTime>0) //超時值大于0時刷新變量
{
Ptr->OSTCBOverTime--; //防止其它任務(wù)將此值變成0后減成0xFF
OS_EXIT_CRITICAL();
if(Ptr->OSTCBOverTime==0)
{
OS_TaskRdy(i); //超時時間到,就緒對應(yīng)任務(wù)
}
}
OS_EXIT_CRITICAL();
Ptr++; //刷新到下一個任務(wù)的超時標(biāo)志
}
}
主要提高自己的編程樂趣。 ======================================================================================== 以下列出函數(shù),具體細(xì)節(jié)請查看源代碼: extern OS_STA OS_CreateTask(void(*Task)(void),OS_MSG OSTCBMsg,char OSTCBLogicl,int OverTime,u8 OSTCBPrio);//任務(wù)建立 extern OS_STA OS_DeleteTask(u8 OSTCBPrio); //刪除任務(wù) extern void OS_ResumeTime(u8 OSTCBPrio); //重新將恢復(fù)任務(wù)超時時長 extern OS_STA OS_TaskRdy(u8 OSTCBPrio)reentrant; //將任務(wù)就緒,不可重入,不同的編譯器可能處理不一樣 extern OS_MSG OS_MsgGet(u8 OSTCBPrio,OS_STA *err); //獲取指定任務(wù)消息 extern OS_STA OS_MsgPost(u8 OSTCBPrio,OS_MSG OSMsg); //發(fā)送消息給指定任務(wù),并判斷是否需要就緒 extern void OS_TimeMsgPost(void); //刷新超時時長,就緒超時的任務(wù),由定時器調(diào)用 extern OS_STA OS_TaskSuspend(u8 OSTCBPrio); //掛起任務(wù) extern OS_STA OS_MsgClear(u8 OSTCBPrio,OS_MSG OSMsg); //清除指定任務(wù)消息 extern OS_STA OS_TaskSw(void); //任務(wù)切換 extern OS_STA OS_TaskSched(void); //任務(wù)調(diào)度 extern void OS_Start(void); //開始系統(tǒng)運(yùn)行 extern OS_STA OS_Init(void); //系統(tǒng)初始化,用于建立空閑任務(wù)和統(tǒng)計任務(wù)(可選) extern void OS_StatInit(void); //統(tǒng)計任務(wù)初始化(需要使能) ======================================================================================== 沒有實(shí)踐就沒有發(fā)言權(quán):請看例子: sbit LED1=P2^0; sbit LED2=P2^1; sbit LED3=P2^2; #define TASK_PRI_LED1 0 //定義任務(wù)的優(yōu)先級 #define TASK_PRI_LED2 1 #define TASK_RPI_LED3 2 void LED1_Task(void) //任務(wù)1就是閃爍LED1,運(yùn)行它的條件由任務(wù)建立時給出,200ms 1次 { LED1=~LED1; OS_MsgPost(TASK_PRI_LED3,MBit1); //給LED3發(fā)送消息0x01 OS_TaskSuspend(OSPrioSelf); //將自身掛起 } void LED2_Task(void) //任務(wù)2就是閃爍LED2,運(yùn)行它的條件由任務(wù)建立時給出,200ms 1次 { LED2=~LED2; OS_MsgPost(TASK_PRI_LED3,MBit2); //給LED3發(fā)送消息0x02 OS_TaskSuspend(OSPrioSelf); //將自身掛起 } void LED3_Task(void) //任務(wù)3就是閃爍LED3,運(yùn)行它的條件由任務(wù)建立時給出,接收由任務(wù)1和任務(wù)2的消息才被調(diào)用 { LED3=~LED3; OS_TaskSuspend(OSPrioSelf); //將自身掛起 } void main(void) { OS_Init(); Timer_Init(); OS_CreateTask(LED1_Task, MBitNop, 0, 200, TASK_PRI_LED1); //200ms后進(jìn)入就緒態(tài) OS_CreateTask(LED2_Task, MBitNop, 0, 200, TASK_PRI_LED2); //200ms后進(jìn)入就緒態(tài) OS_CreateTask(LED3_Task, MBit1|MBit2, OS_AND, 0, TASK_PRI_LED3); //等到Mbit1且還要等到Mbit2即0x03。因為是OS_AND與操作 OS_Start(); } 任務(wù)3由任務(wù)1和任務(wù)2一起驅(qū)動,在任務(wù)1或者任務(wù)2中的任一1個OS_MsgPost注釋掉任務(wù)3將不會被執(zhí)行。燈自然也不會閃爍,雖然是三個獨(dú)立任務(wù),并且不是同時點(diǎn)亮,但確是同步閃爍。 一共運(yùn)行了四個任務(wù)(+空閑任務(wù))和1個1ms中斷的定時器的代碼量和運(yùn)行效果如下:1.5K左右的ROM空間和84字節(jié)的RAM空間。 |
-
代碼量
-
效果
========================================================================================
上面的例子可以看出任務(wù)是同步運(yùn)行的,但看不出CPU的效率,接下來的例子則是測試一下CPU的使用率,使用數(shù)碼管顯示,并且使用1個按鍵故意阻塞CPU查看效率
一共運(yùn)行了7個任務(wù),至于ROM和RAM的空間后面將列舉(因為統(tǒng)計任務(wù)和數(shù)碼顯示中有乘除運(yùn)算+數(shù)碼管取碼等使ROM量大些)。
========================================================================================
接下來我們需要增加幾個任務(wù)了,統(tǒng)計任務(wù)+數(shù)碼管顯示+按鍵任務(wù)。
主要功能有:
數(shù)碼管顯示CPU的利用率,由按鍵按下阻塞任務(wù)的運(yùn)行查看CPU利用率的的變化。任務(wù)中去了1個LED1燈。實(shí)現(xiàn)如下:
sbit LED1=P2^0;
sbit LED2=P2^1;
sbit LED3=P2^2;
sbit KEY1=P2^3;
#define TASK_PRI_LED2 1 //定義優(yōu)先級
#define TASK_PRI_LED3 2
#define TASK_PRI_MAIN 3
#define TASK_PRI_TUBE 4
#define TASK_PRI_KEY1 0
unsigned char CONST distab[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8, //共陽數(shù)碼管段選碼表,無小數(shù)點(diǎn)
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff};
unsigned char UsageBuf[4];
//數(shù)碼管任務(wù),顯示CPU利用率
void Tube_Task(void)
{
static u8 Bsel=0x10,i=0;
UsageBuf[0]=0x0b;
UsageBuf[3]=CPUUsage/100; //取出CPU利用率
UsageBuf[2]=CPUUsage/10;
UsageBuf[1]=CPUUsage;
if(Bsel==0)Bsel=0x10;
if(i==4)i=0;
P3=0xff;
P1=Bsel;
P3=distab[UsageBuf[i++]];
Bsel<<=1;
OS_TaskSuspend(OSPrioSelf);
}
//點(diǎn)亮LED2,并發(fā)消息給LED3
void LED2_Task(void)
{
LED2=~LED2;
OS_MsgPost(TASK_PRI_LED3,MBit1); //給LED3發(fā)送消息
OS_TaskSuspend(OSPrioSelf); //將自身掛起
}
//按鍵任務(wù)阻塞CPU,并發(fā)消息給LED3
//有些狀態(tài)機(jī)的意思
void KEY1_Task(void)
{
u16 Hold;
static u8 Statue=0;
if(KEY1==0)
{
if(Statue<4)Statue++; //用于去抖動
}
else
{
Statue=0; //松手后
}
if(Statue>2) //簡單的去抖動
{
OS_MsgPost(TASK_PRI_LED3,MBit2); //給LED3發(fā)送消息
Hold=TL0+1000; //阻塞CPU,理論上是一隨機(jī)數(shù),但長按后則是一常數(shù)
while(Hold--);
}
OS_TaskSuspend(OSPrioSelf); //將自身掛起
}
//接收消息才運(yùn)行,現(xiàn)象即為按鍵長按后同LED1同時閃爍
void LED3_Task(void)
{
LED3=~LED3;
OS_TaskSuspend(OSPrioSelf);
}
//開始任務(wù),在采樣CPU空閑時總計數(shù)值后運(yùn)行,超時時長為采樣時長,這里取200,也可取1S UCOS為1S
void Start_Task(void)
{
#if IS_ENABLE_STAT
OS_StatInit(); //采樣1s中空閑任務(wù)的計數(shù)值
#endif
OS_CreateTask(LED2_Task, MBitNop, 0, 200, TASK_PRI_LED2); //每400ms閃爍一次
OS_CreateTask(LED3_Task, MBit1|MBit2,OS_AND, 0, TASK_PRI_LED3); //等待兩個消息同時有效才被運(yùn)行,即按鍵按下LED2任務(wù)被執(zhí)行
OS_CreateTask(Tube_Task, MBitNop, 0, 5, TASK_PRI_TUBE); //5ms刷新1位數(shù)碼管,刷新四位需要20ms
OS_CreateTask(KEY1_Task, MBitNop, 0, 20, TASK_PRI_KEY1); //按鍵任務(wù)20ms對按鍵進(jìn)行一次掃描
OS_DeleteTask(OSPrioSelf); //刪除本任務(wù)
}
void main( void )
{
OS_Init();
Timer_Init();
#if IS_ENABLE_STAT
OS_CreateTask(Start_Task,0,0,STAT_SAMP_TIME,TASK_PRI_MAIN); //用于統(tǒng)計任務(wù) STAT_SAMP_TIME后再注冊用戶軟件
#else
OS_CreateTask(Start_Task,0,0, 0,TASK_PRI_MAIN);//創(chuàng)建開始任務(wù),主要為了在調(diào)用OS_Start()后建立用戶任務(wù)
#endif //費(fèi)1個任務(wù)內(nèi)存,但統(tǒng)計任務(wù)必須在沒有其它任務(wù)運(yùn)行時先運(yùn)行
OS_Start(); //啟動任務(wù)
}
========================================================================================
任務(wù)中維持了1個統(tǒng)計任務(wù),在開機(jī)時阻塞STAT_SAMP_TIME時長獲取總空閑時長。再獲取后才能建立用戶任務(wù),所以建立了個Start_Task的任務(wù)用作等待。
========================================================================================
運(yùn)行了7個任務(wù)后的內(nèi)存使用情況 rom 2.2k ram 127b。
cpu的使用率如下(200ms進(jìn)行1次測試):
-
容量
-
沒有阻塞時7個任務(wù)的CPU使用率
-
阻塞后的CPU使用率
依舊懷念學(xué)校課堂上點(diǎn)亮LED燈的樂趣,學(xué)校用的東西都不便宜,13元左右的8位MCU。可以想想在學(xué)校的實(shí)驗儀上點(diǎn)亮led燈 運(yùn)行個數(shù)碼管再加上個按鍵這樣的系統(tǒng)cpu使用率其實(shí)不到10%,而大部分時間都在delay中。
所以并不是真去設(shè)計個什么OS。能夠在不可能移植到RTOS的情況下能不能換種編程思維,使軟件模塊化,提高編程樂趣,降低維護(hù)成本。
========================================================================================
改進(jìn):
這只是消息觸發(fā)任務(wù)就緒,任務(wù)在掛起時將會清除觸發(fā)事件和重新裝載超時時長。當(dāng)然還要增加功能如下:
給每個任務(wù)增加事件標(biāo)志,用于任務(wù)之間的通信(或都叫郵箱也行)。
給每個任務(wù)增加一參數(shù)指針
可以修改任務(wù)等待的消息
可以修改任務(wù)優(yōu)先級
在中斷中不能調(diào)用的函數(shù)在軟件上加上限制,而不是口頭約定(如掛起任務(wù)函數(shù),刪除任務(wù)函數(shù)等)。
當(dāng)然了這只是一個非搶占式的調(diào)度,用于低內(nèi)存的MCU管理用戶任務(wù),優(yōu)缺點(diǎn)也是共知的。所以不一定要多復(fù)雜。越簡單越好,夠用就好。在內(nèi)存足夠時,移植一RTOS才是最爽的。
說明:
當(dāng)然了,并不是說讓你用這個東西,而是提出一種思想。不是提到操作系統(tǒng)就會只想到搶占式。提到非搶占式只會想到書上的那幾道算法題——建立的任務(wù)的順序和任務(wù)優(yōu)先級,求任務(wù)調(diào)試次序。要是真來個中斷,掛起了某個任務(wù),順序不就變了么?哈哈。
祝大家好運(yùn)!!!
以上是針對非搶占式調(diào)度算法的設(shè)計,代碼并不完整。該調(diào)度也被我用到了產(chǎn)品中去。真的有在設(shè)計的時候就會有一種動力。樂趣是自己找出來的。
-