DIY:給單片機寫個實時操作系統(tǒng)內核!
調度策略:實現(xiàn)了調度,還要繼續(xù)考慮調度策略,就是什么情況下需要調度哪些任務。調度策略分很多種,有興趣的可以去看那本《操作系統(tǒng)原理》,在我的源代碼里面使用了”搶占式優(yōu)先級調度+同一優(yōu)先級下時間片輪詢調度“的方法。
所謂搶占式優(yōu)先級調度是一種實時調度的方法,在實時操作系統(tǒng)中常用,這種方法的原理就是:操作系統(tǒng)在任何時候都要保證擁有最高優(yōu)先級的那個任務處于運行態(tài),比如此記在運行著優(yōu)先級為2的任務,因為一些信號到達,優(yōu)先級為1的那個任務解除了阻塞,處于就緒態(tài),這時操作系統(tǒng)就必須馬上停止任務2,切換到任務1,切換的這段時間需要越短越好。
而時間片輪詢即是讓每個任務都處于平等地位,然后給每個任務相同的時間片,當一個任務的運行時間用完了,操作系統(tǒng)就馬上切換給下一個需要執(zhí)行的任務,這種方法的實時性不高,但它確保了每個任務都有相同的執(zhí)行時間。
我把這兩種方法結合起來,首先設定了8個優(yōu)先級組,每個優(yōu)先級組下面都用單向鏈表把具有相同優(yōu)先級的任務連接起來。這樣的話首先操作系統(tǒng)會查找最高優(yōu)先級的那組,然后在組里面輪流執(zhí)行所有任務(和UCOS II相比這種做法更具有靈活性,因為UCOS II只有搶占式調度,這是UCOS II的硬傷。。)。我聲明了一個任務結構體稱為線程控制塊,把關于該任務的所有狀態(tài)都放在一起:
/**
* @結構體聲明
* @名稱 : 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號
* 5. OS_DataType_ThreadStatus ThreadStatus
* 線程運行狀態(tài)
* 6. OS_DataType_PSW PSW
* 記錄線程的程序狀態(tài)寄存器
* 7. struct _OS_TCB *Front
* 指向上一個線程控制塊的指針
* 8. struct _OS_TCB *Next
* 指向下一人線程控制塊的指針
* 9.struct _OS_TCB *CommWaitNext ;
* 指向線程通信控制塊的指針
* 10.struct _OS_TCB *TimeWaitNext ;
* 指向延時等待鏈表的指針
* 11.OS_DataType_PreemptionPriority Priority ;
* 任務優(yōu)先級
* 12.OS_DataType_TimeDelay TimeDelay ;
* 任務延時時間
* @描述 : 定義線程控制塊的成員
* @建立時間 : 2011-11-15
* @最近修改時間: 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;
首先啟動系統(tǒng)的時候需要先創(chuàng)建任務,任務被創(chuàng)建之后才可以得到執(zhí)行,使用如下函數(shù):
/**
* @名稱:線程創(chuàng)建函數(shù)
* @輸入?yún)?shù):1.pOS_TCB ThreadControlBlock 線程控制塊結構體指針
* 2.void (*Thread)(void*) 線程函數(shù)入口地址,接受一個空指針形式的輸入?yún)?shù),無返回參數(shù)
* 3.void *Argument 需要傳遞給線程的參數(shù),空指針形式
* @建立時間 : 2011-11-18
* @最近修改時間: 2011-11-18
*/
void OS_ThreadCreate(pOS_TCB ThreadControlBlock,void (*Thread)(void *),void *Argument)
關于創(chuàng)建任務的大致描述就是:填定線程控制塊,把線程控制塊鏈到單向鏈表中,設置人工堆棧,細節(jié)很多,就不一一贅述了。
當前版本只實現(xiàn)了輪詢調度,還沒加上搶占調度,使用下面的函數(shù)就可以啟動操作系統(tǒng)開始多線程任務!
/**
* @名稱 : 實時內核引發(fā)函數(shù)
* @版本 : V 0.0
* @輸入?yún)?shù) : 無
* @輸出參數(shù) : 無
* @描述 : 在主函數(shù)中用于啟動,調用該函數(shù)后不會返回,直接切換到最高優(yōu)先級任務開始執(zhí)行
* @建立時間 : 2011-11-15
* @最近修改時間: 2011-11-15
*/
void OS_KernelStart(void)
{
OS_Status = OS_RUNNING ; //把內核狀態(tài)設置為運行態(tài)
//取得第一個需要運行的任務
OS_CurrentThread = OS_TCB_PriorityGroup[pgm_read_byte(ThreadSearchTab + OS_PreemptionPriority)].OS_TCB_Current;
OS_LastThread = NULL ;
//SP指針指向該任務的棧頂
SP = (uint16_t)OS_CurrentThread->ThreadStackTop ;
//使用出棧操作
POP_REG();
//調用RET,調用之后開始執(zhí)行任務,不會再返回到這里
_asm("RET");
}
怎樣實現(xiàn)時間片?答案是用定時器定時,每次定時器產生中斷的時候就轉換一次任務,時基可以自己確定,一般來說時基越小的話會讓CPU花很多時間在切換任務上,降低了效率,時基大的話又使時間粒度變粗,會使一些程序得不到及時的執(zhí)行。我設定了每10MS中斷一次,就是說每一輪中每個線程都有10MS的執(zhí)行時間。具體算法不再贅述。
內存管理策略
接下來要考慮怎樣管理內存了!在PC里面編程的時候,如果需要開辟一個內存空間,我們可以很容易地調用malloc()和free()來完成,但是在單片機里面卻行不通,因為要實現(xiàn)這兩個函數(shù)背后需要完成很多算法支持,從速度和空間上單片機都做不到。
在單片機里面如果你需要開辟內存空間,你只有在編譯的時候就先定義好變量,無法動態(tài)申請,但是我們可以設計一個簡單的內存管理策略來實現(xiàn)這種動態(tài)申請!原理就是在編譯的時候先向編譯器要一塊足夠大的內存并且聲明為靜態(tài),然后把這塊空間交給內存管理模塊來調用,內存管理模塊負責分配這塊內存,當有任務要向它申請內存的時候它就從里面拿出一塊交給任務,而任務要釋放的時候就把該內存空間交給內存管理模塊來實現(xiàn)。
關于內存管理也有很多種策略,在這里就不一一述說了,我在源代碼里面使用了一種簡單的隨機分配的方法,即有線程申請的時候就從當前內存塊的可用空間里拿出一塊來,然后在內存頭加上一個專用的結構體,把每個內存塊都鏈接起來,這樣便于管理。當線程釋放內存的時候,就把內存返回到內存空間并跟其他空間的內存塊合并起來等待線程再次調用。
/**
* @名稱 : 內存塊申請函數(shù)
* @版本 : V 0.0
* @輸入?yún)?shù) : 1. OS_DataType_MemorySize MemorySize
需要申請內存塊的大小
* @輸出參數(shù) : 1. void *
若申請成功,則返回可使用內存塊首地址,否則返回NULL
* @描述 :
* @建立時間 : 2011-11-16
* @最近修改時間: 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 ;
//進入內存搜索算法
while(1)
{
//檢測該內存塊是否存在
if(pmcb==NULL)
{
return NULL ;
}
//如果存在則檢測該內存塊的使用狀態(tài)
else if( (pmcb->Status==OS_MEMORY_STATUS_IDLE) && (pmcb->Size >= MemorySize) )
{
//如果可用內存塊大小剛好等于需要申請的大小
//則立即分配
if(pmcb->Size == MemorySize)
{
pmcb->Status=OS_MEMORY_STATUS_USING ;
OS_MemoryIdleCount -= MemorySize ;
return (OS_DataType_Memory *)pmcb + OS_MEMORY_SIZE ;
}
//若可用內存塊大小大于需要申請的大小
//則進行分割操作
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
內存釋放函數(shù):
/**
* @名稱 : 內存塊釋放函數(shù)
* @版本 : V 0.0
* @輸入?yún)?shù) : 1. OS_DataType_MemorySize MemorySize
需要申請內存塊的大小
* @輸出參數(shù) : 1. void *
若申請成功,則返回可使用內存塊首地址,否則返回NULL
* @描述 :
* @建立時間 : 2011-11-16
* @最近修改時間: 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 );
//將當前內存塊設置為空閑狀態(tài)
pmcb->Status=OS_MEMORY_STATUS_IDLE ;
OS_MemoryIdleCount += pmcb->Size ;
//如果存在上一塊內存塊,則進入判斷
if(pmcb->Front!=NULL)
{
//如果上一塊內存塊處于空閑狀態(tài),則進行合并操作
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 ;
}
}
//如果存在下一塊內存塊,則進入判斷
if(pmcb->Next!=NULL)
{
//如果下一塊內存塊處于空閑狀態(tài),則進行合并操作
if(pmcb->Next->Status==OS_MEMORY_STATUS_IDLE)
{
pmcb->Size += pmcb->Next->Size ;
pmcb->Next = pmcb->Next->Next ;
OS_MemoryIdleCount += pmcb->Size ;
}
}
}
#endif
這種分配策略雖然實現(xiàn)簡單,但是缺點就是容易產生內存碎片,即隨著時間推移,可用內存會越來越碎片化,最后導致想要申請足夠大的內存塊都沒辦法。。。
/********************************************************************************/
至此,一個簡單的單片機使用的操作系統(tǒng)模型就算完成了,應用在AVR單片機中,下面進入測試階段:
因為還沒有完成線程通信模塊還搶占式算法,所以目前只能執(zhí)行輪詢多任務操作。我寫了一個測試程序,就是創(chuàng)建三個流水燈程序(是不是覺得寫個操作系統(tǒng)就用來跑流水燈太浪費了,哈哈),讓它們同時閃,在PROTEUS中仿真查看
在AVR STUDIO5開發(fā)環(huán)境中編寫,代碼如下:
#include "includes.h"
#include "OS_core.h"
#define STACK_SIZE 80 //定義每個任務的人工堆棧大小
//定義三個任務各自的人工堆棧
uint8_t Test1Stack[STACK_SIZE];
uint8_t Test2Stack[STACK_SIZE];
uint8_t Test3Stack[STACK_SIZE];
//定義三個任務各自的線程控制塊
OS_TCB Task1;
OS_TCB Task2;
OS_TCB Task3;
//線程1讓PB口閃爍
void Test1(void *p)