新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > DIY:給單片機(jī)寫個(gè)實(shí)時(shí)操作系統(tǒng)內(nèi)核!

DIY:給單片機(jī)寫個(gè)實(shí)時(shí)操作系統(tǒng)內(nèi)核!

作者: 時(shí)間:2016-11-29 來源:網(wǎng)絡(luò) 收藏

 
調(diào)度策略:實(shí)現(xiàn)了調(diào)度,還要繼續(xù)考慮調(diào)度策略,就是什么情況下需要調(diào)度哪些任務(wù)。調(diào)度策略分很多種,有興趣的可以去看那本《操作系統(tǒng)原理》,在我的源代碼里面使用了”搶占式優(yōu)先級(jí)調(diào)度+同一優(yōu)先級(jí)下時(shí)間片輪詢調(diào)度“的方法。
所謂搶占式優(yōu)先級(jí)調(diào)度是一種實(shí)時(shí)調(diào)度的方法,在實(shí)時(shí)操作系統(tǒng)中常用,這種方法的原理就是:操作系統(tǒng)在任何時(shí)候都要保證擁有最高優(yōu)先級(jí)的那個(gè)任務(wù)處于運(yùn)行態(tài),比如此記在運(yùn)行著優(yōu)先級(jí)為2的任務(wù),因?yàn)橐恍┬盘?hào)到達(dá),優(yōu)先級(jí)為1的那個(gè)任務(wù)解除了阻塞,處于就緒態(tài),這時(shí)操作系統(tǒng)就必須馬上停止任務(wù)2,切換到任務(wù)1,切換的這段時(shí)間需要越短越好。
而時(shí)間片輪詢即是讓每個(gè)任務(wù)都處于平等地位,然后給每個(gè)任務(wù)相同的時(shí)間片,當(dāng)一個(gè)任務(wù)的運(yùn)行時(shí)間用完了,操作系統(tǒng)就馬上切換給下一個(gè)需要執(zhí)行的任務(wù),這種方法的實(shí)時(shí)性不高,但它確保了每個(gè)任務(wù)都有相同的執(zhí)行時(shí)間。
我把這兩種方法結(jié)合起來,首先設(shè)定了8個(gè)優(yōu)先級(jí)組,每個(gè)優(yōu)先級(jí)組下面都用單向鏈表把具有相同優(yōu)先級(jí)的任務(wù)連接起來。這樣的話首先操作系統(tǒng)會(huì)查找最高優(yōu)先級(jí)的那組,然后在組里面輪流執(zhí)行所有任務(wù)(和UCOS II相比這種做法更具有靈活性,因?yàn)閁COS II只有搶占式調(diào)度,這是UCOS II的硬傷。。)。我聲明了一個(gè)任務(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號(hào)
* 5. OS_DataType_ThreadStatus ThreadStatus
* 線程運(yùn)行狀態(tài)
* 6. OS_DataType_PSW PSW
* 記錄線程的程序狀態(tài)寄存器
* 7. struct _OS_TCB *Front
* 指向上一個(gè)線程控制塊的指針
* 8. struct _OS_TCB *Next
* 指向下一人線程控制塊的指針
* 9.struct _OS_TCB *CommWaitNext ;
* 指向線程通信控制塊的指針
* 10.struct _OS_TCB *TimeWaitNext ;
* 指向延時(shí)等待鏈表的指針
* 11.OS_DataType_PreemptionPriority Priority ;
* 任務(wù)優(yōu)先級(jí)
* 12.OS_DataType_TimeDelay TimeDelay ;
* 任務(wù)延時(shí)時(shí)間
* @描述 : 定義線程控制塊的成員
* @建立時(shí)間 : 2011-11-15
* @最近修改時(shí)間: 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;

 
首先啟動(dòng)系統(tǒng)的時(shí)候需要先創(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ù)入口地址,接受一個(gè)空指針形式的輸入?yún)?shù),無返回參數(shù)
* 3.void *Argument 需要傳遞給線程的參數(shù),空指針形式
* @建立時(shí)間 : 2011-11-18
* @最近修改時(shí)間: 2011-11-18
*/
void OS_ThreadCreate(pOS_TCB ThreadControlBlock,void (*Thread)(void *),void *Argument)
關(guān)于創(chuàng)建任務(wù)的大致描述就是:填定線程控制塊,把線程控制塊鏈到單向鏈表中,設(shè)置人工堆棧,細(xì)節(jié)很多,就不一一贅述了。

 
當(dāng)前版本只實(shí)現(xiàn)了輪詢調(diào)度,還沒加上搶占調(diào)度,使用下面的函數(shù)就可以啟動(dòng)操作系統(tǒng)開始多線程任務(wù)!
/**
* @名稱 : 實(shí)時(shí)內(nèi)核引發(fā)函數(shù)
* @版本 : V 0.0
* @輸入?yún)?shù) : 無
* @輸出參數(shù) : 無
* @描述 : 在主函數(shù)中用于啟動(dòng),調(diào)用該函數(shù)后不會(huì)返回,直接切換到最高優(yōu)先級(jí)任務(wù)開始執(zhí)行
* @建立時(shí)間 : 2011-11-15
* @最近修改時(shí)間: 2011-11-15
*/
void OS_KernelStart(void)
{
OS_Status = OS_RUNNING ; //把內(nèi)核狀態(tài)設(shè)置為運(yùn)行態(tài)

 
//取得第一個(gè)需要運(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ù),不會(huì)再返回到這里
_asm("RET");
}

 
怎樣實(shí)現(xiàn)時(shí)間片?答案是用定時(shí)器定時(shí),每次定時(shí)器產(chǎn)生中斷的時(shí)候就轉(zhuǎn)換一次任務(wù),時(shí)基可以自己確定,一般來說時(shí)基越小的話會(huì)讓CPU花很多時(shí)間在切換任務(wù)上,降低了效率,時(shí)基大的話又使時(shí)間粒度變粗,會(huì)使一些程序得不到及時(shí)的執(zhí)行。我設(shè)定了每10MS中斷一次,就是說每一輪中每個(gè)線程都有10MS的執(zhí)行時(shí)間。具體算法不再贅述。

 
內(nèi)存管理策略
接下來要考慮怎樣管理內(nèi)存了!在PC里面編程的時(shí)候,如果需要開辟一個(gè)內(nèi)存空間,我們可以很容易地調(diào)用malloc()和free()來完成,但是在單片機(jī)里面卻行不通,因?yàn)橐獙?shí)現(xiàn)這兩個(gè)函數(shù)背后需要完成很多算法支持,從速度和空間上單片機(jī)都做不到。
在單片機(jī)里面如果你需要開辟內(nèi)存空間,你只有在編譯的時(shí)候就先定義好變量,無法動(dòng)態(tài)申請(qǐng),但是我們可以設(shè)計(jì)一個(gè)簡(jiǎn)單的內(nèi)存管理策略來實(shí)現(xiàn)這種動(dòng)態(tài)申請(qǐng)!原理就是在編譯的時(shí)候先向編譯器要一塊足夠大的內(nèi)存并且聲明為靜態(tài),然后把這塊空間交給內(nèi)存管理模塊來調(diào)用,內(nèi)存管理模塊負(fù)責(zé)分配這塊內(nèi)存,當(dāng)有任務(wù)要向它申請(qǐng)內(nèi)存的時(shí)候它就從里面拿出一塊交給任務(wù),而任務(wù)要釋放的時(shí)候就把該內(nèi)存空間交給內(nèi)存管理模塊來實(shí)現(xiàn)。
關(guān)于內(nèi)存管理也有很多種策略,在這里就不一一述說了,我在源代碼里面使用了一種簡(jiǎn)單的隨機(jī)分配的方法,即有線程申請(qǐng)的時(shí)候就從當(dāng)前內(nèi)存塊的可用空間里拿出一塊來,然后在內(nèi)存頭加上一個(gè)專用的結(jié)構(gòu)體,把每個(gè)內(nèi)存塊都鏈接起來,這樣便于管理。當(dāng)線程釋放內(nèi)存的時(shí)候,就把內(nèi)存返回到內(nèi)存空間并跟其他空間的內(nèi)存塊合并起來等待線程再次調(diào)用。
/**
* @名稱 : 內(nèi)存塊申請(qǐng)函數(shù)
* @版本 : V 0.0
* @輸入?yún)?shù) : 1. OS_DataType_MemorySize MemorySize
需要申請(qǐng)內(nèi)存塊的大小
* @輸出參數(shù) : 1. void *
若申請(qǐng)成功,則返回可使用內(nèi)存塊首地址,否則返回NULL
* @描述 :
* @建立時(shí)間 : 2011-11-16
* @最近修改時(shí)間: 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)
{
//檢測(cè)該內(nèi)存塊是否存在
if(pmcb==NULL)
{
return NULL ;
}
//如果存在則檢測(cè)該內(nèi)存塊的使用狀態(tài)
else if( (pmcb->Status==OS_MEMORY_STATUS_IDLE) && (pmcb->Size >= MemorySize) )
{
//如果可用內(nèi)存塊大小剛好等于需要申請(qǐng)的大小
//則立即分配
if(pmcb->Size == MemorySize)
{
pmcb->Status=OS_MEMORY_STATUS_USING ;
OS_MemoryIdleCount -= MemorySize ;
return (OS_DataType_Memory *)pmcb + OS_MEMORY_SIZE ;
}
//若可用內(nèi)存塊大小大于需要申請(qǐng)的大小
//則進(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
需要申請(qǐng)內(nèi)存塊的大小
* @輸出參數(shù) : 1. void *
若申請(qǐng)成功,則返回可使用內(nèi)存塊首地址,否則返回NULL
* @描述 :
* @建立時(shí)間 : 2011-11-16
* @最近修改時(shí)間: 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

 
這種分配策略雖然實(shí)現(xiàn)簡(jiǎn)單,但是缺點(diǎn)就是容易產(chǎn)生內(nèi)存碎片,即隨著時(shí)間推移,可用內(nèi)存會(huì)越來越碎片化,最后導(dǎo)致想要申請(qǐng)足夠大的內(nèi)存塊都沒辦法。。。

 
/********************************************************************************/
至此,一個(gè)簡(jiǎn)單的單片機(jī)使用的操作系統(tǒng)模型就算完成了,應(yīng)用在AVR單片機(jī)中,下面進(jìn)入測(cè)試階段:
因?yàn)檫€沒有完成線程通信模塊還搶占式算法,所以目前只能執(zhí)行輪詢多任務(wù)操作。我寫了一個(gè)測(cè)試程序,就是創(chuàng)建三個(gè)流水燈程序(是不是覺得寫個(gè)操作系統(tǒng)就用來跑流水燈太浪費(fèi)了,哈哈),讓它們同時(shí)閃,在PROTEUS中仿真查看
在AVR STUDIO5開發(fā)環(huán)境中編寫,代碼如下:
#include "includes.h"
#include "OS_core.h"

 
#define STACK_SIZE 80 //定義每個(gè)任務(wù)的人工堆棧大小

 
//定義三個(gè)任務(wù)各自的人工堆棧
uint8_t Test1Stack[STACK_SIZE];
uint8_t Test2Stack[STACK_SIZE];
uint8_t Test3Stack[STACK_SIZE];

 
//定義三個(gè)任務(wù)各自的線程控制塊
OS_TCB Task1;
OS_TCB Task2;
OS_TCB Task3;

 
//線程1讓PB口閃爍
void Test1(void *p)
{
uint8_t i;
DDRB=0XFF;
PORTB=0xff;
SREG|=0X80;
while(1)
{
for(i=0;i<8;i++)PORTB=1<}

評(píng)論


技術(shù)專區(qū)

關(guān)閉