转载自:http://blog.csdn.net/embedded_hunter

    http://www.360doc.com/content/12/0405/00/1671317_200882538.shtml

Linux应用程序在内存中的布局,由高地址到低地址依次为:栈、堆、BSS段、数据段、代码段。

代码段的起始地址固定为0x8048000,无论哪一个应用程序它的代码段起始地址一定是0x8048000,这里的地址虚拟地址,映射到不同的物理地址中去。

堆主要用来分配动态内存,操作系统提供了malloc等内存分配机制来供程序员进行堆内存的分配,同时,堆内存的释放需要程序员来进行。malloc分配的是虚拟地址空间,和用到的实实在在的物理内存是两码事,只有真正往空间里写东西了,os内核会触发缺页异常,然后才真正得到物理内存。32位Linux系统总共有4G内存空间,Linux把最高的1G(0xC0000000-0xFFFFFFFF)作为内核空间,把低地址的3G(0x00000000-0xBFFFFFFF)作为用户空间。malloc函数在这3G的用户空间中分配内存,能分配到的大小不超过3G,需除去栈、数据段(初始化及未初始化的)、共享so及代码段等占的内存空间。

栈由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。栈的地址空间是由高向低减少的(先分配高地址)。在Linux中,用ulimit -a命令可以看到栈的最大分配空间(stack size)是8192kB,即8MB多。

进程地址空间中典型的存储区域分配:

从图中可以看出:

  • 从低地址到高地址分别为:代码段、(初始化)数据段、(未初始化)数据段(BSS)、堆、栈、命令行参数和环境变量
  • 堆向高内存地址生长
  • 栈向低内存地址生长

Linux中进程最大地址空间:

因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。其中,很重要的一点是虚拟地址空间,并不是实际的地址空间。进程地址空间是用多少分配多少,4G仅仅是最大限额而已。往往,一个进程的地址空间总是小于4G的,你可以通过查看/proc/pid/maps文件来获悉某个具体进程的地址空间。但进程的地址空间并不对应实际的物理页,Linux采用Lazy的机制来分配实际的物理页("Demand paging"和"和写时复制(Copy On Write)的技术"),从而提高实际内存的使用率。即每个虚拟内存页并不一定对应于物理页。虚拟页和物理页的对应是通过映射的机制来实现的,即通过页表进行映射到实际的物理页。因为每个进程都有自己的页表,因此可以保证不同进程从上到下(地址从高到低)依次为栈(函数内部局部变量),堆(动态分配内存) ,bss段(存未初始化的全局变量),数据段(存初始化的全局变量),文本段(存代码)同虚拟地址可以映射到不同的物理页,从而为不同的进程都可以同时拥有4G的虚拟地址空间提供了可能。

代码测试1:

#include <stdio.h>
#include <stdlib.h> char bss_1[40];
static double bss_2;
int data_1 = 13;
static long data_2 = 2001; int main(int argc, char *argv[])
{
int stack_1 = 3, stack_2, *heap_1, *heap_2;
heap_1 = malloc(sizeof(stack_1));
heap_2 = malloc(sizeof(stack_1));
bss_1[5] = stack_1;
bss_2 = 2.0 * data_1;
printf("stack segment: stack_1:%p, stack_2:%p\n", &stack_1, &stack_2);
printf("heap segment: heap_1:%p, heap_2:%p\n", heap_1, heap_2);
printf("bss segment: bss_1:%p, bss_2:%p\n", bss_1, &bss_2);
printf("data segment: data_1:%p, data_2:%p\n", &data_1, &data_2);
printf("the stack top is near %p\n", &stack_1);
return 0;
}

运行结果:
stack segment: stack_1:0xbfab012c, stack_2:0xbfab0128
stack segment: stack_1:0xbfb9e2ec, stack_2:0xbfb9e2e8
heap segment: heap_1:0x8c56008, heap_2:0x8c56018
bss segment:  bss_1:0x804a040, bss_2:0x804a028
data segment: data_1:0x804a018, data_2:0x804a01c
the stack top is near 0xbfb9e2ec

