brk和sbrk的定義
在man手冊中定義了這兩個函數:
1 #include <unistd.h> 2 int brk(void *addr); 3 void *sbrk(intptr_t increment);
手冊上說brk和sbrk會改變program break的位置,program break被定義為程序data segment的結束位置。感覺這句話不是很好理解,從下面程序地址空間的分布來看,data segment后面還有bss segment,顯然和手冊說的不太一樣。一種可能的解釋就是手冊中的data segment和下圖中的data segment不是一個意思,手冊中的data segment應該包含了下圖中的data segment、bss segment和heap,所以program break指的就是下圖中heap的結束地址。
有了前面program break的概念后,我們來看下brk和sbrk的作用。brk通過傳遞的addr來重新設置program break,成功則返回0,否則返回-1。而sbrk用來增加heap,增加的大小通過參數increment決定,返回增加大小前的heap的program break,如果increment為0則返回program break。
從上面的圖可以看出heap的起始地址並不是bss segment的結束地址,而是隨機分配的,下面我們用一個程序來驗證下:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int bss_end; 5 6 int main(void) 7 { 8 void *tret; 9 10 printf("bss end: %p\n", (char *)(&bss_end) + 4); 11 tret = sbrk(0); 12 if (tret != (void *)-1) 13 printf ("heap start: %p\n", tret); 14 return 0; 15 }
運行的結果為:
從上面運行結果可以知道bss和heap是不相鄰的,並且同一個程序bss的結束地址是固定的,而heap的起始地址在每次運行的時候都會改變。你可能會說sbkr(0)返回的是heap的結束地址,怎么上面確把它當做起始地址呢?由於程序開始運行時heap的大小是為0,所以起始地址和結束地址是一樣的,不信我們可以用下面的程序驗證下。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 5 int bss_end; 6 7 int main(void) 8 { 9 void *tret; 10 char *pmem; 11 12 printf("bss end: %p\n", (char *)(&bss_end) + 4); 13 tret = sbrk(0); 14 if (tret != (void *)-1) 15 printf ("heap1 start: %p\n", tret); 16 17 if (brk((char *)tret - 1) == -1) 18 printf("brk error\n"); 19 20 tret = sbrk(0); 21 if (tret != (void *)-1) 22 printf ("heap2 start: %p\n", tret); 23 24 pmem = (char *)malloc(32); 25 if (pmem == NULL) { 26 perror("malloc"); 27 exit (EXIT_FAILURE); 28 } 29 printf ("pmem:%p\n", pmem); 30 31 tret = sbrk(0); 32 if (tret != (void *)-1) 33 printf ("heap1 end: %p\n", tret); 34 35 if (brk((char *)tret - 10) == -1) 36 printf("brk error\n"); 37 38 tret = sbrk(0); 39 if (tret != (void *)-1) 40 printf ("heap2 end: %p\n", tret); 41 return 0; 42 }
運行結果為:
程序開始的時候打印出來heap的結束地址,並用這個地址減1來重新設置heap的結束地址,結果兩次的結束地址居然是一樣的,那說明這個結束地址就是heap的起始地址,再減小這個起始地址是不允許的,不過brk也不會報錯。然后調用malloc獲取內存,並打印出該內存的起始地址pmem,可以發現pmem與heap的起始地址相差8個字節,為什么會有8個字節沒有?這8個字節應該是用來管理heap空間的(不深究)。最后再次獲得heap的結束地址,並用這個地址減10來重新設置heap的結束地址,這下地址設置成功了。
堆的管理
上面的函數我們其實很少使用,大部分我們使用的是malloc和free函數來分配和釋放內存。這樣能夠提高程序的性能,不是每次分配內存都調用brk或sbrk,而是重用前面空閑的內存空間。brk和sbrk分配的堆空間類似於緩沖池,每次malloc從緩沖池獲得內存,如果緩沖池不夠了,再調用brk或sbrk擴充緩沖池,直到達到緩沖池大小的上限,free則將應用程序使用的內存空間歸還給緩沖池。
如果緩沖池需要擴充時,一次擴充多少呢?先運行下面的程序看看:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 5 int main(void) 6 { 7 void *tret; 8 char *pmem; 9 10 tret = sbrk(0); 11 if (tret != (void *)-1) 12 printf ("heap start: %p\n", tret); 13 14 pmem = (char *)malloc(64); //分配內存 15 if (pmem == NULL) { 16 perror("malloc"); 17 exit (EXIT_FAILURE); 18 } 19 printf ("pmem:%p\n", pmem); 20 tret = sbrk(0); 21 if (tret != (void *)-1) 22 printf ("heap size on each load: %p\n", (char *)tret - pmem); 23 free(pmem) 24 return 0; 25 }
運行結果如下:
從結果可以看出調用malloc(64)后緩沖池大小從0變成了0x20ff8,將上面的malloc(64)改成malloc(1)結果也是一樣,只要malloc分配的內存數量不超過0x20ff8,緩沖池都是默認擴充0x20ff8大小。值得注意的是如果malloc一次分配的內存超過了0x20ff8,malloc不再從堆中分配空間,而是使用mmap()這個系統調用從映射區尋找可用的內存空間。
參考
http://blog.csdn.net/sgbfblog/article/details/7772153