【@.1 指針與動態內存管理】
在C語言中的指針若不經過初始化是無法進行操作的,在編譯時甚至不會報錯,但是一旦運行到這里時就會出現程序錯誤死機。所以對於指針的操作一定要首先初始化再賦值。考慮如下代碼:
void foo1(void) { unsigned char * pdata; ... *pdata = 0x0f; }
當運行到pdata賦值時由於沒有初始化,程序必死。當然這還是比較明顯的錯誤,若指針被封裝在結構或聯合中就不用已發現了,比如如下代碼:
#include <stdio.h> typedef struct{ void * next; unsigned int data; }NodeType; NodeType *pNodes, Node1, Node2; int main(void) { Node1.next = &Node2; Node1.data = 0xff; pNodes->next=(void*)0; //Wrong! printf("hello!"); return 1; }
上面程序可能不會報錯,而且打印的東西跟pNodes沒關系,但實際運行程序會死掉,關鍵就是那句pNodes->node的賦值句,這里看上去是第一次給pNodes賦值,以為是初始化,但是實際上操作了一個沒有初始化的內存地址程序會死機。好的方式是想下面這樣寫主函數(其余部分一樣):
int main(void) { Node1.next = &Node2; Node1.data = 0xff; pNodes = &Node1; printf("%d",pNodes->data); return 1; }
這里直接將Node1的地址復制給pNodes,此時pNodes才能被正常初始化。所以一般我們在程序中會采取動態內存分配的方式申請內存空間,為指針型變量分配內存,比如如下代碼:
int main(void) { pNodes = (NodeType*)malloc(sizeof(NodeType)); pNodes->next = &Node1; printf("%d",pNodes->data); return 1; }
調用malloc函數之后就可以隨便操作這個指針變量了。參考這里可以看malloc的具體使用方法。但是這種malloc函數是一種平台相關函數,不同的CPU,不同的操作系統的具體實現內容不一樣。另外對於嵌入式系統來說,寫代碼時調用這類函數並不妥當,可能會使本來就吃緊的內存被分割成許多小而不連續的內存塊。因此若編寫嵌入式代碼,甚至是編寫PC代碼時想自己手動了解變量分配的機制並加以內存管理時,可以考慮自己編寫這類內存管理分配的代碼。以下的代碼就實現了這一功能,編程思想跟uCOS-II中的一模一樣,你可以看做是一個uCOS-II中內存管理的分析,但是這種機制完全可以在PC機上使用,而實際上我的代碼測試也是在window下完成的。如果你手上沒有一個純潔的C語言編譯器,可以參考我的這篇博客利用gcc來進行C語言編譯。
【@.2 uCOS-II中動態內存管理的C語言實現】
嵌入式編程比較關心的就是內存大小,在有限的內存中實現動態和靜態的代碼分配是有學問的。對於像malloc()這類在運行時從堆(heap)中請求內存的函數,若調用次數太多可能會造成內存的快速消耗殆盡。一個比較好的放大是,放棄使用malloc系列函數,將所有將要運行時分配的空間計算好,利用全局變量分配到靜態代碼空間中,實際使用時調用自己編寫好的內存管理函數從這塊靜態空間中申請內存。這樣的一個最大的好處是,內存的總體大小是受控的,所有涉及到動態分配內存的地方實際上都已經被預先分配好,在編譯時寫在靜態代碼區(利用全局變量)。若對於程序靜態和動態的內存需求還不清楚的可以參考我的這一篇博客有一個比較簡單但是通俗易懂的介紹。因此,第一步需要做的事就是申請一個全局變量存放將要動態分配的內存,比如下面的全局變量申明:
INT8U Buff8U [40][32]; //Store runtime data
INT16U Buff16U [40][32]; //Store runtime data
這里采用了二維數組。這篇博客利用一維數組構建了內存管理機制,可以參考。對於二維數組,左邊的可以看做分區,右邊的下標可以看做是在不同分區中的內存塊,但是整個數組在內存空間中還是線性分布的,可參考如下圖示:
每個這樣的二維數組就可以看成是一個連續的內存空間。如果程序要求層次結構不高,在程序中甚至可以僅僅包含這一個二維數組,只要計算好大小即可。
有了這樣的二維數組之后,還需要另一個內存管理塊來對這片內存進行管理,如下定義:
typedef struct{ void *EntryAddr; //Point to the addr of storage array void *FreeAddr; //Point to current free array INT32U BlkNum; //Number of blocks INT32U BlkSize; //Size of each block INT32U BlkFreeNum; //Free block number can be used }MMUType; MMUType *MMUBuff8; //Controller of Buff8U MMUType *MMUBuff16; //Controller of Buff16U
前面代碼中的兩個控制塊這里只畫了一個,每個內存管理塊中記錄了對應存儲的數組地址(EntryAddr),當前可用的數組的地址(FreeAddr),當內存進行分配時,這一位會進行偏移操作,指向還未被分配的存儲數組。而這些管理塊也需要一個總的數組來存儲其位置(MMUPool[ ]),否則這些指針也是無法初始化的指針。並且新建一個指針(*MMUFree)指向當前還未被分配的,可以用的管理塊數組中位置。
MMUType MMUPool[MMU_BUFF_MAX]; //Store all the MMUs MMUType *MMUFree; //Pointer to the available MMUType in MMUPool MMUType *MMUBuff8; //Controller of Buff8U MMUType *MMUBuff16; //Controller of Buff16U
圖中紅色的就是實際存儲的數組。可以看出核心思想是,所有需要實際存儲的指針都由數組來保存,所有指針最終均指向一個實際的變量。若要使用這種內存管理,第一步要做的是初始化MMUPool,調用MemInit()之后將內部所有的成員形成一個鏈表,由MMUFree指向最開頭的一個。
這樣一來就可以為內存管理塊分配一個實際的對象了。調用MemCreate()可以分配一個實際的對象給內存管理塊並且將內存管理塊與實際的內存存儲區聯系在一起
這一步之后就可以分配實際的內存給指針了,比如如下代碼:
void foo(void){
unsigned short *data;
unsigned char err;
data=(unsigned short*)Memloc(MMUBuff16,&err);
//…
}
當最后不用這篇內存時,一定要記得調用MemFree()回收這篇內存空間,不然這片內存空間將會越來越小導致無法再次使用這篇內存空間。所以這也是C語言沒有垃圾回收機制的一個弊端,雖然我們很多時候並沒有注意這件事,但是一旦程序編的很長,這些內存的細節就不得不注意了。
void foo(void){
unsigned short *data;
unsigned char err;
data=(unsigned short*)Memloc(MMUBuff16,&err);
//…
MemFree(MMUBuff16,data); //一定記得要回收內存空間到原控制塊
}
這種手動內存回收沒有重新清零,所以當下次再分配內存時將會把這篇包含數據的使用過的內存分配給新的對象,有可能造成內存泄露。而實際上在uCOS-II源代碼中,並沒有進行重新清零的操作,當然像工業控制中這類問題不是很重要,所以可以忽略。而若要編寫實際的操作系統,這種簡單的回收機制就不適用了。
整個完整的流程可以用下面的C代碼寫出,這里的實現思想基本跟uCOS-II中的一樣,但是可以在PC上進行編譯,這樣測試的效果也很明顯。注意有的C++編譯器可能會報錯(比如Visual Studio),可以參考我的這篇博客使用gcc的C編譯器來測試。
/******************************************************************************** * MMU.c * @author. apollius * @date. 03/25/2013 Mon * @brief. A tiny Memory Management controller in C, almost the same as that in * uCOS-II, demonstrate some basic ideas in memory management. * Successed in gcc toolchain ********************************************************************************/ #include <stdio.h> #define INT8U unsigned char #define INT16U unsigned short #define INT32U unsigned int #define MMU_BUFF_MAX 6 //The largest number to store MMUType typedef struct{ void *EntryAddr; //Point to the addr of storage array void *FreeAddr; //Point to current free array INT32U BlkNum; //Number of blocks INT32U BlkSize; //Size of each block INT32U BlkFreeNum; //Free block number can be used }MMUType; MMUType MMUPool[MMU_BUFF_MAX]; //Store all the MMUs MMUType *MMUFree; //Pointer to the available MMUType in MMUPool MMUType *MMUBuff8; //Controller of Buff8U MMUType *MMUBuff16; //Controller of Buff16U INT8U Buff8U [40][32]; //Store runtime data INT16U Buff16U [40][32]; //Store runtime data static void MemInit(void); static MMUType *MemCreate(void *buffaddr, INT32U blkn, INT32U blksize, INT8U * err); static void *Memloc(MMUType *pmem, INT8U *err); static INT8U MemFree(MMUType *pmem, void * pblk); int main(void) { INT8U err; INT16U *data; INT32U i; MemInit(); //Must be called first to use the runtime memory allocation err=0; MMUBuff16=MemCreate(Buff16U,60,32,&err); //Link MMUBuff16 with Buff16U data=(INT16U*)Memloc(MMUBuff16,&err); //malloc pointer *data for(i=0; i<32; i++) data[i]=i+2; for(i=0; i<32; i++) printf("%#04x ",data[i]); MemFree(MMUBuff16,data); //If not use, you must free it manually //Do not free data to other MMUcontroler like MMUBuff8, since it is malloced //from MMUBuff16. It may not cause error, but it's not a good habit printf("\r\n"); for(i=0; i<32; i++) printf("%#04x ",*((INT8U*)MMUBuff16->FreeAddr+i)); //Here can see while the MMUBuff16 is freed, it still contains datas -> memory leak return 1; } static void MemInit(void){ MMUType *pmmu; INT32U i; for(i=0;i<MMU_BUFF_MAX-1;i++){ MMUPool[i].EntryAddr = (void*)0; pmmu = &MMUPool[i+1]; MMUPool[i].FreeAddr = (void*)pmmu; //Point .FreeAddr to next MMUType*, it's different from the behavior in MemCreate() } MMUPool[MMU_BUFF_MAX].EntryAddr = (void*)0; MMUPool[MMU_BUFF_MAX].FreeAddr = (void*)0; MMUFree = &MMUPool[0]; //MMUFree point to the first pool } static MMUType *MemCreate(void * buffaddr, INT32U blkn, INT32U blksize, INT8U * err){ MMUType *pmem; void **plink; void *pblk; INT32U i; //If MMUPool is full, return void* 0, set *err=1 if(MMUFree->FreeAddr ==(void*)0){ *err = 1; return (MMUType*)0; } pmem = (MMUType*)MMUFree->FreeAddr; MMUFree = (MMUType*)MMUFree->FreeAddr; plink = (void**) buffaddr; pblk = (void* ) buffaddr + blksize; for(i=0; i<blkn-1; i++){ *plink = pblk; plink = (void**)pblk; pblk = pblk + blksize; } *plink = (void*)0; //Last node point to void* 0 pmem->EntryAddr = buffaddr; pmem->FreeAddr = buffaddr; pmem->BlkNum = blkn; pmem->BlkSize = blksize; pmem->BlkFreeNum = blkn; *err = 0; return pmem; } static void *Memloc(MMUType *pmem, INT8U *err){ void *pblk; if(pmem->BlkFreeNum>0){ pblk = pmem->FreeAddr; pmem->FreeAddr = *(void**)pblk; pmem->BlkFreeNum--; *err = 0; return pblk; } else{ *err=1; return (void*)0; } } static INT8U MemFree(MMUType *pmem, void * pblk){ if(pmem->BlkFreeNum >= pmem->BlkNum){ return 1; //error } *(void**)pblk = pmem->FreeAddr; pmem->FreeAddr = pblk; pmem->BlkFreeNum++; /*Note there is no re-clear memory function*/ return 0; }
@.[FIN] @.date->Mar 26, 2012 @.author->apollius