μC/OS-II的內(nèi)存管理
我們知道,在ANSIC中可以用malloc()和free()兩個(gè)函數(shù)動(dòng)態(tài)地分配內(nèi)存和釋放內(nèi)存。但
是,在嵌入式實(shí)時(shí)操作系統(tǒng)中,多次這樣做會(huì)把原來很大的一塊連續(xù)內(nèi)存區(qū)域,逐漸地分割成
許多非常小而且彼此又不相鄰的內(nèi)存區(qū)域,也就是內(nèi)存碎片。由于這些碎片的大量存在,使得
程序到后來連非常小的內(nèi)存也分配不到。在4.02節(jié)的任務(wù)堆棧中,我們講到過用malloc()函
數(shù)來分配堆棧時(shí),曾經(jīng)討論過內(nèi)存碎片的問題。另外,由于內(nèi)存管理算法的原因,malloc()和
free()函數(shù)執(zhí)行時(shí)間是不確定的。
在μC/OS-II中,操作系統(tǒng)把連續(xù)的大塊內(nèi)存按分區(qū)來管理。每個(gè)分區(qū)中包含有整數(shù)個(gè)大小相同
的內(nèi)存塊,如同圖F7.1。利用這種機(jī)制,μC/OS-II對(duì)malloc()和free()函數(shù)進(jìn)行了改進(jìn),使
得它們可以分配和釋放固定大小的內(nèi)存塊。這樣一來,malloc()和free()函數(shù)的執(zhí)行時(shí)間也是
固定的了。
如圖F7.2,在一個(gè)系統(tǒng)中可以有多個(gè)內(nèi)存分區(qū)。這樣,用戶的應(yīng)用程序就可以從不同的內(nèi)存
分區(qū)中得到不同大小的內(nèi)存塊。但是,特定的內(nèi)存塊在釋放時(shí)必須重新放回它以前所屬于的內(nèi)
存分區(qū)。顯然,采用這樣的內(nèi)存管理算法,上面的內(nèi)存碎片問題就得到了解決。
圖F7.1內(nèi)存分區(qū)——Figure7.1
圖F7.2多個(gè)內(nèi)存分區(qū)——Figure7.2
內(nèi)存控制塊
為了便于內(nèi)存的管理,在μC/OS-II中使用內(nèi)存控制塊(memorycONtrolblocks)的數(shù)據(jù)結(jié)構(gòu)來跟蹤每一個(gè)內(nèi)存分區(qū),系統(tǒng)中的每個(gè)內(nèi)存分區(qū)都有它自己的內(nèi)存控制塊。程序清單L7.1是內(nèi)存控制塊的定義。
程序清單L7.1內(nèi)存控制塊的數(shù)據(jù)結(jié)構(gòu)
typedefSTruct{
void*OSMemAddr;
void*OSMemFreeList;
INT32UOSMemBlkSize;
INT32UOSMemNBlks;
INT32UOSMemNFree;
}OS_MEM;
.OSMemAddr是指向內(nèi)存分區(qū)起始地址的指針。它在建立內(nèi)存分區(qū)[見7.1節(jié),建立一個(gè)內(nèi)存分區(qū),OSMemCreate()]時(shí)被初始化,在此之后就不能更改了。
.OSMemFreeList是指向下一個(gè)空閑內(nèi)存控制塊或者下一個(gè)空閑的內(nèi)存塊的指針,具體含義要根據(jù)該內(nèi)存分區(qū)是否已經(jīng)建立來決定[見7.1節(jié)]。
.OSMemBlkSize 是內(nèi)存分區(qū)中內(nèi)存塊的大小,是用戶建立該內(nèi)存分區(qū)時(shí)指定的[見7.1節(jié)]。
.OSMemNBlks是內(nèi)存分區(qū)中總的內(nèi)存塊數(shù)量,也是用戶建立該內(nèi)存分區(qū)時(shí)指定的[見7.1節(jié)]。
.OSMemNFree是內(nèi)存分區(qū)中當(dāng)前可以得空閑內(nèi)存塊數(shù)量。
如果要在μC/OS-II中使用內(nèi)存管理,需要在OS_CFG.H文件中將開關(guān)量OS_MEM_EN設(shè)置為1。這樣μC/OS-II在啟動(dòng)時(shí)就會(huì)對(duì)內(nèi)存管理器進(jìn)行初始化[由OSInit()調(diào)用OSMemInit()實(shí)現(xiàn)]。該初始化主要建立一個(gè)圖F7.3所示的內(nèi)存控制塊鏈表,其中的常數(shù)OS_MAX_MEM_PART(見文件OS_CFG.H)定義了最大的內(nèi)存分區(qū)數(shù),該常數(shù)值至少應(yīng)為2。
圖F7.3空閑內(nèi)存控制塊鏈表——Figure7.3
建立一個(gè)內(nèi)存分區(qū),OSMemCreate()在使用一個(gè)內(nèi)存分區(qū)之前,必須先建立該內(nèi)存分區(qū)。這個(gè)操作可以通過調(diào)用OSMemCreate()函數(shù)來完成。程序清單L7.2說明了如何建立一個(gè)含有100個(gè)內(nèi)存塊、每個(gè)內(nèi)存塊32字節(jié)的內(nèi)存分區(qū)。
程序清單L7.2建立一個(gè)內(nèi)存分區(qū)
OS_MEM*CommTxBuf;
INT8UCommTxPart[100][32];
voidmain(void)
{
INT8Uerr;
OSInit();
.
.
CommTxBuf=OSMemCreate(CommTxPart,100,32,&err);
.
.
OSStart();
}
程序清單L7.3是OSMemCreate()函數(shù)的源代碼。該函數(shù)共有4個(gè)參數(shù):內(nèi)存分區(qū)的起始地址、分區(qū)內(nèi)的內(nèi)存塊總塊數(shù)、每個(gè)內(nèi)存塊的字節(jié)數(shù)和一個(gè)指向錯(cuò)誤信息代碼的指針。如果OSMemCreate()操作失敗,它將返回一個(gè)NULL指針。否則,它將返回一個(gè)指向內(nèi)存控制塊的指針。對(duì)內(nèi)存管理的其它操作,象OSMemGet(),OSMemPut(),OSMemQuery()函數(shù)等,都要通過該指針進(jìn)行。
每個(gè)內(nèi)存分區(qū)必須含有至少兩個(gè)內(nèi)存塊[L7.3(1)],每個(gè)內(nèi)存塊至少為一個(gè)指針的大小,因?yàn)橥环謪^(qū)中的所有空閑內(nèi)存塊是由指針串聯(lián)起來的[L7.3(2)]。接著,OSMemCreate()從系統(tǒng)中的空閑內(nèi)存控制塊中取得一個(gè)內(nèi)存控制塊[L7.3(3)],該內(nèi)存控制塊包含相應(yīng)內(nèi)存分區(qū)的運(yùn)行信息。OSMemCreate()必須在有空閑內(nèi)存控制塊可用的情況下才能建立一個(gè)內(nèi)存分區(qū)[L7.3(4)]。在上述條件均得到滿足時(shí),所要建立的內(nèi)存分區(qū)內(nèi)的所有內(nèi)存塊被鏈接成一個(gè)單向的鏈表[L7.3(5)]。然后,在對(duì)應(yīng)的內(nèi)存控制塊中填寫相應(yīng)的信息[L7.3(6)]。完成上述各動(dòng)作后,OSMemCreate()返回指向該內(nèi)存塊的指針。該指針在以后對(duì)內(nèi)存塊的操作中使用[L7.3(6)]。
程序清單L7.3OSMemCreate()
OS_MEM*OSMemCreate(void*addr,INT32Unblks,INT32Ublksize,INT8U*err)
{
OS_MEM*pmem;
INT8U*pblk;
void**plink;
INT32Ui;
if(nblks<2){(1)
*err=OS_MEM_INVALID_BLKS;
return((OS_MEM*)0);
}
if(blksize
*err=OS_MEM_INVALID_SIZE;
return((OS_MEM*)0);
}
OS_ENTER_CRITICAL();
pmem=OSMemFreeList;(3)
if(OSMemFreeList!=(OS_MEM*)0){
OSMemFreeList=(OS_MEM*)OSMemFreeList->OSMemFreeList;
}
OS_EXIT_CRITICAL();
if(pmem==(OS_MEM*)0){(4)
*err=OS_MEM_INVALID_PART;
return((OS_MEM*)0);
}
plink=(void**)addr;(5)
pblk=(INT8U*)addr+blksize;[!--empirenews.page--]
for(i=0;i<(nblks-1);i++){
*plink=(void*)pblk;
plink=(void**)pblk;
pblk=pblk+blksize;
}
*plink=(void*)0;
OS_ENTER_CRITICAL();
pmem->OSMemAddr=addr;(6)
pmem->OSMemFreeList=addr;
pmem->OSMemNFree=nblks;
pmem->OSMemNBlks=nblks;
pmem->OSMemBlkSize=blksize;
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
return(pmem);(7)
}
圖F7.4是OSMemCreate()函數(shù)完成后,內(nèi)存控制塊及對(duì)應(yīng)的內(nèi)存分區(qū)和分區(qū)內(nèi)的內(nèi)存塊之間的關(guān)系。在程序運(yùn)行期間,經(jīng)過多次的內(nèi)存分配和釋放后,同一分區(qū)內(nèi)的各內(nèi)存塊之間的鏈接順序會(huì)發(fā)生很大的變化。
分配一個(gè)內(nèi)存塊,OSMemGet()應(yīng)用程序可以調(diào)用OSMemGet()函數(shù)從已經(jīng)建立的內(nèi)存分區(qū)中申請(qǐng)一個(gè)內(nèi)存塊。該函數(shù)的唯一參數(shù)是指向特定內(nèi)存分區(qū)的指針,該指針在建立內(nèi)存分區(qū)時(shí),由OSMemCreate()函數(shù)返回。顯然,應(yīng)用程序必須知道內(nèi)存塊的大小,并且在使用時(shí)不能超過該容量。例如,如果一個(gè)內(nèi)存分區(qū)內(nèi)的內(nèi)存塊為32字節(jié),那么,應(yīng)用程序最多只能使用該內(nèi)存塊中的32字節(jié)。當(dāng)應(yīng)用程序不再使用這個(gè)內(nèi)存塊后,必須及時(shí)把它釋放,重新放入相應(yīng)的內(nèi)存分區(qū)中[見7.03節(jié),釋放一個(gè)內(nèi)存塊,OSMemPut()]。
圖F7.4OSMemCreate()——Figure7.4
程序清單L7.4是OSMemGet()函數(shù)的源代碼。參數(shù)中的指針pmem指向用戶希望從其中分配內(nèi)存塊的內(nèi)存分區(qū)[L7.4(1)]。OSMemGet()首先檢查內(nèi)存分區(qū)中是否有空閑的內(nèi)存塊[L7.4(2)]。
如果有,從空閑內(nèi)存塊鏈表中刪除第一個(gè)內(nèi)存塊[L7.4(3)],并對(duì)空閑內(nèi)存塊鏈表作相應(yīng)的修改[L7.4(4)]。這包括將鏈表頭指針后移一個(gè)元素和空閑內(nèi)存塊數(shù)減1[L7.4(5)]。最后,返回指向被分配內(nèi)存塊的指針[L7.4(6)]。
程序清單L7.4OSMemGet()
void*OSMemGet(OS_MEM*pmem,INT8U*err)(1)
{
void*pblk;
OS_ENTER_CRITICAL();
if(pmem->OSMemNFree>0){(2)
pblk=pmem->OSMemFreeList;(3)
pmem->OSMemFreeList=*(void**)pblk;(4)
pmem->OSMemNFree--;(5)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
return(pblk);(6)
}else{
OS_EXIT_CRITICAL();
*err=OS_MEM_NO_FREE_BLKS;
return((void*)0);
}
}
值得注意的是,用戶可以在中斷服務(wù)子程序中調(diào)用OSMemGet(),因?yàn)樵跁簳r(shí)沒有內(nèi)存塊可用的情況下,OSMemGet()不會(huì)等待,而是馬上返回NULL指針。
釋放一個(gè)內(nèi)存塊,OSMemPut()
當(dāng)用戶應(yīng)用程序不再使用一個(gè)內(nèi)存塊時(shí),必須及時(shí)地把它釋放并放回到相應(yīng)的內(nèi)存分區(qū)中。這個(gè)操作由OSMemPut()函數(shù)完成。必須注意的是,OSMemPut()并不知道一個(gè)內(nèi)存塊是屬于哪個(gè)內(nèi)存分區(qū)的。例如,用戶任務(wù)從一個(gè)包含32字節(jié)內(nèi)存塊的分區(qū)中分配了一個(gè)內(nèi)存塊,用完后,把它返還給了一個(gè)包含120字節(jié)內(nèi)存塊的內(nèi)存分區(qū)。當(dāng)用戶應(yīng)用程序下一次申請(qǐng)120字節(jié)分區(qū)中的一個(gè)內(nèi)存塊時(shí),它會(huì)只得到32字節(jié)的可用空間,其它88字節(jié)屬于其它的任務(wù),這就有可能使系統(tǒng)崩潰。
程序清單L7.5是OSMemPut()函數(shù)的源代碼。它的第一個(gè)參數(shù)pmem是指向內(nèi)存控制塊的指針,也即內(nèi)存塊屬于的內(nèi)存分區(qū)[L7.5(1)]。OSMemPut()首先檢查內(nèi)存分區(qū)是否已滿[L7.5(2)]。如果已滿,說明系統(tǒng)在分配和釋放內(nèi)存時(shí)出現(xiàn)了錯(cuò)誤。如果未滿,要釋放的內(nèi)存塊被插入到該分區(qū)的空閑內(nèi)存塊鏈表中[L7.5(3)]。最后,將分區(qū)中空閑內(nèi)存塊總數(shù)加1[L7.5(4)]。
程序清單L7.5OSMemPut()
INT8UOSMemPut(OS_MEM*pmem,void*pblk)(1)
{
OS_ENTER_CRITICAL();
if(pmem->OSMemNFree>=pmem->OSMemNBlks){(2)
OS_EXIT_CRITICAL();
return(OS_MEM_FULL);
}
*(void**)pblk=pmem->OSMemFreeList;(3)
pmem->OSMemFreeList=pblk;
pmem->OSMemNFree++;(4)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
查詢一個(gè)內(nèi)存分區(qū)的狀態(tài),OSMemQuery()
在μC/OS-II中,可以使用OSMemQuery()函數(shù)來查詢一個(gè)特定內(nèi)存分區(qū)的有關(guān)消息。通過該函數(shù)可以知道特定內(nèi)存分區(qū)中內(nèi)存塊的大小、可用內(nèi)存塊數(shù)和正在使用的內(nèi)存塊數(shù)等信息。所有這些信息都放在一個(gè)叫OS_MEM_DATA的數(shù)據(jù)結(jié)構(gòu)中,如程序清單L7.6。
程序清單L7.6OS_MEM_DATA數(shù)據(jù)結(jié)構(gòu)
typedefstruct{
void*OSAddr;/*指向內(nèi)存分區(qū)首地址的指針*/
void*OSFreeList;/*指向空閑內(nèi)存塊鏈表首地址的指針*/
INT32UOSBlkSize;/*每個(gè)內(nèi)存塊所含的字節(jié)數(shù)*/
INT32UOSNBlks;/*內(nèi)存分區(qū)總的內(nèi)存塊數(shù)*/
INT32UOSNFree;/*空閑內(nèi)存塊總數(shù)*/
INT32UOSNUsed;/*正在使用的內(nèi)存塊總數(shù)*/
}OS_MEM_DATA;
程序清單L7.7是OSMemQuery()函數(shù)的源代碼,它將指定內(nèi)存分區(qū)的信息復(fù)制到OS_MEM_DATA定義的變量的對(duì)應(yīng)域中。在此之前,代碼首先禁止了外部中斷,防止復(fù)制過程中某些變量值被修改[L7.7(1)]。由于正在使用的內(nèi)存塊數(shù)是由 OS_MEM_DATA中的局部變量計(jì)算得到的,所以,可以放在(criticalsection中斷屏蔽)的外面。
程序清單L7.7OSMemQuery()
INT8UOSMemQuery(OS_MEM*pmem,OS_MEM_DATA*pdata)
{
OS_ENTER_CRITICAL();
pdata->OSAddr=pmem->OSMemAddr;(1)
pdata->OSFreeList=pmem->OSMemFreeList;
pdata->OSBlkSize=pmem->OSMemBlkSize;
pdata->OSNBlks=pmem->OSMemNBlks;
pdata->OSNFree=pmem->OSMemNFree;
OS_EXIT_CRITICAL();
pdata->OSNUsed=pdata->OSNBlks-pdata->OSNFree;(2)
return(OS_NO_ERR);
}
UsingMemoryPartitions[!--empirenews.page--]
圖F7.5是一個(gè)演示如何使用μC/OS-II中的動(dòng)態(tài)分配內(nèi)存功能,以及利用它進(jìn)行消息傳遞[見第6章]的例子。程序清單L7.8是這個(gè)例子中兩個(gè)任務(wù)的示意代碼,其中一些重要代碼的標(biāo)號(hào)和圖F7.5中括號(hào)內(nèi)用數(shù)字標(biāo)識(shí)的動(dòng)作是相對(duì)應(yīng)的。
第一個(gè)任務(wù)讀取并檢查模擬輸入量的值(如氣壓、溫度、電壓等),如果其超過了一定的閾值,就向第二個(gè)任務(wù)發(fā)送一個(gè)消息。該消息中含有時(shí)間信息、出錯(cuò)的通道號(hào)和錯(cuò)誤代碼等可以想象的任何可能的信息。
錯(cuò)誤處理程序是該例子的中心。任何任務(wù)、中斷服務(wù)子程序都可以向該任務(wù)發(fā)送出錯(cuò)消息。錯(cuò)誤處理程序則負(fù)責(zé)在顯示設(shè)備上顯示出錯(cuò)信息,在磁盤上登記出錯(cuò)記錄,或者啟動(dòng)另一個(gè)任務(wù)對(duì)錯(cuò)誤進(jìn)行糾正等。
圖F7.5使用動(dòng)態(tài)內(nèi)存分配——Figure7.5
程序清單L7.8內(nèi)存分配的例子——掃描模擬量的輸入和報(bào)告出錯(cuò)
AnalogInputTask()
{
for(;;){
for(所有的模擬量都有輸入){
讀入模擬量輸入值;(1)
if(模擬量超過閾值){
得到一個(gè)內(nèi)存塊;(2)
得到當(dāng)前系統(tǒng)時(shí)間(以時(shí)鐘節(jié)拍為單位);(3)
將下列各項(xiàng)存入內(nèi)存塊:(4)
系統(tǒng)時(shí)間(時(shí)間戳);
超過閾值的通道號(hào);
錯(cuò)誤代碼;
錯(cuò)誤等級(jí);
等.
向錯(cuò)誤隊(duì)列發(fā)送錯(cuò)誤消息;(5)
(一個(gè)指向包含上述各項(xiàng)的內(nèi)存塊的指針)
}
}
延時(shí)任務(wù),直到要再次對(duì)模擬量進(jìn)行采樣時(shí)為止;
}
}
ErrorHandlerTask()
{
for(;;){
等待錯(cuò)誤隊(duì)列的消息;(6)
(得到指向包含有關(guān)錯(cuò)誤數(shù)據(jù)的內(nèi)存塊的指針)
讀入消息,并根據(jù)消息的內(nèi)容執(zhí)行相應(yīng)的操作;(7)
將內(nèi)存塊放回到相應(yīng)的內(nèi)存分區(qū)中;(8)
}
}
等待一個(gè)內(nèi)存塊
有時(shí)候,在內(nèi)存分區(qū)暫時(shí)沒有可用的空閑內(nèi)存塊的情況下,讓一個(gè)申請(qǐng)內(nèi)存塊的任務(wù)等待也是有用的。但是,μC/OS-II本身在內(nèi)存管理上并不支持這項(xiàng)功能。如果確實(shí)需要,則可以通過為特定內(nèi)存分區(qū)增加信號(hào)量的方法,實(shí)現(xiàn)這種功能(見6.05節(jié),信號(hào)量)。應(yīng)用程序?yàn)榱松暾?qǐng)分配內(nèi)存塊,首先要得到一個(gè)相應(yīng)的信號(hào)量,然后才能調(diào)用OSMemGet()函數(shù)。整個(gè)過程見程序清單L7.9。
程序代碼首先定義了程序中使用到的各個(gè)變量[L7.9(1)]。該例中,直接使用數(shù)字定義了各個(gè)變量的大小,實(shí)際應(yīng)用中,建議將這些數(shù)字定義成常數(shù)。在系統(tǒng)復(fù)位時(shí),μC/OS-II調(diào)用OSInit()進(jìn)行系統(tǒng)初始化[L7.9(2)],然后用內(nèi)存分區(qū)中總的內(nèi)存塊數(shù)來初始化一個(gè)信號(hào)量[L7.9(3)],緊接著建立內(nèi)存分區(qū)[L7.9(4)]和相應(yīng)的要訪問該分區(qū)的任務(wù)[L7.9(5)]。當(dāng)然,到此為止,我們對(duì)如何增加其它的任務(wù)也已經(jīng)很清楚了。顯然,如果系統(tǒng)中只有一個(gè)任務(wù)使用動(dòng)態(tài)內(nèi)存塊,就沒有必要使用信號(hào)量了。這種情況不需要保證內(nèi)存資源的互斥。事實(shí)上,除非我們要實(shí)現(xiàn)多任務(wù)共享內(nèi)存,否則連內(nèi)存分區(qū)都不需要。多任務(wù)執(zhí)行從OSStart()開始[L7.9(6)]。當(dāng)一個(gè)任務(wù)運(yùn)行時(shí),只有在信號(hào)量有效時(shí)[L7.9(7)],才有可能得到內(nèi)存塊[L7.9(8)]。一旦信號(hào)量有效了,就可以申請(qǐng)內(nèi)存塊并使用它,而沒有必要對(duì)OSSemPend()返回
的錯(cuò)誤代碼進(jìn)行檢查。因?yàn)樵谶@里,只有當(dāng)一個(gè)內(nèi)存塊被其它任務(wù)釋放并放回到內(nèi)存分區(qū)后,
μC/OS-II才會(huì)返回到該任務(wù)去執(zhí)行。同理,對(duì)OSMemGet()返回的錯(cuò)誤代碼也無需做進(jìn)一步的檢查(一個(gè)任務(wù)能得以繼續(xù)執(zhí)行,則內(nèi)存分區(qū)中至少有一個(gè)內(nèi)存塊是可用的)。當(dāng)一個(gè)任務(wù)不再使用某內(nèi)存塊時(shí),只需簡單地將它釋放并返還到內(nèi)存分區(qū)[L7.9(9)],并發(fā)送該信號(hào)量[L7.9(10)]。
程序清單L7.9等待從一個(gè)內(nèi)存分區(qū)中分配內(nèi)存塊
OS_EVENT*SemaphorePtr;(1)
OS_MEM*PartitionPtr;
INT8UPartition[100][32];
OS_STKTaskStk[1000];
voidmain(void)
{
INT8Uerr;
OSInit();(2)
.
.
SemaphorePtr=OSSemCreate(100);(3)
PartitionPtr=OSMemCreate(Partition,100,32,&err);(4)
.
OSTaskCreate(Task,(void*)0,&TaskStk[999],&err);(5)
.
OSStart();(6)
}
voidTask(void*pdata)
{
INT8Uerr;
INT8U*pblock;
for(;;){
OSSemPend(SemaphorePtr,0,&err);(7)
pblock=OSMemGet(PartitionPtr,&err);(8)
.
./*使用內(nèi)存塊*/
.
OSMemPut(PartitionPtr,pblock);(9)
OSSemPost(SemaphorePtr);(10)
}
}