由此可见:从上到下(地址从高到低)依次为栈(函数内部局部变量),动态链接库,堆(动态分配内存),bss段(存未初始化的全局变量),数据段(存初始化的全局变量),文本段(存代码)

在 linux 下,程序运行时空间的分配情况:
  #cat /proc/self/maps
  1. ===============================================================================
  2. 00110000-00267000 r-xp 00000000 08:08 406311 /lib/libc-2.12.1.so
  3. 00267000-00269000 r--p 00157000 08:08 406311 /lib/libc-2.12.1.so
  4. 00269000-0026a000 rw-p 00159000 08:08 406311 /lib/libc-2.12.1.so
  5. 0026a000-0026d000 rw-p 00000000 00:00 0
  6. 0038f000-00390000 r-xp 00000000 00:00 0 [vdso]
  7. 0040e000-0042a000 r-xp 00000000 08:08 402927 /lib/ld-2.12.1.so
  8. 0042a000-0042b000 r--p 0001b000 08:08 402927 /lib/ld-2.12.1.so
  9. 0042b000-0042c000 rw-p 0001c000 08:08 402927 /lib/ld-2.12.1.so
  10. 08048000-08051000 r-xp 00000000 08:08 390934 /bin/cat
  11. 08051000-08052000 r--p 00008000 08:08 390934 /bin/cat
  12. 08052000-08053000 rw-p 00009000 08:08 390934 /bin/cat
  13. 09058000-09079000 rw-p 00000000 00:00 0 [heap]
  14. b740f000-b75a2000 r--p 002a3000 08:08 130931 /usr/lib/locale/locale-archive
  15. b75a2000-b77a2000 r--p 00000000 08:08 130931 /usr/lib/locale/locale-archive
  16. b77a2000-b77a3000 rw-p 00000000 00:00 0
  17. b77b5000-b77b7000 rw-p 00000000 00:00 0
  18. bf987000-bf9a8000 rw-p 00000000 00:00 0 [stack]
  19. ===============================================================================

解释:

每个区间都有四个属性:
r 表示可以读取。
w 表示可以修改。
x 表示可以执行。
p/s 表示是否为共享内存。
有文件名的内存区间,属性为 r—p 表示存放的是 rodata。
有文件名的内存区间,属性为 rw-p 表示存放的是 bss 和 data
有文件名的内存区间,属性为 r-xp 表示存放的是 text 数据。
没有文件名的内存区间,表示用 mmap 映射的匿名空间。
文件名为[stack]的内存区间表示是栈。
文件名为[heap]的内存区间表示是堆。

代码测试2:

#include <stdio.h>

