brk(), sbrk() 用法详解

brk() , sbrk() 的声明如下:

[cpp] view plaincopy

  1. #include <unistd.h>
  2. int brk(void *addr);
  3. void *sbrk(intptr_t increment);

这两个函数都用来改变 "program break" (程序间断点)的位置,这个位置可参考下图:

如 man 里说的:

引用

brk()  and  sbrk() change the location of the program break, which defines the end of the process‘s data segment (i.e., the program break is the first location after the end of the uninitialized data segment).

brk() 和 sbrk() 改变 "program brek" 的位置,这个位置定义了进程数据段的终止处(也就是说,program break 是在未初始化数据段终止处后的第一个位置)。
如此翻译过来,似乎会让人认为这个 program break 是和上图中矛盾的,上图中的 program break 是在堆的增长方向的第一个位置处(堆和栈的增长方向是相对的),而按照说明手册来理解,似乎是在 bss segment 结束那里(因为未初始化数据段一般认为是 bss segment)。

首先说明一点,一个程序一旦编译好后,text segment ,data segment 和 bss segment 是确定下来的,这也可以通过 objdump 观察到。下面通过一个程序来测试这个 program break 是不是在 bss segment 结束那里:

[cpp] view plaincopy

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/time.h>
  5. #include <sys/resource.h>
  6. int bssvar;    //声明一个味定义的变量,它会放在 bss segment 中
  7. int main(void)
  8. {
  9. char *pmem;
  10. long heap_gap_bss;
  11. printf ("end of bss section:%p\n", (long)&bssvar + 4);
  12. pmem = (char *)malloc(32);          //从堆中分配一块内存区,一般从堆的开始处获取
  13. if (pmem == NULL) {
  14. perror("malloc");
  15. exit (EXIT_FAILURE);
  16. }
  17. printf ("pmem:%p\n", pmem);
  18. //计算堆的开始地址和 bss segment 结束处得空隙大小,注意每次加载程序时这个空隙都是变化的,但是在同一次加载中它不会改变
  19. heap_gap_bss = (long)pmem - (long)&bssvar - 4;
  20. printf ("1-gap between heap and bss:%lu\n", heap_gap_bss);
  21. free (pmem);   //释放内存,归还给堆
  22. sbrk(32);        //调整 program break 位置(假设现在不知道这个位置在堆头还是堆尾)
  23. pmem = (char *)malloc(32);   //再一次获取内存区
  24. if (pmem == NULL) {
  25. perror("malloc");
  26. exit (EXIT_FAILURE);
  27. }
  28. printf ("pmem:%p\n", pmem);   //检查和第一次获取的内存区的起始地址是否一样
  29. heap_gap_bss = (long)pmem - (long)&bssvar - 4;  //计算调整 program break 后的空隙
  30. printf ("2-gap between heap and bss:%lu\n", heap_gap_bss);
  31. free(pmem);   //释放
  32. return 0;
  33. }

下面,我们分别运行两次程序,并查看其输出:

引用

[[email protected] C]$ ./sbrk 
end of bss section:0x8049938
pmem:0x82ec008
1-gap between heap and bss:2762448
pmem:0x82ec008
2-gap between heap and bss:2762448
[[email protected] C]$ ./sbrk 
end of bss section:0x8049938
pmem:0x8dbc008
1-gap between heap and bss:14100176
pmem:0x8dbc008
2-gap between heap and bss:14100176

从上面的输出中,可以发现几点:
1. bss 段一旦在在程序编译好后,它的地址就已经规定下来。
2. 一般及简单的情况下,使用 malloc() 申请的内存,释放后,仍然归还回原处,再次申请同样大小的内存区时,还是从第 1 次那里获得。
3. bss segment 结束处和堆的开始处的空隙大小,并不因为 sbrk() 的调整而改变,也就是说明了 program break 不是调整堆头部。

所以,man 手册里所说的  “program break 是在未初始化数据段终止处后的第一个位置” ,不能将这个位置理解为堆头部。这时,可以猜想应该是在堆尾部,也就是堆增长方向的最前方。下面用程序进行检验:

