转自:https://www.cnblogs.com/chengxuyuancc/p/3566710.html
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 ", (char *)(&bss_end) + 4); 11 tret = sbrk(0); 12 if (tret != (void *)-1) 13 printf ("heap start: %p ", 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 ", (char *)(&bss_end) + 4); 13 tret = sbrk(0); 14 if (tret != (void *)-1) 15 printf ("heap1 start: %p ", tret); 16 17 if (brk((char *)tret - 1) == -1) 18 printf("brk error "); 19 20 tret = sbrk(0); 21 if (tret != (void *)-1) 22 printf ("heap2 start: %p ", tret); 23 24 pmem = (char *)malloc(32); 25 if (pmem == NULL) { 26 perror("malloc"); 27 exit (EXIT_FAILURE); 28 } 29 printf ("pmem:%p ", pmem); 30 31 tret = sbrk(0); 32 if (tret != (void *)-1) 33 printf ("heap1 end: %p ", tret); 34 35 if (brk((char *)tret - 10) == -1) 36 printf("brk error "); 37 38 tret = sbrk(0); 39 if (tret != (void *)-1) 40 printf ("heap2 end: %p ", 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 ", tret); 13 14 pmem = (char *)malloc(64); //分配内存 15 if (pmem == NULL) { 16 perror("malloc"); 17 exit (EXIT_FAILURE); 18 } 19 printf ("pmem:%p ", pmem); 20 tret = sbrk(0); 21 if (tret != (void *)-1) 22 printf ("heap size on each load: %p ", (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()这个系统调用从映射区寻找可用的内存空间。