int global_init_a = 1;                              //全局初始化的变量
int global_uinit_a;   //全局未初始化的变量
static int static_global_init_a = 1; //全局静态初始化变量
static int static_global_uinit_a; //全局静态未初始化变量
const int const_global_a = 1;   //全局常量 int global_init_b = 1;   //全局初始化的变量
int global_uinit_b; //全局未初始化的变量
static int static_global_init_b = 1; //全局静态初始化变量
static int static_global_uinit_b; //全局静态未初始化变量
const int const_global_b = 1; //全局常量 void main()
{
int local_init_a = 1; //局部初始化变量
int local_uinit_a; //局部未初始化变量
static int static_local_init_a = 1; //局部静态初始化变量
static int static_local_uinit_a; //局部静态未初始化变量
const int const_local_a = 1; //局部常量 int local_init_b = 1;   //局部初始化变量
int local_uinit_b; //局部未初始化变量
static int static_local_init_b = 1; //局部静态初始化变量
static int static_local_uinit_b; //局部静态未初始化变量
const int const_local_b = 1; //局部常量 int *malloc_p_a;
malloc_p_a = malloc(sizeof(int)); //通过malloc分配得到的局部 printf("&global_init_a=%p,global_init_a=%d\n",&global_init_a,global_init_a);
printf("&global_uinit_a=%p,global_uinit_a=%d\n",&global_uinit_a,global_uinit_a);
printf("&static_global_init_a=%p,static_global_init_a=%d\n",&static_global_init_a,static_global_init_a);
printf("&static_global_uinit_a%p,static_global_uinit_a=%d\n",&static_global_uinit_a,static_global_uinit_a);
printf("&const_global_a=%p,const_global_a=%d\n",&const_global_a,const_global_a); printf("&global_init_b=%p,global_init_b=%d\n",&global_init_b,global_init_b);
printf("&global_uinit_b=%p,global_uinit_b=%d\n",&global_uinit_b,global_uinit_b);
printf("&static_global_init_b=%p,static_global_init_b=%d\n",&static_global_init_b,static_global_init_b);
printf("&static_global_uinit_b%p,static_global_uinit_b=%d\n",&static_global_uinit_b,static_global_uinit_b);
printf("&const_global_b=%p,const_global_b=%d\n",&const_global_b,const_global_b); printf("&local_init_a=%p,local_init_a=%d\n",&local_init_a,local_init_a);
printf("&local_uinit_a=%p,local_uinit_a=%d\n",&local_uinit_a,local_uinit_a);
printf("&static_local_init_a=%p,static_local_init_a=%d\n",&static_local_init_a,static_local_init_a);
printf("&static_local_uinit_a%p,static_local_uinit_a=%d\n",&static_local_uinit_a,static_local_uinit_a);
printf("&const_local_a=%p,const_local_a=%d\n",&const_local_a,const_local_a); printf("&local_init_b=%p,local_init_b=%d\n",&local_init_b,local_init_b);
printf("&local_uinit_b=%p,local_uinit_b=%d\n",&local_uinit_b,local_uinit_b);
printf("&static_local_init_b=%p,static_local_init_b=%d\n",&static_local_init_b,static_local_init_b);
printf("&static_local_uinit_b%p,static_local_uinit_b=%d\n",&static_local_uinit_b,static_local_uinit_b);
printf("&const_local_b=%p,const_local_b=%d\n",&const_local_b,const_local_b); printf("malloc_p_a=%p,*malloc_p_a=%d\n",malloc_p_a,*malloc_p_a); while(1);
}

  

Linux下查看程序运行时的内存空间分配

#ps aux     #查看进程ID

#cat /proc/进程ID/maps

下面是输出结果。

先仔细分析一下上面的输出结果,看看能得出什么结论。貌似很难分析出来什么结果。好了我们继续往下看吧。

接下来,通过查看proc文件系统下的文件,看一下这个进程的真实内存分配情况。(我们需要在程序结束前加一个死循环,不让进程结束,以便我们进一步分析)。

在return 0前,增加 while(1); 语句

重新编译后,运行程序,程序将进入死循环。

使用ps命令查看一下进程的pid

#ps -aux | grep a.out

查看/proc/2699/maps文件,这个文件显示了进程在内存空间中各个区域的分配情况。

#cat  /proc/2699/maps

文件有6列,分别为:
  地址:库在进程里地址范围
  权限:虚拟内存的权限,r=读,w=写,x=,s=共享,p=私有;
  偏移量:库在进程里地址范围
  设备:映像文件的主设备号和次设备号;
  节点:映像文件的节点号;
  路径: 映像文件的路径

上面红颜色标出的几个区间是我们感兴趣的区间:

  • 08048000-08049000  r-xp 代码段
  • 08049000-0804a000 r--p
  • 0804a000-0804b000 rw-p  数据段
  • 08a7e000-08a9f000  rw-p  堆
  • bff73000-bff88000     rw-p  栈

我们把这些数据与最后一次的程序运行结果进行比较,看看有什么结论。

