【图解】Linux下C程序进程地址空间布局 .

 作者:沧海猎人   出处:http://blog.csdn.net/embedded_hunter  转载请注明出处   嵌入式技术交流QQ群:179012822

我们在学习C程序开发时经常会遇到一些概念:代码段、数据段、BSS段(Block Started by Symbol) 、堆(heap)和栈(stack)。先看一张教材上的示意图(来源,《UNIX环境高级编程》一书),显示了进程地址空间中典型的存储区域分配情况。

          

从图中可以看出:

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

还经常看到下面这个图(来源,不详):

                                                                  

 

先看一段程序。

 

#include <stdio.h>
#include <stdlib.h>

int global_init_a=1;
int global_uninit_a;
static int static_global_init_a=1;
static int static_global_uninit_a;
const int const_global_a=1;

int global_init_b=1;
int global_uninit_b;
static int static_global_init_b=1;
static int static_global_uninit_b;
const int const_global_b=1;
/*上面全部为全局变量,main函数中的为局部变量*/
int main()
{
	int local_init_a=1;
	int local_uninit_a;
	static int static_local_init_a=1;
	static int static_local_uninit_a;
	const int const_local_a=1;

	int local_init_b=1;
	int local_uninit_b;
	static int static_local_init_b=1;
	static int static_local_uninit_b;
	const int const_local_b=1;

	int * malloc_p_a;
	malloc_p_a=malloc(sizeof(int));

	printf("\n         &global_init_a=%p \t
         global_init_a=%d\n",&global_init_a,global_init_a);	

	printf("       &global_uninit_a=%p \t
        global_uninit_a=%d\n",&global_uninit_a,global_uninit_a);	

	printf("  &static_global_init_a=%p \t
        static_global_init_a=%d\n",&static_global_init_a,static_global_init_a);

	printf("&static_global_uninit_a=%p \t
        static_global_uninit_a=%d\n",&static_global_uninit_a,static_global_uninit_a);

	printf("        &const_global_a=%p \t
        const_global_a=%d\n",&const_global_a,const_global_a);	

	printf("\n         &global_init_b=%p \t
        global_init_b=%d\n",&global_init_b,global_init_b);	

	printf("       &global_uninit_b=%p \t
        global_uninit_b=%d\n",&global_uninit_b,global_uninit_b);	

	printf("  &static_global_init_b=%p \t
        static_global_init_b=%d\n",&static_global_init_b,static_global_init_b);

	printf("&static_global_uninit_b=%p \t
        static_global_uninit_b=%d\n",&static_global_uninit_b,static_global_uninit_b);

	printf("        &const_global_b=%p \t
        const_global_b=%d\n",&const_global_b,const_global_b);

	printf("\n          &local_init_a=%p \t
        local_init_a=%d\n",&local_init_a,local_init_a);	

	printf("        &local_uninit_a=%p \t
        local_uninit_a=%d\n",&local_uninit_a,local_uninit_a);

	printf("   &static_local_init_a=%p \t
        static_local_init_a=%d\n",&static_local_init_a,static_local_init_a);

	printf(" &static_local_uninit_a=%p \t
        static_local_uninit_a=%d\n",&static_local_uninit_a,static_local_uninit_a);	

	printf("         &const_local_a=%p \t
        const_local_a=%d\n",&const_local_a,const_local_a);	

	printf("\n          &local_init_b=%p \t
        local_init_b=%d\n",&local_init_b,local_init_b);	

	printf("        &local_uninit_b=%p \t
        local_uninit_b=%d\n",&local_uninit_b,local_uninit_b);

	printf("   &static_local_init_b=%p \t
        static_local_init_b=%d\n",&static_local_init_b,static_local_init_b);

	printf(" &static_local_uninit_b=%p \t
        static_local_uninit_b=%d\n",&static_local_uninit_b,static_local_uninit_b);	

	printf("         &const_local_b=%p \t
        const_local_b=%d\n",&const_local_b,const_local_b);

	printf("             malloc_p_a=%p \t
        *malloc_p_a=%d\n",malloc_p_a,*malloc_p_a);

	return 0;
}

 

下面是输出结果。

          

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

 

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

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

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

     

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

  #ps -aux | grep a.out

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

  #cat  /proc/2699/maps

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

  • 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文件)的过程如下,此图引自《程序员的自我修养》一书的一个图:

                                 

可以通过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文件中,找不到局部非静态变量和动态分配的内容。

 

以上有很多地方的分析,我在网上基本找不到很明确的结论,很多教材上也没有描述,只是我通过程序分析得出的结论,如有不妥之处,请指出,欢迎交流。

作者:沧海猎人   出处:http://blog.csdn.net/embedded_hunter  转载请注明出处   嵌入式技术交流QQ群:179012822

时间: 2024-08-02 19:46:10

【图解】Linux下C程序进程地址空间布局 .的相关文章

Linux x86_64下进程地址空间布局

关于Linux 32位内存下的内存空间布局,可以参考这篇博文Linux下C程序进程地址空间局关于源代码中各种数据类型/代码在elf格式文件以及进程空间中所处的段,在x86_64下和i386下是类似的,本文主要关注vm.legacy_va_layout以及kernel.randomize_va_space参数影响下的进程空间内存宏观布局. 情形一: vm_legacy_va_layout=1 kernel.randomize_va_space=0 此种情况下采用传统内存布局方式,不开启随机化 ca

linux下某程序中实现对进程的实时流量监控功能

问题描述 linux下某程序中实现对进程的实时流量监控功能 求大牛赐教 现在开发了一个程序,在linux下跑,想在里面加一个对特定进程的网络流量监控,实时统计进程流量大小 现在想到的办法就是用libpcap库,对应/proc里面文件按照pid 端口号 数据包 数据大小 进行统计得出当前流量大小. 目前有如下问题: 1.程序中已有功能中已经使用了libpcap去抓去一段数据包然后输出libpcap文件,如果按照上述办法,会不会造成再用libpcap采集数据包出问题?或者说libpcap可不可以多次

python-如何在linux下开启守护进程

问题描述 如何在linux下开启守护进程 问题是这样的:我用python写了两个模块:Store.py,Search.py,在这两个文件中,分别会开启Store线程和Search线程.这两个线程是需要一直开启的,如果发现这两个线程挂了,需要重新开启. 我之前的做法是:在linux的begin.sh脚本中写下如下内容: #!/bin/bash python Store.py python Search.py 然后执行./begin.sh. 然后出现下面的问题: 由于Store.py中开启了线程,程

Linux下c++程序内存泄漏检测代码范例

Linux下对于程序内存泄漏检测的方法很多,最常用的的莫过于使用valgrind工具.但是valgrind相当于让程序在虚拟机中运行,会带 来较大的系统资源开销,还会对程序的运行效率产生较大影响,对于那种资源占用大的程序,如果需要长时间运行才能暴露的泄漏问题,它就显得不太好用. linux下的c++程序中自己实现一个轻量级的泄漏检测代码其实是比较方便的,下面我就给出一个简单的范例,并作简单的说明.当然,我们还是应该提倡使用共享指针,用共享指针自动管理内存可以避免内存泄漏这样的不必要的麻烦. 基本

总结UNIX/LINUX下C++程序计时的方法_C 语言

前言 良好的计时器可帮助程序开发人员确定程序的性能瓶颈,或对不同算法进行性能比较.但要精确测量程序的运行时间并不容易,因为进程切换.中断.共享的多用户.网络流量.高速缓存访问及转移预测等因素都会对程序计时产生影响. 下面看看小编为大家整理几个计时方法 方法一: 如果是想统计某个程序的运行时间,那么可以使用 time ./a.out 方法二: 如果是想对某个函数或者语句进行计时,那么有别的方法.比如说,gettimeofday函数.直接贴示例代码: #include <sys/time.h> v

linux下的守护进程_Linux

Linux下的常驻进程的作用不可忽略,但这里面的问题也不能忽略,怎么启动进程,怎么结束进程,怎么在进程挂掉之后重启进程都要设计的合理.下面看一个shell控制的php常驻进程的例子. 不废话,直接捞干货,上代码,通过代码来讲解更容易理解: 复制代码 代码如下: #!/bin/sh #filename test.sh #绝对定位该文件的位置,不随执行目录而变化 cd $(cd "$(dirname "$0")";pwd) readonly path=$(pwd)/ f

Linux下C程序的编辑,编译和运行以及调试

国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html 内部邀请码:C8E245J (不写邀请码,没有现金送)国内私募机构九鼎控股打造,九鼎投资是在全国股份转让系统挂牌的公众公司,股票代码为430719,为"中国PE第一股",市值超1000亿元.  -----------------------------------------------------------------------------

Linux下读取默认MAC地址的方法

  Linux下读取默认MAC地址的方法           MAC(Media Access Control,介质访问控制)计算机通过它来定义并识别网络设备的位置.在嵌入式linux学习中不可避免也会遇到MAC,本文主要描述了如何通过操作OTP来读取嵌入式linux设备网卡中的MAC地址 一.适用范围 这里主要介绍读取网卡MAC地址的方法,适用于EasyARM-i.MX287A开发套件,其应用原理及配套示例也适用于下表1.1所列出的产品型号. 二.原理介绍 MAC(Media Access C

多线程-linux 下c 程序,开了1024个线程 依次等待共同完成某个任务,程序异常退出,不出core

问题描述 linux 下c 程序,开了1024个线程 依次等待共同完成某个任务,程序异常退出,不出core 程序简单来说类似一个多线程下载器,开了1024个线程,然后并发去服务器读取一个大文件的某一块,读取完成后,文件合并要按照顺序写文件,所以我采用了pthread_join依次等待上一个线程写完成操作.测试时候发现程序偶尔会突然down掉,也不出core,并不是总down,也会有成功执行时候.机器配制足够高了,内存96G,24核cpu...希望大家帮忙分析下..谢谢,代码逻辑如下. void*