浅析Linux下的堆内存管理

  最近在看一本叫《程序员的自我修养-链接.装载域库》(俞甲子,石凡,潘爱民 著)这本书,不得不像大家安利这本书,从最基本的EFL文件到链接,再到装载,虽然现在还没看完,但是对程序的可执行文件和虚拟内存的布局有了进一步的了解,不得不说是一本好书。言归正传,本文讨论的话题是堆内存的管理,首先让我们来了解一下进程的虚拟地址空间的组成部分(如下图):

首先来讨论一下这个program break,手册上说brk和sbrk会改变program break的位置,program break被定义为程序data segment的结束位置。感觉program break被定义成程序data segment的结束位置理解有点模糊,下面我们就用最基本的程序来验证。

1.程序代码如下:

#include <stdio.h>   
#include <unistd.h>
#include <apue.h>   
int main(){  
     void* p = sbrk(0);  //获取当前program break的当前位置(此时是heap的起始地址)
     int* p1 = (int *)p; // 将当前空指针转化成int型指针
     brk(p1+4);   //分配了16个字节的空间
     p1[0] = 10;  
     p1[1] = 20;  
     p1[2] = 30;  
     p1[3] = 40;  
     p1[4] = 50;     
     printf("%p\n",p1);
     printf("%p\n",p1+1);
     printf("%p\n",p1+2);
     printf("%p\n",p1+3); 
     printf("%p\n",sbrk(0));//这个时候program break的值比较大,因为一次性分配了较大的空间,而不必每次都调用sbrk()来分配堆空间
     while(1){
	sleep(1);	
	}
}  

首先我们将程序在后台运行(./a.out &),得到的结果是:


然后,执行命令cat  /proc/18092/maps就可以得到进程堆的地址范围,如下图:

可以看到我们进程堆的范围是09c2b000-09c4d000 rw-p 00000000 00:00 0  [heap],堆的起始地址和p1的地址一样,说明程序brk()在堆上申请空间,也就是说刚开始的时候program break和堆的起始地址是一样的。

2.我们对上面的程序进行修改,在这个基础上继续分配空间,如下:

#include <stdio.h>   
#include <unistd.h>
#include <apue.h>   
int main(){  
     void* p = sbrk(0);  //获取当前program break的当前位置(此时是heap的起始地址)
     int* p1 = (int *)p; // 将当前空指针转化成int型指针
     brk(p1+4);  
     printf("program break is :%p\n",sbrk(0));//这个时候program break的值比较大,因为一次性分配了较大的空间,而不必每次都调用sbrk()来分配堆空间 
     p1[0] = 10;
     printf("program break is %p\n",sbrk(0));  
     p1[1] = 20;  
     p1[2] = 30;  
     printf("program break is %p\n",sbrk(0));
     p1[3] = 40;  
     p1[4] = 50;     
     printf("%d,%p\n",*p1,p1);
     printf("%d,%p\n",*(p1+1),p1+1);
     printf("%d,%p\n",*(p1+2),p1+2);
     printf("%d,%p\n",*(p1+3),p1+3); 
     printf("program break is %p\n",sbrk(0));//这个时候program break的值比较大,因为一次性分配了较大的空间,而不必每次都调用sbrk()来分配堆空间 
}  

运行结果为:


那么问题来了,我们在调用brk()之后,program break的值为0x95f6010,刚好是从0x95f6000地址开始的16个字节,但当我们对空间进行赋值之后,program break的值就变成0x9618000,一下子就变大了很多,这是疑问?

3.我们继续修改程序:

#include <stdio.h>   
#include <unistd.h>
#include <apue.h>   
int main(){  
     void* p = sbrk(0);  //获取当前program break的当前位置(此时是heap的起始地址)
     int* p1 = (int *)p; // 将当前空指针转化成int型指针
     int *p2,*temp;
     brk(p1+4);  
     printf("program break is :%p\n",sbrk(0)); 
     p1[0] = 10;  
     printf("program break is :%p\n",sbrk(0)); 
     p1[1] = 20;  
     p1[2] = 30;  
     p1[3] = 40;  
     p1[4] = 50;     
     printf("%p\n",&p1[0]);
     printf("%p\n",&p1[1]);
     printf("%p\n",&p1[2]);
     printf("%p\n",&p1[3]); 
     temp=p1+4;
     brk(temp+2);
     temp[0]=60;
     temp[1]=70;
     printf("%p\n",&temp[0]);
     printf("%p\n",&temp[1]);
     printf("program break is :%p\n",sbrk(0)); 
}  

输出结果为:

这个时候我们看到program break的位置本来已经在一个足够大的位置上(0x962f000),但是在追加申请空间之后又变回到0x960d018(这个偏移量刚好为24个字节,是6个int型变量的大小),为什么program break从0x962f000回退到了0x960d018,这又是个疑问?

4.下面来看看第二个程序:

#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
int main(){
	void* ptr, *ptr1,*ptr2;
	ptr = sbrk(0);
	printf("sbrk:%p\n", ptr);
	ptr1 = malloc(100);
	ptr = sbrk(0);
	printf("sbrk:%p, ptr1:%p\n", ptr, ptr1);
	free(ptr1);
	ptr2=malloc(50);
	ptr = sbrk(0);
	printf("sbrk:%p,ptr2:%p\n",ptr,ptr2);
}
输出结果为:

我们可以看到ptr的当前地址为0x991f000,这个地址就是堆空间的起始地址,也是program break的位置,后来我们调用malloc()在堆上分配100字节的空间,这100字节空间起始地址为0x991f410(至于为什么和0x991f000相差这么多还没搞明白),如果是正真分配100字节,那么program break的位置应该是(0x991f410+100字节=0x991f474),但是我们显示program break当前位置(0x9940000)的时候却比0x991f474大很多,说明系统一次性就分配了比100字节大很多的堆空间,至于为什么?我想如果每次分配几字节或几十字节的空间都调用sbrk()函数的话,系统开销太大,所以索性一次分配足够大的空间。当进程需要另外申请空间时,系统可能的工作就是在这个一次性分配足够大的剩余空间里再分配。至于为什么不从0x991f000直接开始分配,初步设想是0x991f000-0x991f410这段堆空间肯能用作记录目前堆的分配情况。

针对以上三个疑问,目前还在探索中,希望已经深入理解这一块的大牛指点!!!!!!


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值