brk()和sbrk()使用方法解析

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

[cpp] view plaincopy

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

首先说明一点 sbrk()是函数库调用,brk()是系统调用

这两个函数都用来改变 "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-10-10 21:49:23

brk()和sbrk()使用方法解析的相关文章

清除浮动方法解析

清除浮动方法解析 清除浮动带来的额外影响 如果对于浮动不熟悉的同学,可以看看介绍float的文章.传送门:CSS float 我们知道,在一个父元素内如果遇到某个浮动元素,此时父元素的高度会发生塌陷.针对父元素高度塌陷的问题,现在已经有了很多的解决方案.针对每一个方案,我们来进行深度的剖析. 添加空块级元素 这种方法比较容易,但是要注意的是这个空元素必须是一个块级元素,不能是行内元素或者是行内块元素.缺点就是多了一些没有意义的标签.代码如下. //HTML <div class="cont

【Android 多媒体开发】 MediaPlayer 状态机 接口 方法 解析

作者 : 韩曙亮 转载请著名出处 :  http://blog.csdn.net/shulianghan/article/details/38487967 一. MediaPlayer 状态机 介绍 Android MediaPlayer 状态即图例 : 1. Idle (闲置) 状态 和 End (结束) 状态 MediaPlayer 对象声明周期 : 从 Idle 到 End 状态就是 MediaPlayer 整个生命周期; -- 生命周期開始 : 进入 Idle (闲置) 状态; -- 生

用json方法解析webqq好友列表文本

本节课主要讲解了用json方法解析webqq好友列表文本,并显示在超级列表框里.相信大家看完本节课,会对json格式文本的解析有更深层次的理解. 用json方法解析webqq好友列表文本,布布扣,bubuko.com

用原始方法解析复杂字符串,json一定要用JsonMapper么?

转自数据之巅原文用原始方法解析复杂字符串,json一定要用JsonMapper么? 阅读目录 1.不规则非json字符串 2.键值对字符串分割函数 3.复杂Json格式的字符串 4.标准的json格式 5.总结 经常采集数据,肯定会碰到解析字符串,包括整个页面的html,或者json以及一些不标准的json格式... 以前用json序列化,有时候需要实体类,有的时候没有,比较麻烦,听说可以用JsonMapper,解析为字典格式.不过没用过,习惯了用最原始的方法来解析字符串,所以这里分享几个解析的

IOS开发之——四种方法解析Jason数据(转)

本文将介绍TouchJson. SBJson .JSONKit 和 iOS5所支持的原生的json方法,解析国家气象局API,TouchJson和SBJson需要下载他们的库 TouchJson包下载: http://download.csdn.net/detail/enuola/4523169 SBJson 包下载: http://download.csdn.net/detail/enuola/4523177 JSONKit包下载:http://download.csdn.net/detail

JSON.parse() 方法解析一个JSON字符串

JSON.parse() 方法解析一个JSON字符串,构造由字符串描述的JavaScript值或对象.可以提供可选的reviver函数以在返回之前对所得到的对象执行变换. 语法EDIT JSON.parse(text[, reviver]) 参数 text 要被解析成JavaSctipt值的字符串,查看 JSON 对象学习的JSON 语法的说明. reviver 可选 如果是一个函数,则规定了原始值如何被解析改造,在被返回之前. 返回值 Object对应给定的JSON文本. 异常 若被解析的 J

js 将json字符串转换为json对象的方法解析(转)

js 将json字符串转换为json对象的方法解析 将json字符串转换为json对象的方法.在数据传输过程中,json是以文本,即字符串的形式传递的,而JS操作的是JSON对象,所以,JSON对象和JSON字符串之间的相互转换是关键 例如: JSON字符串:var str1 = '{ "name": "cxh", "sex": "man" }'; JSON对象:var str2 = { "name": &

activity生命周期中方法解析

对于activity的生命周期我觉得是一个简单而又不简单的问题,很多人可能觉得自己已经很精通了!往往事实却不以为然! 要接着讨论下面的问题,先来简单了解一下activity,来看一段原文的说明,如下: An activity is a single, focused thing that the user can do.  Almost all activities interact with the user, so the Activity class takes care of creat

window.open方法解析

一.前言 最近在项目中需要新窗口打开一个第三方的页面,大家都知道,使用window.open打开新窗口某些情况下会被浏览器的屏蔽程序阻止.如果要打开的URL是通过AJAX获取的,就一定会被浏览器拦截.为了解决这个问题,温习了window.open的详细用法.记录下来备忘. 二.window.open有两个用途 (1).导航到指定的URL (2).打开一个新窗口.该应用场景比较常见. 先看一个完整示例,打开招商银行大众版: var cmbBankWin = window.open('https:/