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