天天看點

Linux下程序記憶體管理之malloc和sbrk 程序記憶體空間配置設定

之前自己突發興趣想寫一下malloc函數,順便了解一下程序的記憶體管理。在寫的過程中發現其實malloc隻不過是通過調用Linux下的sbrk函數來實作記憶體的配置設定,隻是在sbrk之上加了一層對所配置設定的記憶體的管理罷了,而sbrk以及brk是實作從虛拟記憶體到記憶體的映射的。在實際動手寫之前先來了解一下Linux下一個程序的記憶體空間配置設定。

程序記憶體空間配置設定

Linux下每個程序所配置設定的虛拟記憶體空間是3G,但實際使用過程中不可能也沒有必要為一個程序配置設定如此大的空間,畢竟記憶體是很寶貴的資源。當一個程序執行的時候系統為其配置設定的記憶體空間主要包括資料段,代碼段,棧,堆等等。而malloc所申請的空間就是從堆中配置設定的。先來看下面這張圖:

Linux下程式記憶體管理之malloc和sbrk 程式記憶體空間配置設定

這就是一個程序的記憶體空間,其中的Data Segment出要是存放已經初始化的靜态資料,而BSS segment則存放為初始化的靜态資料,在此之上的堆,然後是棧。值得注意的是,堆和棧的增長方向正好是相反的。現在先通過一段簡單的代碼來看一下data segment 和BSS segment的配置設定。

8 #include <stdio.h>
  9 #include <stdlib.h>
 10 
 11 int bssvar;
 18 int dataSegmentVar = 1;
 19 
 20 int main()
 21 {
 22     printf("bssvar:%p, dataSegmentVar:%p,gap:%d", &bssvar, &dataSegmentVar,     ((int)&bssvar - (int)&dataSegmentVar));
 23     return 0;
 24 }  
           

程式運作的結果是:

bssvar:0x6008c0, dataSegmentVar:0x6008ac,gap:20

可以看到dataSegment在BSS segment之下,他們之間的有20個位元組的空間也即data segment的配置設定空間大小是20位元組。但是這個大小并不是固定的,如果程式中的靜态未初始化變量大于20個位元組,那麼data segment的空間會相應地增長。

8 #include <stdio.h>
  9 #include <stdlib.h>                                                                                                                                                              
 10 
 11 int bssvar, bssvar1, bssvar2, bssvar3, bssvar4, bssvar5;
 12 char c;
 13 int dataSegmentVar = 1;
 14 
 15 int main()
 16 {
 17     printf("bssvar:%p, dataSegmentVar:%p,gap:%d", &bssvar, &dataSegmentVar, ((int)&bssvar - (int)&dataSegmentVar));
 18     return 0;
 19 }
           

程式運作的結果是:

bssvar:0x6008c4, dataSegmentVar:0x6008ac,gap:24

這個時候dataSegment的變量是5個int和一個char,總共的大小是21個位元組,而此時dataSegment的大小是24個位元組,空間超過20,但是為了對齊,是以不是21而是24。

另外在上圖中還有一個值得注意的地方就是program break,這是程序堆的末尾位址。當使用者通過malloc函數申請空間的時候,實際就是利用sbrk函數移動program break,使其向上增長,以獲得更大的堆空間。是以看起來很神秘的記憶體申請隻不過是移動一個指針而已,哈哈。

不過這隻是對簡單的原理,裡面還有很多細節需要考慮,接下來還是用一段程式來說。

8 #include <stdio.h>
  9 #include <stdlib.h>
 10 
 11 int main()
 12 {
 13     void* ptr, *ptr1;
 14     ptr = sbrk(0);
 15     printf("sbrk:%p\n", ptr);
 16     ptr1 = malloc(100);
 17     ptr = sbrk(0);
 18     printf("sbrk:%p, ptr1:%p\n", ptr, ptr1);
 19     free(ptr1);
 20     ptr = sbrk(0);
 21     printf("sbrk:%p\n",ptr);
 22 }
~     
           

程式中首先用sbrk(0)得到堆部分的末尾位址,然後利用malloc申請了一個100位元組長度的空間,這個時候再來看堆空間的末尾位址以及所申請的空間的位址。最後,再釋放所申請的空間然後再來看堆空間位址。

程式的運作結果:

sbrk:0x2439000

sbrk:0x245a000, ptr1:0x2439010

sbrk:0x245a000

一開始堆區的末尾位址是0x2439000,但是當利用malloc申請完100位元組的空間之後,堆區的末尾位址變為了0x245a000,一下子變大了0x21000。另外還值得注意的就是malloc所申請的空間的起始位址是0x2439010,比一開始的堆末尾位址向後移動了16個位元組。這個不難了解,每一段記憶體空間都需要有一些中繼資料去管理該空間,是以我猜想這16個位元組就是用來記錄malloc所配置設定這100個位元組空間的資訊,包括大小,狀态等等。

那麼為什麼明明隻申請了100個位元組的空間,program break卻向後移動了這麼多?這個也不難了解,總不能每次使用者申請一段小的空間都去調用一次sbrk吧,這樣的開銷太大。是以幹脆一次性配置設定一段大空間出來,除了使用者所申請的空間之外,剩下的空間可以用于之後的malloc空間申請。來看下一段程式:

8 #include <stdio.h>
  9 #include <stdlib.h>
 10 
 11 int main()
 12 {
 13     void* ptr, *ptr1;
 14     ptr = sbrk(0);
 15     printf("sbrk:%p\n", ptr);
 16     ptr1 = malloc(100);
 17     ptr = sbrk(0);
 18     printf("sbrk:%p, ptr1:%p\n", ptr, ptr1);
 19     ptr1 = malloc(100);
 20     ptr = sbrk(0);
 21     printf("sbrk:%p, ptr1:%p\n",ptr, ptr1);                                                                                                                                      
 22     free(ptr1);
 23     ptr = sbrk(0);
 24     printf("sbrk:%p\n",ptr);
 25 }
           

運作結果:

sbrk:0x933000

sbrk:0x954000, ptr1:0x933010

sbrk:0x954000, ptr1:0x933080

sbrk:0x954000

可以看到,盡管通過malloc函數申請了兩塊100位元組的空間,但是program break并未是以而移動兩次。另外,第一塊空間和第二塊空間的位址相差的不是100個位元組而是112個位元組,究其原因估計還是因為對齊的問題吧。

通過上文的講解,我們發現,其實malloc也沒有這麼神秘了,它隻不過就是利用sbrk來申請了一段空間罷了。不過除了申請空間之外,還需要管理這些空間才是malloc真正核心的地方。這些問題将在下一篇博文《自己動手寫malloc》中詳細講解。

關于sbrk還有brk的用法網上有一大堆,這裡就不細講了,推薦一篇文章吧:點選打開連結