当 sbrk() 中的参数为 0 时,我们可以找到 program break 的位置。那么根据这一点,检查一下每次在程序加载时,系统给堆的分配是不是等同大小的:

[cpp] view plaincopy

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/time.h>
  5. #include <sys/resource.h>
  6. int main(void)
  7. {
  8. void *tret;
  9. char *pmem;
  10. pmem = (char *)malloc(32);
  11. if (pmem == NULL) {
  12. perror("malloc");
  13. exit (EXIT_FAILURE);
  14. }
  15. printf ("pmem:%p\n", pmem);
  16. tret = sbrk(0);
  17. if (tret != (void *)-1)
  18. printf ("heap size on each load: %lu\n", (long)tret - (long)pmem);
  19. return 0;
  20. }

运行上面的程序 3 次:

引用

[[email protected] C]$ ./sbrk 
pmem:0x80c9008
heap size on each load: 135160
[[email protected] C]$ ./sbrk 
pmem:0x9682008
heap size on each load: 135160
[[email protected] C]$ ./sbrk 
pmem:0x9a7d008
heap size on each load: 135160
[[email protected] C]$ ./sbrk 
pmem:0x8d92008
heap size on each load: 135160
[[email protected] C]$ vi sbrk.c

从输出可以看到,虽然堆的头部地址在每次程序加载后都不一样,但是每次加载后,堆的大小默认分配是一致的。但是这不是不能改的,可以使用 sysctl 命令修改一下内核参数:

引用

#sysctl -w kernel/randomize_va_space=0

这么做之后,再运行 3 次这个程序看看:

引用

