1. 程式人生 > >malloc記憶體分配原理

malloc記憶體分配原理

一、malloc的工作機制
  它有一個將可用的記憶體塊連線為一個長長的列表的所謂空閒連結串列
  這裡寫圖片描述
  呼叫malloc函式時,它沿連線表尋找一個大到足以滿足使用者請求所需要的記憶體塊。然後,將該記憶體塊一分為二(一塊的大小與使用者請求的大小相等,另一塊的大小就是剩下的位元組)。接下來,將分配給使用者的那塊記憶體傳給使用者,並將剩下的那塊(如果有的話)返回到連線表上。
  呼叫free函式時,它將使用者釋放的記憶體塊連線到空閒鏈上。到最後,空閒鏈會被切成很多的小記憶體片段,如果這時使用者申請一個大的記憶體片段,那麼空閒鏈上可能沒有可以滿足使用者要求的片段了。於是,malloc函式請求延時,並開始在空閒鏈上翻箱倒櫃地檢查各記憶體片段,對它們進行整理,將相鄰的小空閒塊合併成較大的記憶體塊


  
  glibc維護了不止一個不定長的記憶體塊連結串列,而是好幾個,每一個這種連結串列負責一個大小範圍,這種做法有效減少了分配大記憶體時的遍歷開銷
  glibc另外的策略就是不止維護一類空閒連結串列,而是另外再維護一個緩衝連結串列一個高速緩衝連結串列
  在分配的時候首先在快取記憶體中查詢,失敗之後再在空閒連結串列查詢,如果找到的記憶體塊比較大,那麼將切割之後的剩餘記憶體塊插入到快取連結串列。
  如果空閒連結串列查詢失敗那麼就往快取連結串列中查詢. 如果還是沒有合適的空閒塊,就向記憶體申請比請求數更大的記憶體塊,然後把剩下的記憶體放入連結串列中。

  在對記憶體塊進行了 free 呼叫之後,我們需要做的是諸如將它們標記為未被使用的等事情,並且,在呼叫 malloc 時,我們要能夠定位未被使用的記憶體塊。因此, malloc返回的每塊記憶體的起始處首先要有這個結構:

struct mem_control_block {
    int is_available;
    int size;
};

這就解釋了,為什麼在程式中free之後,但是堆的記憶體還是沒有釋放。

二、sbrk & brk
  malloc所申請的記憶體主要從Heap區域分配(本文不考慮通過mmap申請大塊記憶體的情況)。

  程序所面對的虛擬記憶體地址空間,只有按頁對映到實體記憶體地址,才能真正使用。受物理儲存容量限制,整個堆虛擬記憶體空間不可能全部對映到實際的實體記憶體。Linux對堆的管理示意如下:
  這裡寫圖片描述

  Linux維護一個break指標,這個指標指向堆空間的某個地址。從堆起始地址到break之間的地址空間為對映好的,可以供程序訪問

;而從break往上,是未對映的地址空間,如果訪問這段空間則程式會報錯。
  由上文知道,要增加一個程序實際的可用堆大小,就需要將break指標向高地址移動Linux通過brk和sbrk系統呼叫操作break指標。兩個系統呼叫的原型如下:

int brk(void *addr);
void *sbrk(intptr_t increment);

  brk將break指標直接設定為某個地址,而sbrk將break從當前位置移動increment所指定的增量。brk在執行成功時返回0,否則返回-1並設定errno為ENOMEM;sbrk成功時返回break移動之前所指向的地址,否則返回(void *)-1。

  一個小技巧是,如果將increment設定為0,則可以獲得當前break的地址。

  另外需要注意的是,由於Linux是按頁進行記憶體對映的,所以如果break被設定為沒有按頁大小對齊,則系統實際上會在最後對映一個完整的頁,從而實際已對映的記憶體空間比break指向的地方要大一些。但是使用break之後的地址是很危險的(儘管也許break之後確實有一小塊可用記憶體地址)。
  
  注意大部份UNIX虛擬記憶體的使用是隻增不減的。


CODE:malloc(32 * 1024) --->;sbrk += 32 * 1024
free()    --->;sbrk 不減少。
但如如果再來一次
malloc(32 * 1024) ---->;sbrk 也不增,使用原有空間.
但對於LINUX來說它是要以記憶體的最大數收縮的;
CODE:<code>
a = malloc(32 * 1024) -->;sbrk += 32 * 1024
b = malloc(32 * 1024) -->;sbrk += 32 * 1024
if(****){
free(b); --->;sbrk -= 32 * 1024;
}
else{
free(a); --->;sbrk 不減少。只是多了個空洞.
}