&global_init_a=0x804a018       全局初始化:数据段              global_init_a=1
            &global_uninit_a=0x804a04c      全局未初始化:数据段          global_uninit_a=0
     &static_global_init_a=0x804a01c      全局静态初始化:数据段      static_global_init_a=1
&static_global_uninit_a=0x804a038      全局静态未初始化:数据段     static_global_uninit_a=0
             &const_global_a=0x80487c0     全局只读变量: 代码段        const_global_a=1

&global_init_b=0x804a020       全局初始化:数据段      global_init_b=1
            &global_uninit_b=0x804a048        全局未初始化:数据段      global_uninit_b=0
     &static_global_init_b=0x804a024        全局静态初始化:数据段    static_global_init_b=1
&static_global_uninit_b=0x804a03c        全局静态未初始化:数据段   static_global_uninit_b=0
            &const_global_b=0x80487c4        全局只读变量: 代码段             const_global_b=1

&local_init_a=0xbff8600c          局部初始化:栈                     local_init_a=1
             &local_uninit_a=0xbff86008         局部未初始化:栈                 local_uninit_a=134514459
     &static_local_init_a=0x804a028         局部静态初始化:数据段      static_local_init_a=1
 &static_local_uninit_a=0x804a040        局部静态未初始化:数据段     static_local_uninit_a=0
             &const_local_a=0xbff86004        局部只读变量:栈     const_local_a=1

&local_init_b=0xbff86000        局部初始化:栈          local_init_b=1
                &local_uninit_b=0xbff85ffc         局部未初始化:栈        local_uninit_b=-1074241512
      &static_local_init_b=0x804a02c        局部静态初始化:数据段      static_local_init_b=1
 &static_local_uninit_b=0x804a044        局部静态未初始化:数据段      static_local_uninit_b=0
                &const_local_b=0xbff85ff8        局部只读变量:栈        const_local_b=1

p_chars=0x80487c8        字符串常量:代码段          p_chars=abcdef
                    malloc_p_a=0x8a7e008        malloc动态分配:堆        *malloc_p_a=0

通过以上分析我们暂时可以得到的结论如下,在进程的地址空间中

  • 数据段中存放:全局变量(初始化以及未初始化的)、静态变量(全局的和局部的、初始化的以及未初始化的)
  • 代码段中存放:全局只读变量(const)、字符串常量
  • 堆中存放:动态分配的区域
  • 栈中存放:局部变量(初始化以及未初始化的,但不包含静态变量)、局部只读变量(const)

这里我们没有发现BSS段,但是我们将未初始化的数据按照地址进行排序看一下,可以发现一个规律。

&global_init_a=0x804a018       全局初始化:数据段              global_init_a=1
    &static_global_init_a=0x804a01c      全局静态初始化:数据段      static_global_init_a=1
                &global_init_b=0x804a020       全局初始化:数据段      global_init_b=1
    &static_global_init_b=0x804a024        全局静态初始化:数据段    static_global_init_b=1
       &static_local_init_a=0x804a028         局部静态初始化:数据段      static_local_init_a=1
       &static_local_init_b=0x804a02c        局部静态初始化:数据段      static_local_init_b=1

&static_global_uninit_a=0x804a038      全局静态未初始化:数据段     static_global_uninit_a=0
&static_global_uninit_b=0x804a03c        全局静态未初始化:数据段   static_global_uninit_b=0
  &static_local_uninit_a=0x804a040        局部静态未初始化:数据段     static_local_uninit_a=0
  &static_local_uninit_b=0x804a044        局部静态未初始化:数据段      static_local_uninit_b=0
           &global_uninit_b=0x804a048        全局未初始化:数据段      global_uninit_b=0
            &global_uninit_a=0x804a04c      全局未初始化:数据段          global_uninit_a=0