[[email protected] C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
[[email protected] C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
[[email protected] C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160

从输出看到,每次加载后,堆头部的其实地址都一样了。但我们不需要这么做,每次堆都一样,容易带来缓冲区溢出攻击(以前老的 linux 内核就是特定地址加载的),所以还是需要保持 randomize_va_space 这个内核变量值为 1 。

下面就来验证 sbrk() 改变的 program break 位置在堆的增长方向处:

[cpp] view plaincopy

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/time.h>
  5. #include <sys/resource.h>
  6. int main(void)
  7. {
  8. void *tret;
  9. char *pmem;
  10. int i;
  11. long sbrkret;
  12. pmem = (char *)malloc(32);
  13. if (pmem == NULL) {
  14. perror("malloc");
  15. exit (EXIT_FAILURE);
  16. }
  17. printf ("pmem:%p\n", pmem);
  18. for (i = 0; i < 65; i++) {
  19. sbrk(1);
  20. printf ("%d\n", sbrk(0) - (long)pmem - 0x20ff8);   //0x20ff8 就是堆和 bss段 之间的空隙常数;改变后要用 sbrk(0) 再次获取更新后的program break位置
  21. }
  22. free(pmem);
  23. return 0;
  24. }

运行输出:

引用

[[email protected] C]$ ./sbrk 
pmem:0x804a008
1
2
3
4
5

... ...
61
62
63
64

从输出看到,sbrk(1) 每次让堆往栈的方向增加 1 个字节的大小空间。

而 brk() 这个函数的参数是一个地址,假如你已经知道了堆的起始地址,还有堆的大小,那么你就可以据此修改 brk() 中的地址参数已达到调整堆的目的。

实际上,在应用程序中,基本不直接使用这两个函数,取而代之的是 malloc() 一类函数,这一类库函数的执行效率会更高。 还需要注意一点,当使用 malloc() 分配过大的空间,比如超出 0x20ff8 这个常数(在我的系统(Fedora15)上是这样,别的系统可能会有变)时,malloc 不再从堆中分配空间,而是使用 mmap() 这个系统调用从映射区寻找可用的内存空间。

时间: 2024-12-11 23:54:51

brk(), sbrk() 用法详解的相关文章

js的offsetParent属性用法详解

js的offsetParent属性用法详解:此属性是javascript中较为常用的属性,对于它的良好掌握也是非常有必要的,下面就通过代码实例介绍一下它的用法,希望能够给需要的朋友带来一定的帮助.一.基本介绍:此属性可以返回距离指定元素最近的采用定位(position属性值为fixed.relative或者absolute)父级元素,如果父级元素中没有采用定位的元素,则返回body对象的引用.语法结构: obj.offsetParent 二.代码实例: <!DOCTYPE html> <

python处理word文件:win32com用法详解

目标:用python处理doc文件 方法:引入win32com模块 ************************************************************************** 一.安装 ************************************************************************** 首先要先下载安装win32com模块(起先在linux下装不成功,后在windows下面成功了...) 下载地址:http

jQuery 事件用法详解

jQuery 事件用法详解 目录 简介 实现原理 事件操作 绑定事件 解除事件 触发事件 事件委托 事件操作进阶 阻止默认事件 阻止事件传播 阻止事件向后执行 命名空间 自定义事件 事件队列 jquery中文文档 简介 jquery 之所以成为最受欢迎的前端库,很大一部分是得益于它的事件具有良好的语义,优秀的兼容性,并且便于管理和扩展. 在这里我会介绍 jquery 事件的一些比较基础的用法. 实现原理 jquery 事件脱胎于浏览器的 addEventListener (W3) 和 attac

(转)ProgressDialog用法详解

转载自: ProgressDialog用法详解 ProgressDialog的基本用法 ProgressDialog为进度对话框.android手机自带的对话框显得比较单一,我们可以通过ProgressDialog来自己定义对话框中将要显示出什么东西. 首先看看progressDialog里面的方法 setProgressStyle:设置进度条风格,风格为圆形,旋转的.  setTitlt:设置标题  setMessage:设置提示信息:  setIcon:设置标题图标:  setIndeter

BigDecimal用法详解(转)

BigDecimal用法详解    http://www.cnblogs.com/linjiqin/p/3413894.html 一.简介Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算.双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal.BigDecimal所创建的是对象

mapminmax的用法详解 _MATLAB

============外一篇 有关mapminmax的用法详解 by faruto==================================转自:http://www.ilovematlab.cn/thread-47224-1-1.html几个要说明的函数接口:[Y,PS] = mapminmax(X)[Y,PS] = mapminmax(X,FP)Y = mapminmax('apply',X,PS)X = mapminmax('reverse',Y,PS) 用实例来讲解,测试数据

Nmap用法详解

nmap是一个网络探测和安全扫描程序,系统管理者和个人可以使用这个软件扫描大型的网络,获取那台主机正在运行以及提供什么服务等信息.nmap支持很多扫描技术,例如:UDP.TCP connect().TCP SYN(半开扫描).ftp代理(bounce攻击).反向标志.ICMP.FIN.ACK扫描.圣诞树(Xmas Tree).SYN扫描和null扫描.从扫描类型一节可以得到细节.nmap还提供了一些高级的特征,例如:通过TCP/IP协议栈特征探测操作系统类型,秘密扫描,动态延时和重传计算,并行扫

CSS中伪类及伪元素用法详解

原文:CSS中伪类及伪元素用法详解 伪类的分类及作用: 注:该表引自W3School教程 伪元素的分类及作用: 接下来让博主通过一些生动的实例(之前的作业或小作品)来说明几种常用伪类的用法和效果,其他的读者可以自己尝试: :active  大致效果为用鼠标点击时,元素增加特效,鼠标松开时,特效消失.多用在按钮的点击上. 写法: 这里id为box的是一div块,在css中首先设置了他的基本样式,下面为加入:active伪类后需要修改的样式. 未点击时: 点击之后: :active.:hover.:

C# ListView用法详解

一.ListView类 1.常用的基本属性: (1)FullRowSelect:设置是否行选择模式.(默认为false) 提示:只有在Details视图该属性才有意义. (2) GridLines:设置行和列之间是否显示网格线.(默认为false)提示:只有在Details视图该属性才有意义. (3)AllowColumnReorder:设置是否可拖动列标头来对改变列的顺序.(默认为false)提示:只有在Details视图该属性才有意义. (4)View:获取或设置项在控件中的显示方式,包括D