簡述記憶體管理的原理和其簡單實現
我們平時在linux或者其他的嵌入式系統中經常會用到兩個函式,malloc和free。因為有這種動態記憶體分配的方法,所以給我們日常的程式設計帶來了很多的便捷之處。那麼,這兩個函式其工作機理是怎麼樣的呢?在這裡,我不想太過複雜地去闡述作業系統中其真正的執行過程,只是把這種動態記憶體分配的一些思路給說明一下,因為真正的動態記憶體分配是十分複雜的,我這裡只能簡單地描述其中的一種,並用C來把這種動態記憶體分配的方法給模擬出來。
malloc就是從一塊空間中開闢出有一部分能用的記憶體塊,返回給使用者的是這塊分配好的空間的首地址。而free正好相反,其作用是把分配好的空間重新歸還於原來的地方,就是我們常說的“釋放記憶體”。
相對free而言,malloc還是比較好理解,而free中的釋放又是怎麼一回事呢?簡單的來說,比如我們在一塊記憶體中佔用了其間的一塊空間,那麼此時作業系統中的某個機制就會把這個事件記錄下來,給已經分配給使用者的這塊空間作好標誌,防止下次再次分配的時候會涉及到這部分空間,造成指標越界。
而free的實現和其工作機理,要比malloc要複雜的多了。其要做的工作,不僅僅是把分配過的空間的標誌位給擦除掉,而且還要把記憶體中的碎片進行整合,這是非常必要的。記憶體中的碎片存在的越多,本來可分配的記憶體會變得越來越少,最後變得不可分割,從而導致malloc失敗,導致程式異常中斷。
可以用C來對上述的動態記憶體分配進行模擬實現。思路是,取一塊陣列,一部分可分配空間,一部分是用於記錄當前分配空間狀態的目錄,通過查詢目錄來獲取此時的記憶體分配情況,管理可分配空間,從而實現動態分配方法。實現程式碼如下:
#include<stdio.h>
#include<time.h>
#include<string.h>
#define MEMSIZE 10000 //總記憶體塊的大小
#define CATALOG_COUNT_MAX 100 //目錄的個數
char memory[MEMSIZE];
typedef struct mumm
{
void *start; /*指向當前分配空間的首地址*/
int used; /*分配的標誌位,1表示已分配,0表示空閒*/
int size; /*該目錄指向的空間的大小*/
}catalog;
catalog *memu ; //目錄指標
void init()
{
/* 定義好一串目錄連結串列,用於查詢分配,個數為100個。同時給目錄進行初始化 ,其中,第一個目錄指向的就是未分配的總記憶體大小*/
memu = (catalog *)(memory + MEMSIZE - sizeof(catalog)*CATALOG_COUNT_MAX);
memu[0].start = memory;
memu[0].used = 0;
memu[0].size = MEMSIZE - sizeof(catalog)*CATALOG_COUNT_MAX;
int i;
for(i = 1;i < CATALOG_COUNT_MAX; i++)
{
memu[i].start = NULL;
memu[i].used = 0 ;
memu[i].size = 0;
}
}
/* malloc 的實現方法 */
void *mymalloc(int size)
{
int i,j;
/*查詢所有目錄,看是否有空閒的資料塊*/
for(i = 0;i < CATALOG_COUNT_MAX; i++)
{
/*找到了一塊資料塊*/
if(memu[i].size >= size && memu[i].used == 0)
{
/*標記為使用中*/
memu[i].used = 1;
for(j = 0;j < CATALOG_COUNT_MAX; j++)
{
/*查詢目錄,尋找未有分配狀態的目錄*/
if(memu[j].used == 0 && memu[j].size == 0)
{
/*若找到,把該目錄指標置於已分配的空間的後面,並分配餘下空間大小,並返回*/
memu[j].start = memu[i].start + size;
memu[j].size = memu[i].size - size;
memu[i].size = size;
return memu[i].start;
}
}
break;
}
}
return memu[i].start;
}
/*free 的實現方法*/
void *myfree(void *p)
{
int i, j, k;
for(i = 0;i < CATALOG_COUNT_MAX; i++)
{
/*從目錄中尋找要釋放的記憶體,通過其指向的首地址和使用狀態來尋找*/
if(p == memu[i].start && memu[i].used == 1)
{
/*找到了,把標誌位清0。但此時工作並未結束,需要整理記憶體碎片*/
memu[i].used = 0;
for(j = 0; j < CATALOG_COUNT_MAX; j++)
{
/*尋找目錄,找到指向要釋放的空間的後一塊記憶體,如果該記憶體仍在使用中,則放棄合併,如果未使用,此時把後一塊空間合併到要釋放空間的記憶體中來,組成一塊新的大的記憶體塊*/
if((memu[i].start + memu[i].size == memu[j].start) && memu[j].used == 0)
{
memu[j].start = NULL;
memu[i].size = memu[j].size + memu[i].size;
memu[j].size = 0;
break;
}
}
for(k = 0;k < CATALOG_COUNT_MAX;k++)
{
/*同上,尋找目錄,找到指向要釋放的空間的前一塊記憶體,如果該記憶體仍在使用中,則放棄合併,如果未使用,此時把要釋放的空間合併到前一塊空間的記憶體中來,組成一塊新的大的記憶體塊*/
if((memu[k].start + memu[k].size == memu[i].start) && memu[k].used == 0 )
{
memu[i].start = NULL;
memu[k].size = memu[i].size + memu[k].size;
memu[i].size = 0;
break;
}
}
break;
}
}
}
/* 該主函式為壓力測試,如果夠完成9999次malloc和free,那麼表示該動態分配方法沒有問題 */
int main(int argc, char *argv[])
{
init();
int i;
char *pp[80];
int n;
char ch;
for(i = 0;i < 80;i++)
pp[i] = "-1";
srand(time(NULL));
for(i = 0;i < 10000;i++)
{
printf("i = %d\n",i);
n = random()%80;
if(strcmp(pp[n], "-1") == 0)
{
pp[n] = (char *)mymalloc(random()%100 + 1);
if(pp[n] == NULL)
break;
strcpy(pp[n], "a");
printf("malloc %d\t%x\n", n, pp[n]);
}
else
{
printf("free %d\t%x\n", n, pp[n]);
myfree(pp[n]);
pp[n] = "-1";
}
}/*
int *p = mymalloc(50);
int *a = mymalloc(100);
int *b = mymalloc(100);
int *c = mymalloc(100);
int *d = mymalloc(1000);
int *f = mymalloc(7450);
printf("%p\n",p);
printf("%p\n",a);
printf("%p\n",b);
printf("%p\n",c);
printf("%p\n",d);
myfree(a);
myfree(b);
int *e = mymalloc(150);
printf("%p\n",e);
int *g = mymalloc(100);
printf("%p\n",g);
int *h = mymalloc(100);
printf("%p\n",h);
int *i = mymalloc(100);
printf("%p\n",i);
*/
return 0;
}
其結果如下:
i表示malloc和free的次數,如malloc 60 8049339 表示malloc 60byte大小空間地址為0x8049339。