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