这里可以发现,初始化的和未初始化的数据好像是分开存放的,因此我们可以猜测BSS段是存在的,只不过数据段是分为初始化和未初始化(即BSS段)的两部分,他们在加载到进程地址空间时是合并为数据段了,在进程地址空间中没有单独分为一个区域。

还有一个问题,静态数据与非静态数据是否是分开存放的呢?请读者自行分析一下。

接下来我们从程序的角度看一下,这些存储区域是如何分配的。首先我们先介绍一下ELF文件格式。

ELF(Executable and Linkable Format )文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:
–可重定位的目标文件(Relocatable,或者Object File)
–可执行文件(Executable)
–共享库(Shared Object,或者Shared Library)
 
下图为ELF文件的结构示意图(来源,不详):

一个程序编译生成目标代码文件(ELF文件)的过程如下,此图引自《程序员的自我修养》一书的一个图:

      !此处貌似有个错误 int a=1; int b;应该存储在栈区

可以通过readelf命令查看EFL文件的相关信息,例如 readelf  -a  a.out  ,我们只关心各个段的分配情况,因此我们使用以下命令:

    # readelf -S a.out
                        

将这里的内存布局与之前看到的程序的运行结果进行分析:

&global_init_a=0x804a018       全局初始化:数据段              global_init_a=1
            &global_uninit_a=0x804a04c      全局未初始化:BSS段          global_uninit_a=0
     &static_global_init_a=0x804a01c      全局静态初始化:数据段      static_global_init_a=1
&static_global_uninit_a=0x804a038      全局静态未初始化:BSS段     static_global_uninit_a=0
             &const_global_a=0x80487c0     全局只读变量: 只读数据段        const_global_a=1

&global_init_b=0x804a020       全局初始化:数据段      global_init_b=1
            &global_uninit_b=0x804a048        全局未初始化:BSS段      global_uninit_b=0
     &static_global_init_b=0x804a024        全局静态初始化:数据段    static_global_init_b=1
&static_global_uninit_b=0x804a03c        全局静态未初始化:BSS段   static_global_uninit_b=0
            &const_global_b=0x80487c4        全局只读变量: 只读数据段             const_global_b=1

&static_local_init_a=0x804a028         局部静态初始化:数据段      static_local_init_a=1
 &static_local_uninit_a=0x804a040        局部静态未初始化:BSS段     static_local_uninit_a=0

&static_local_init_b=0x804a02c        局部静态初始化:数据段      static_local_init_b=1
 &static_local_uninit_b=0x804a044        局部静态未初始化:BSS段      static_local_uninit_b=0

p_chars=0x80487c8        字符串常量:只读数据段          p_chars=abcdef
ELF 文件一般包含以下几个段 :

  • .text section:主要是编译后的源码指令,是只读字段。
  • .data section :初始化后的非const的全局变量、局部static变量。
  • .bss:未初始化后的非const全局变量、局部static变量。
  • .rodata字段  是存放只读数据 

分析到这以后,我们在和之前分析的结果对比一下,会发现确实存在BSS段,地址为0804a030 ,大小为0x20,之前我们的程序中未初始化的的确存放在这个地址区间中了,只不过执行exec系统调用时,将这部分的数据初始化为0后,放到了进程地址空间的数据段中了,在进程地址空间中就没有必要存在BSS段了,因此都称做数据段。同理,.rodata字段也是与text段放在一起了。

在ELF文件中,找不到局部非静态变量和动态分配的内容。

