brk()和sbrk()函数的使用

http://www.it165.net/pro/html/201409/22619.html

  • 1. 1 #include <unistd.h>
    2. 2 int brk(void *addr);
    3. 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的结束地址,而是随机分配的,下面我们用一个程序来验证下:

     

    01. 1 #include <stdio.h>
    02.  2 #include <unistd.h>
    03.  3  
    04.  4 int bss_end;
    05.  5 
    06.  6 int main(void)
    07.  7 {
    08.  8     void *tret;
    09.  9         
    10. 10     printf("bss end: %p
    11. ", (char *)(&bss_end) + 4);
    12. 11     tret = sbrk(0);
    13. 12     if (tret != (void *)-1)
    14. 13         printf ("heap start: %p
    15. ", tret);
    16. 14     return 0;
    17. 15 }

     

      运行的结果为:

    \

      从上面运行结果可以知道bss和heap是不相邻的,并且同一个程序bss的结束地址是固定的,而heap的起始地址在每次运行的时候都会改变。你可能会说sbkr(0)返回的是heap的结束地址,怎么上面确把它当做起始地址呢?由于程序开始运行时heap的大小是为0,所以起始地址和结束地址是一样的,不信我们可以用下面的程序验证下。

     

    01. 1 #include <stdio.h>
    02.  2 #include <unistd.h>
    03.  3 #include <stdlib.h>
    04.  4  
    05.  5 int bss_end;
    06.  6 
    07.  7 int main(void)
    08.  8 {
    09.  9     void *tret;
    10. 10     char *pmem;
    11. 11         
    12. 12     printf("bss end: %p
    13. ", (char *)(&bss_end) + 4);
    14. 13     tret = sbrk(0);
    15. 14     if (tret != (void *)-1)
    16. 15         printf ("heap1 start: %p
    17. ", tret);
    18. 16     
    19. 17     if (brk((char *)tret - 1) == -1)
    20. 18         printf("brk error
    21. ");
    22. 19         
    23. 20     tret = sbrk(0);
    24. 21     if (tret != (void *)-1)
    25. 22         printf ("heap2 start: %p
    26. ", tret);
    27. 23     
    28. 24     pmem = (char *)malloc(32);
    29. 25     if (pmem == NULL) {
    30. 26         perror("malloc");
    31. 27         exit (EXIT_FAILURE);
    32. 28     }
    33. 29     printf ("pmem:%p
    34. ", pmem);
    35. 30     
    36. 31     tret = sbrk(0);
    37. 32     if (tret != (void *)-1)
    38. 33         printf ("heap1 end: %p
    39. ", tret);
    40. 34     
    41. 35     if (brk((char *)tret - 10) == -1)
    42. 36         printf("brk error
    43. ");
    44. 37         
    45. 38     tret = sbrk(0);
    46. 39     if (tret != (void *)-1)
    47. 40         printf ("heap2 end: %p
    48. ", tret);
    49. 41     return 0;
    50. 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则将应用程序使用的内存空间归还给缓冲池。

      如果缓冲池需要扩充时,一次扩充多少呢?先运行下面的程序看看:


    01. 1 #include <stdio.h>
    02.  2 #include <unistd.h>
    03.  3 #include <stdlib.h>
    04.  4  
    05.  5 int main(void)
    06.  6 {
    07.  7         void *tret;
    08.  8         char *pmem;
    09.  9         
    10. 10         tret = sbrk(0);
    11. 11         if (tret != (void *)-1)
    12. 12                 printf ("heap start: %p
    13. ", tret);
    14. 13                 
    15. 14         pmem = (char *)malloc(64);  //分配内存
    16. 15         if (pmem == NULL) {
    17. 16                 perror("malloc");
    18. 17                 exit (EXIT_FAILURE);
    19. 18         }
    20. 19         printf ("pmem:%p
    21. ", pmem);
    22. 20         tret = sbrk(0);
    23. 21         if (tret != (void *)-1)
    24. 22                 printf ("heap size on each load: %p
    25. ", (char *)tret - pmem);
    26. 23     free(pmem)
    27. 24     return 0;
    28. 25 }

      运行结果如下:

    \

      从结果可以看出调用malloc(64)后缓冲池大小从0变成了0x20ff8,将上面的malloc(64)改成malloc(1)结果也是一样,只要malloc分配的内存数量不超过0x20ff8,缓冲池都是默认扩充0x20ff8大小。值得注意的是如果malloc一次分配的内存超过了0x20ff8,malloc不再从堆中分配空间,而是使用mmap()这个系统调用从映射区寻找可用的内存空间。

     

    接下来就是要对用brk和mmap分配好内存进行管理了。因为brk(),mmap()是系统调用,如果每次调用malloc动态分配内存都执行一次系统调用,那开销是比较大的。再者,如果每次申请的内存较小,但是系统分配的内存都是固定大小的倍数(一般是4KB,一页),这样就会有大量的浪费。所以malloc一般会实现一个内存堆来管理这些内存,malloc分配的内存都会以若干chunk的方式放到内存堆中。每次用户调用malloc动态分配内存的时候,malloc会先到内存堆里进行查找,如果内存堆里没有合适的空闲chunk,再利用brk/malloc系统调用分配一大块内存,然后把新分配的大块内存放到内存堆中,并生成一块合适的chunk块返回给用户。当用户用free释放chunk的时候,可能并不立即使用系统调用释放内存,而是将释放的chunk作为空闲chunk加入内存堆中,和其他的空闲chunk合并,便于下次分配的时候再次使用。

    一般说来,释放的chunk如果标记为mmap申请的,则使用munmap释放。如果是brk申请的,进一步判断堆顶之下的空闲chunk是否大于128KB,如果是,则使用brk()释放。如果小于128KB,仍由内存堆维护。这样对brk()的使用就会有个问题,当brk()释放的内存块在堆顶之下,且内存块到堆顶之间还有未释放的内存。那么这块内存的释放将不会成功,从而形成内存空洞。

    1. 无论是堆,还是栈都是对虚存的操作和管理。
    2. 系统调用brk()和mmap()用来动态分配虚存空间,也就是表明这些虚存地址是合法的,访问的时候,系统应为其分配物理内存,而不是报错。
    3. 堆的本质是动态申请的虚存空间。理论上可以用任何方式去管理这块空间。但数据结构--"堆"是最常用的一种,所以这块分配的空间常称为被堆。
    4. 和栈不一样,堆的管理是在用户函数库中进行,malloc/free等函数是堆的入口。
    5. 每次分配的内存块大小都会被记录下来,释放的时候只需要指定要释放的内存地址就行了。这就是为什么malloc的时候要指定大小,free的时候不用。
    6. 堆和栈一样,仍然使用了物理内存的延迟分配策略。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值