linux-malloc底層實現原理

本文大致講解一下linux下malloc的底層實現原理。

首先malloc肯定是從堆中分配內存,而堆又在用戶空間中佔據什麼位置?通過下面這張圖可以看出來:


很明顯是32位系統,尋址空間是4G,linux系統下0-3G是用戶模式,3-4G是內核模式。而在用戶模式下又分爲代碼段、數據段、.bss段、堆、棧。各個segment所含內容在圖中有具體說明。

其中bss段:存放未初始化的全局變量和局部靜態變量。數據段:存放已經初始化的全局變量和局部靜態變量。至於局部變量存放在棧中。


可以看到heap段位於bss下方,而其中有個重要的標誌:program breakLinux維護一個break指針,這個指針指向堆空間的某個地址。從堆起始地址到break之間的地址空間爲映射好的,可以供進程訪問;而從break往上,是未映射的地址空間,如果訪問這段空間則程序會報錯。我們用malloc進行內存分配就是從break往上進行的。

進程所面對的虛擬內存地址空間,只有按頁映射到物理內存地址,才能真正使用。受物理存儲容量限制,整個堆虛擬內存空間不可能全部映射到實際的物理內存。Linux對堆的管理示意如下:

Linux進程堆管理

  

獲取了break地址,也就是內存申請的初始地址,下面是malloc的整體實現方案

malloc 函數的實質是它有一個將可用的內存塊連接爲一個長長的列表的所謂空閒鏈表。 調用 malloc()函數時,它沿着連接表尋找一個大到足以滿足用戶請求所需要的內存塊。 然後,將該內存塊一分爲二(一塊的大小與用戶申請的大小相等,另一塊的大小就是剩下來的字節)。 接下來,將分配給用戶的那塊內存存儲區域傳給用戶,並將剩下的那塊(如果有的話)返回到連接表上。 調用 free 函數時,它將用戶釋放的內存塊連接到空閒鏈表上。 到最後,空閒鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段, 那麼空閒鏈表上可能沒有可以滿足用戶要求的片段了。於是,malloc()函數請求延時,並開始在空閒鏈表上檢查各內存片段,對它們進行內存整理,將相鄰的小空閒塊合併成較大的內存塊。

1、malloc分配內存前的初始化:

malloc_init 是初始化內存分配程序的函數。 它完成以下三個目的:將分配程序標識爲已經初始化[3],找到操作系統中最後一個有效的內存地址,然後建立起指向需要管理的內存的指針。這裏需要用到三個全局變量。

 malloc_init 分配程序的全局變量

int has_initialized = 0; /* 初始化標記 */

void *managed_memory_start; /* 管理內存起始地址 */

void *last_valid_address; /* 操作系統的最後一個有效地址*/

被映射的內存邊界(操作系統最後一個有效地址)常被稱爲系統中斷點或者當前中斷點。爲了指出當前系統中斷點,必須使用 sbrk(0) 函數。 sbrk 函數根據參數中給出的字節數移動當前系統中斷點,然後返回新的系統中斷點。 使用參數 0 只是返回當前中斷點。 這裏給出 malloc()初始化代碼,它將找到當前中斷點並初始化所需的變量:

Linux通過brk和sbrk系統調用操作break指針。兩個系統調用的原型如下:

  1. int brk(void *addr);
  2. void *sbrk(intptr_t increment);

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

2、下爲malloc_init()代碼:可以看到使用sbrk(0)來獲得break地址。

  1. #include <unistd.h> /*sbrk 函數所在的頭文件 */
  2. void malloc_init()
  3. {
  4. last_valid_address = sbrk(0); /* 用 sbrk 函數在操作系統中
  5. 取得最後一個有效地址 */
  6. managed_memory_start = last_valid_address; /* 將 最 後 一 個
  7. 有效地址作爲管理內存的起始地址 */
  8. has_initialized = 1; /* 初始化成功標記 */
  9. }

3、內存塊的獲取

所要申請的內存是由多個內存塊構成的鏈表。

a、內存塊的大致結構:每個塊由meta區和數據區組成,meta區記錄數據塊的元信息(數據區大小、空閒標誌位、指針等等),數據區是真實分配的內存區域,並且數據區的第一個字節地址即爲malloc返回的地址。

typedef struct s_block *t_block;
struct s_block {
size_t size; /* 數據區大小 */
t_block next; /* 指向下個塊的指針 */
int free; /* 是否是空閒塊 */
int padding; /* 填充4字節,保證meta塊長度爲8的倍數 */
char data[1] /* 這是一個虛擬字段,表示數據塊的第一個字節,長度不應計入meta */
};
現在,爲了完全地管理內存,我們需要能夠追蹤要分配和回收哪些內存。在對內存塊進行了 free 調用之後,我們需要做的是諸如將它們標記爲未被使用的等事情,並且,在調用 malloc 時,我們要能夠定位未被使用的內存塊。因此, malloc 返回的每塊內存的起始處首先要有這個結構:

  1. struct mem_control_block
  2. {
  3. int is_available;//是否空閒
  4. int size; //內存塊大小
  5. };


b、尋找合適的block

現在考慮如何在block鏈中查找合適的block。一般來說有兩種查找算法:

  • First fit:從頭開始,使用第一個數據區大小大於要求size的塊所謂此次分配的塊
  • Best fit:從頭開始,遍歷所有塊,使用數據區大小大於size且差值最小的塊作爲此次分配的塊

  兩種方法各有千秋,best fit具有較高的內存使用率(payload較高),而first fit具有更好的運行效率。

find_block從frist_block開始,查找第一個符合要求的block並返回block起始地址,如果找不到這返回NULL。這裏在遍歷時會更新一個叫last的指針,這個指針始終指向當前遍歷的block。這是爲了如果找不到合適的block而開闢新block使用的。

c、如果現有block都不能滿足size的要求,則需要在鏈表最後開闢一個新的block。下爲利用sbrk()創建新的block示意代碼:

#define BLOCK_SIZE 24 /* 由於存在虛擬的data字段,sizeof不能正確計算meta長度,這裏手工設置 */

t_block extend_heap(t_block last, size_t s) {
t_block b;
b = sbrk(0);
if(sbrk(BLOCK_SIZE + s) == (void *)-1)
return NULL;
b->size = s;
b->next = NULL;
if(last)
last->next = b;
b->free = 0;
return b;
}

4、內存分配,下爲內存分配代碼

  1. void *malloc(long numbytes)
  2. {
  3. void *current_location;
  4. struct mem_control_block *current_location_mcb;
  5. void *memory_location;
  6. if(! has_initialized)
  7. {
  8. malloc_init();
  9. }
  10. numbytes = numbytes + sizeof(struct mem_control_block);
  11. memory_location = 0;
  12. current_location = managed_memory_start;
  13. while(current_location ! = last_valid_address)
  14. {
  15. current_location_mcb =(struct mem_control_block *)current_location;
  16. if(current_location_mcb->is_available)
  17. {
  18. if(current_location_mcb->size >= numbytes)
  19. {
  20. current_location_mcb->is_available = 0;
  21. memory_location = current_location;
  22. break;
  23. }
  24. }
  25. current_location = current_location +current_location_mcb->size;
  26. }
  27. if(! memory_location)
  28. {
  29. sbrk(numbytes);
  30. memory_location = last_valid_address;
  31. last_valid_address = last_valid_address + numbytes;
  32. current_location_mcb = memory_location;
  33. current_location_mcb->is_available = 0;
  34. current_location_mcb->size = numbytes;
  35. }
  36. memory_location = memory_location + sizeof (struct mem_control_block);
  37. return memory_location;
  38. }








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章