從零開始學習UCOSII操作系統12--內存管理
前言:
在標準的C語言中,可以用malloc()和free()2個動態的分配內存和
釋放內存,但是在嵌入式中,調用malloc()和free()卻是非常危險的。
因爲多次調用這兩個函數,會把原來的很大的一塊連續的內存區域逐漸的分割成許多非常小的而且彼此又不相鄰的內存塊,也就是所謂的內存碎片。這樣子的話,使得程序後面連一段非常小的內存都分配不到,另外由於內存管理算法上的原因,malloc()和free()函數執行的時間是不確定的。
1、分區的概念:
操作系統把連續的大塊內存按分區來管理,每個分區中包含整數個大小相同的內存塊,利用這種機制,UCOSII對malloc和free函數進行了改進。使得他們可以得到和釋放固定的大小的內存塊。這樣子malloc和free函數的執行時間就是確定的了。(爲什麼?)
函數原型:void * malloc(unsigned int num_bytes);
//分配長度爲NUM_BYTES字節的內存塊。
返回值是void指針,void*表示未確定的類型的指針,明確說明,這個函數僅僅只是爲了申請內存的空間,而不是申請特定內存空間。
所以用戶可以根據需要把malloc申請到的內存,強制轉換成自己需要的那種模式。但是有一個缺點:malloc只管分配內存空間,並不對空間進行初始化的操作,所以申請到的內存的值是隨機分配的,經常會使用memset()進行置0的操作後再重新的使用。
(3)一個簡單的調用函數的實例:
int p;
p = (int )malloc(sizeof(int) 128);
//這裏需要檢查一下,是否分配成功了,分配不成功需要上報一個錯誤的值
double pd = (double )malloc(sizeof(double) 12);
free(p);
free(pd);
p =NULL;
pd = NULL;
指針賦值完後,需要賦值爲NULL是一個良好的習慣。
2、內存控制塊
爲了便於內存的管理,在UCOSII中使用內存控制塊的數據結構跟蹤每一個內存分區。系統中的每個內存分區都有它自己的內存控制塊。
OSMemAddr:
指向內存分區的起始地址的指針,她在建立內存分區初始化的時候,在此之後就不能更改了。
OSMemFreeList:
指向下一個空餘內存控制塊或者下一個空餘內存塊的指針,具體的含義應該要根據內存分區是否已經建立來決定。
OSMemBlkSize:
內存分區中內存塊的大小,是建立該內存分區時定義的。
OSMemNBlks:
內存分區中總的內存塊的數量,也是建立該內存分區時定義的。
OSMemNFree:
內存分區中當前可以獲得的空餘的內存塊的數量。
typedef struct
{
void * OSMemAddr;
void * OSMemFreeList;
INT32U OSMemBlkSize;
INT32U OSMemBlks;
INT32U OSMemNFree;
}OS_MEM;
3、使用內存管理機制:
建立一個內存分區:OSMEMCreate()
在使用一個內存分區之前,必須先建立該內存分區,這個操作可以通過調用函數OSMemCreate來完成,下面是一個創建100個內存塊並且每個內存塊大小爲32B的內存分區。
OS_MEM * CommTxBuf;
//在創建一塊內存分區之前,需要在棧中建立申請一塊內存。
INT8U CommTxPart[100][32];
void main(void)
{
INT8U err;
OSInit();
CommTxbuf = OSMemCreate(CommTxPart,100,32,&err);
OSStart();
}
申請一塊內存分區OSMemCreate()
OS_MEM OSMemCreate(void addr,
INT32U nblks,INT32U blksize,INT8U err)
{
/初始化定義一些 變量/
OS_MEM pmem;
INT8U pblk;
void *plink;
INT32U i;
pmem = OSMemFreeList;
if(OSMemFreeList != (OS_MEM *)0)
{
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
}
}
分配一個內存塊OSMemGet()
應用程序可以調用OSMemGet()函數,從已經建立的內存分區中申請一個內存塊,該函數的唯一的參數就是指向特定內存分區的指針。
void *OSMemGet(OS_MEM * pmem, INT8U *err)
{
void *pblk;
OS_ENTER_CRITICAL();
/* 如果內存空閒塊的大小大於0的話 */
if(pmem->OSMemNFree > 0)
{
pblk = pmem->OSMemFreeList;
pmem->OSMemFreeList = *(void **)pblk;
pmem->OSMemNFree--;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return (pblk);
}
OS_EXIT_CRITICAL();
*err = OS_MEM_NO_FREE_BLKS;
return ((void *)0);
}
PS:可以在中斷服務子程序中調用OSMemGet(),因爲在暫時沒有內存塊可用的情況下,OSMemGet()不會等待,而是立即返回NULL指針。
當然有請求一個內存塊,就會有一個釋放一塊內存塊:
OSMemPut()
當一個應用程序不再使用某個內存塊時,必須及時的把它釋放,病放回相應的內存分區中,這個操作由OSMEMPut函數完成。
必須注意的是OSMemPut()並不知道該內存塊時屬於哪個內存分區的,也就是說,如果用戶程序從一個包含32B內存塊的分區中分配了一塊內存塊,那麼用完之後,千萬不能返還一個包含120B內存塊的內存分區,因爲,當應用程序下一次申請120B分區中的一個內存塊中,它只會得到32B的可用空間。
其他的88B屬於其他的任務,這就有可能使得系統崩潰。
OSMemPut()
第一個參數是指向:OSMemput()第一個參數pmem是指向內存控制塊的。
pblk指向由pmem管理的內存塊。
OSMemPut()函數必須確保傳遞給它的參數指針非空的,但是遺憾的是,OSMemPut()並不知道要釋放的內存塊是否屬於此內存分區,因此,應用程序必須保證把內存塊釋放到合理的內存分區中。
INT8U OSMemPut(OS_MEM * pmem, void * pblk)
{
*(void **)pblk = pmem->OSMemFreeList;
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
4、查詢一個內存分區的狀態,OSMemQuery()
在UCOSII中可以使用OSMemQuery函數來查詢一個特定內存分區的有關信息。通過該函數尅知道特定內存分區中內存塊的大小,可用內存塊的數目,和已經使用了的內存塊數目等信息。
所有的信息都存放在一個叫做OS_MEM_DATA數據結構中。
typedef struct
{
void *OSAddr; //指向內存分區首地址的指針
void *OSFreeList; //指向空餘內存塊鏈表首地址的指針
INT32U OSBlkSize; //每個內存塊所含有的字節數
INT32U OSNBlks; //內存分區總的內存數
INT32U OSNFree; //空餘內存塊的總數
INT32U OSNUsed; //正在使用的內存塊總數
}OS_MEM_DATA;
5、使用內存分區,理解這個小實例的應用
UCOSII中的動態內存分配功能並利用它進行消息傳遞。
第一個任務讀取並檢查模擬量輸入的值(如氣壓、溫度以及電壓等)如果該值超過一定的閾值,就向第二個任務發送一條消息,該消息中含有出錯的信息,出錯的通道號以及其他的出錯的代碼。
AnalogInputTask()
{
for(;;)
{
for(所有模擬量的輸入) //不斷的輪訓來查詢任務一的模擬量
讀取模擬量的輸入值;
if(模擬量超過閾值)
{
得到一個內存塊,
得到當前系統的時間
將剛剛錯誤的幾項存入到內存塊裏面。
}
}
延時任務,直到再次對模擬量進行採樣時爲止。
}
ErrorHandlerTask()
{
for(;;)
{
等待錯誤處理隊列的消息
得到指向包含有關錯誤數據的內存塊的指針
讀入消息,並根據消息的內存執行相應的操作。
將內存塊放回到相應的內存分區中
}
}
6、等待內存分區中的一個內存塊
有時候,在內存分區暫時沒有可用的空餘內存塊的情況下,讓一個申請內存塊的任務等待也是可以的,但是UCOSII本身在內存管理上面並不支持這項功能,如果確實需要,則可以通過爲特定內存分區增加計數型信號量的方法,實現這個功能。
(2)應用程序爲了申請分配內存塊,首先得到相應的信號量,然後才能調用OSMemget()函數,如果需要釋放內存塊,只要將內存塊釋放到相應的內存分區中,並且發送一個信號量即可。
(3)顯然如果系統中只有一個任務使用動態內存塊的話,就沒有必要使用信號量了,因爲這種情況沒有必要使用信號量了,這種情況不需要保證內存資源的互斥,事實上,除非要實現多任務的共享內存,否則連內存分區都不需要。
(4)當一個任務運行的時候,只有在信號量有效的時候,才能得到內存塊,一旦信號量有效了,就可以申請內存塊並且使用它,兒沒有必要對OSSemPend()返回的錯誤代碼進行檢查。
因爲只有在這裏,只有當一個內存塊被其他的任務釋放病返回到內存分區的時候,UCOSII纔會返回到該任務中,使得繼續運行。
書中的小實例:
OS_EVENT * semaphorePtr;
OS_MEM * PartitionPtr;
INT8U partition[100][32];
OS_STK TaskStk[1000];
void main()
{
INT8U err;
OSInit();
SemaphorePtr = OSSemCreate(100);
PartitionPtr = OSMemCreate(Partition,100,32,&err);
OSTaskCreate(Task,(void *)0, &TaskStk[999], &err);
OSStart();
}
void Task(void * pada)
{
INT8U err;
INT8U *pblock;
for(;;)
{
//這裏使用了我請求一個信號後,然後我才釋放掉一個信號量
OSSemPend(Semaphore,0,&err);
pblock = OSMemGet(PartitionPtr, &err);
//使用內存塊,具體怎麼用,按照客戶的要求
OSMemPut(PartitionPtr, pblock);
OSSemPost(SemaphorePtr);
}
}