Linux应用程序的地址布局的更多相关文章

  1. linux应用程序地址布局,王明学learn

    linux应用程序地址布局 在学习Linux应用程序开发时,经常会遇到如下概念:代码段.数据段.BSS段(Block Started by Symbol,又名:未初始化数据段).堆(heap)和栈(s ...

  2. linux应用程序地址布局

    Linux应用程序在内存中的布局,由高地址到低地址依次为:栈.堆.BSS段.数据段.代码段.代码段的起始地址固定为0x8048000,无论哪一个应用程序它的代码段起始地址一定是0x8048000,这里 ...

  3. [国嵌攻略][072][Linux应用程序地址布局]

    程序构成 代码段.数据段.BSS段(Block Started by Symbol,又叫:未初始化数据段).堆(heap)和栈(stack).这些部分构成了Linux应用程序的重要组成部分. 内存布局 ...

  4. 7、Linux应用程序地址布局

    程序构成 在学习Linux应用程序开发时,经常会遇到如下概念: 代码段.数据段.BSS段(Block Started by Symbol,又名:未始化数据段) .堆(heap)和栈(stack).始化 ...

  5. Linux C程序存储空间的逻辑布局

    原文:http://blog.chinaunix.net/uid-20692625-id-3057053.html ------------------------------------------ ...

  6. Linux C 程序 进程控制(17)

    进程控制 1.进程概述现代操作系统的特点在于程序的并行执行.Linux是一个多用户多任务的操作系统.ps .pstree 查看进程进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得.// ...

  7. Anatomy of a Program in Memory.剖析程序的内存布局

    原文标题:Anatomy of a Program in Memory 原文地址:http://duartes.org/gustavo/blog/ [注:本人水平有限,只好挑一些国外高手的精彩文章翻译 ...

  8. 嵌入式linux应用程序调试方法

    嵌入式linux应用程序调试方法 四 内存工具 五 C/C++代码覆盖.性能profiling工具 四 内存工具 您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形. 许多小组花了许许多多时间来 ...

  9. linux配置网卡IP地址命令详细介绍及一些常用网络配置命令

    linux配置网卡IP地址命令详细介绍及一些常用网络配置命令2010-- 个评论 收藏 我要投稿 Linux命令行下配置IP地址不像图形界面下那么方 便,完全需要我们手动配置,下面就给大家介绍几种配置 ...

随机推荐

  1. springMVC文件上传(转)

    原文链接: http://www.cnblogs.com/lonecloud/p/5989905.html 在Spring-mvc.xml注入bean 1 <!-- 配置文件上传,如果没有使用文 ...

  2. URL动态赋值

    url动态赋值: 指的是url中包含{selector},即花括号括起来的jQuery选择器,当提交该url时,框架会自动将selector对应元素的值替换到花括号所占区域. (感觉实现了一点类似el ...

  3. Swift 实现下拉刷新 JxbRefresh

    JxbRefresh 是采用Swift 实现的 iOS 下拉刷新. 正常下拉刷新: 1 2 3 4 5 self.taleView.addPullRefresh({ [weak self] in    ...

  4. julia与python中的列表解析.jl

    julia与python中的列表解析.jl #=julia与python中的列表解析.jl 2016年3月16日 07:30:47 codegay julia是一门很年轻的科学计算语言 julia文档 ...

  5. git——学习笔记(二)远程仓库

    GIT杀手锏之一——远程仓库 拥有远程仓库的两个办法 1:搭一个Git服务器  2:在GitHub上免费托管的Git仓库 本地仓库   远程仓库 一.在GitHub上免费托管的Git仓库 电脑: 1. ...

  6. CSS 3D旋转 hover 后设置transform 是相对于正常位置

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. Linux下TC使用说明

    Linux下TC使用说明   一.TC原理介绍 Linux操作系统中的流量控制器TC(Traffic Control)用于Linux内核的流量控制,主要是通过在输出端口处建立一个队列来实现流量控制. ...

  8. HD OJ2023

    #include "stdio.h"double stu[60],cla[10];int a[60][60];int main(){ int n,m,i,number,j; whi ...

  9. type safe printf

    在书里看到的,摘录如下: #include <iostream> #include <stdexcept> template<class T> struct is_ ...

  10. Interview----最长连续乘积字串

    题目描述: 给一个浮点数序列,取最大乘积连续子串的值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8. 也就是说,上述数组中,3 0.5 8这3个数的乘积3*0 ...