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

  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 结束那里:

  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. }

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

引用
[beyes@localhost 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
[beyes@localhost 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 的位置。那么根据这一点,检查一下每次在程序加载时,系统给堆的分配是不是等同大小的:

  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 次:

引用
[beyes@localhost C]$ ./sbrk 
pmem:0x80c9008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x9682008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x9a7d008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x8d92008
heap size on each load: 135160
[beyes@localhost C]$ vi sbrk.c

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

引用
#sysctl -w kernel/randomize_va_space=0

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

引用
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160

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

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

  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. }

运行输出:

引用
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
1
2
3
4
5

... ...
61
62
63
64

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

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

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

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

  1. brk(), sbrk() 用法详解【转】

    转自:http://blog.csdn.net/sgbfblog/article/details/7772153 贴上原文地址,好不容易找到了:brk(), sbrk() -- 改变数据段长度 brk ...

  2. [转帖]强大的strace命令用法详解

    强大的strace命令用法详解 文章转自: https://www.linuxidc.com/Linux/2018-01/150654.htm strace是什么? 按照strace官网的描述, st ...

  3. C#中string.format用法详解

    C#中string.format用法详解 本文实例总结了C#中string.format用法.分享给大家供大家参考.具体分析如下: String.Format 方法的几种定义: String.Form ...

  4. @RequestMapping 用法详解之地址映射

    @RequestMapping 用法详解之地址映射 引言: 前段时间项目中用到了RESTful模式来开发程序,但是当用POST.PUT模式提交数据时,发现服务器端接受不到提交的数据(服务器端参数绑定没 ...

  5. linux管道命令grep命令参数及用法详解---附使用案例|grep

    功能说明:查找文件里符合条件的字符串. 语 法:grep [-abcEFGhHilLnqrsvVwxy][-A<显示列数>][-B<显示列数>][-C<显示列数>] ...

  6. mysql中event的用法详解

    一.基本概念mysql5.1版本开始引进event概念.event既“时间触发器”,与triggers的事件触发不同,event类似与linux crontab计划任务,用于时间触发.通过单独或调用存 ...

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

    CSS中伪类及伪元素用法详解   伪类的分类及作用: 注:该表引自W3School教程 伪元素的分类及作用: 接下来让博主通过一些生动的实例(之前的作业或小作品)来说明几种常用伪类的用法和效果,其他的 ...

  8. c++中vector的用法详解

    c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...

  9. AngularJS select中ngOptions用法详解

    AngularJS select中ngOptions用法详解   一.用法 ngOption针对不同类型的数据源有不同的用法,主要体现在数组和对象上. 数组: label for value in a ...

随机推荐

  1. C# 哈希表(Hashtable)用法笔记

    一.什么是Hashtable? Hashtable 类代表了一系列基于键的哈希代码组织起来的键/值对.它使用键来访问集合中的元素. 当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值.哈 ...

  2. spring注解@service("service")括号中的service有什么用

    相当于 xml配置中得 bean id = service 也可以不指定 不指定相当于 bean id = com. service.service 就是这个类的全限定名 好处是:同一个接口可以有多个 ...

  3. TestFlight使用方法

    1 说明 目前IOS版本APP上架前,很少有项目对上架包进行测试,而上架包是人工打包的,很有可能之前的测试包没有问题,可打出的上架包出现问题.苹果市场是有提供对上架包内部测试的,可以对上架包进行测试, ...

  4. jenkins+ANT+jmeter 接口测试环境搭建

    目的 jmeter很早就接触了,最近又在接触项目的时候整了一下.写这篇博客主要有两个目的 1,为了给自己搭建jmeter做一个总结. 2,在部署过程中遇到过一些坑,在这分享出来,也希望能给需要的人一个 ...

  5. C#开发模式——单例模式

    一.单例模式简介 单例模式是为保证一个对象,在程序运行的任何时刻只有一个实例存在,我们把这种实现方式称之为“单例模式”. 二.单例模式的核心 单例类只有一个实例存在 单例类提供一个全局访问点 单例类通 ...

  6. python爬虫从入门到放弃(一)之初识爬虫

    整理这个文档的初衷是自己开始学习的时候没有找到好的教程和文本资料,自己整理一份这样的资料希望能对小伙伴有帮助 什么是爬虫? 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页 ...

  7. BZOJ-1491-社交网络

    题目链接:https://vjudge.net/problem/HYSBZ-1491 题目大意:中文题目,题意见题目 题目分析:由于数据量很小,可以考虑进行Floyd算法. 第一次进行Floyd算法, ...

  8. 【SignalR学习系列】4. SignalR广播程序

    创建项目 创建一个空的 Web 项目,并在 Nuget 里面添加 SignalR,jQuery UI 包,添加以后项目里包含了 jQuery,jQuery.UI ,和 SignalR 的脚本. 服务端 ...

  9. 【css】圆角 +文本阴影

    1.  css3 圆角 http://www.cnblogs.com/lhb25/archive/2013/01/30/css3-border-radius.html 2. text shawdow ...

  10. redis 订阅与发布

    PUBLISH,SUBSCRIBE,等命令实现订阅与发布 订阅/发布到频道 订阅/发布到模式   频道的订阅与信息发送   订阅subscribe,可以让客户端订阅任意数量的频道, 每当有新信息发送到 ...