linux进程地址空间布局浅析

此文章是围绕该文章 的思路进行总结的,原文记录的笔记详尽,在此基础上,我总结了自己的一下(想对浅显)。
如果对具体细节想要了解的,可以访问这篇文章,在文章最后付了一段代码,跟该成程序的内存映射图,如果有哪里写的不正确,欢迎补充和指正。

程序是什么

抽象的来说当我们打开电脑点击应用程序图标,一个程序就运行起来了,
但是在操作系统层面来看,其实是产生了一个进程 ,这是一个程序的实体。

接下来以linux操作系统来介绍linux中进程的各个段和其代表的含义

linux虚拟地址空间

在linux操作系统中,每当生成一个进程时,它就具有了一个虚拟地址空间,通常在32位系统中,它的大小为4GB,按3:1的比例分配给用户空间和内核空间。

虚拟地址空间的简单工作原理就是将虚拟地址通过页表映射到实际物理地址(所以在c语言中malloc分配内存时,即使是malloc一个int大小的空间,但是实际上是分配了一个页的内存,一般是4096个字节)

用户内存空间的各个段分布

代码段:可执行的代码,字符串常量等

数据段: 1.数据段:已初始化的,且初值为非0的全局变量(static变量)
        2.bss段:未初始化,或者初值为0的全局变量 (static变量)
        
堆:可以理解为程序员在程序中自己为变量分配的内存(需手动释放)

栈:保存了函数被调用时所需要的局部变量,函数参数,返回地址。
    该进程在派生出很多线程时,线程栈也保存在这里。

栈说到底也是一种数据结构,它遵循着先进后出的顺序,所以特别适合函数的执行(进栈),函数返回(出栈)。
堆栈的用途分三种:
1.为函数内部声明的局部变量(非静态局部变量)分配内存空间。
2.记录函数调用信息,用栈帧表示。除此之外还有函数的返回地址,一些进出寄存器的变量。
3.临时存储区,用于暂存长算术表达式部分计算结果或alloca()函数分配的栈内内存。(不是很了解)

在此基础上,所以会经常引发一个编程上的问题,即:栈溢出, 这种情况在递归调用中出现的尤其多,
除此之外还有申请了内存,没有释放,也很可能造成内存泄露。避免这种情况,就需要自己注意内存的
释放,一般一个new对应一个的delete。

在linux中可以用ulimit -s 来查看堆栈的大小,下面的是我linux系统中的堆栈大小

[m@m ~]$ ulimit -s
8192

堆是程序运行时动态分配内存的内存段,它通常用malloc和new(底层也是malloc)来获得一段内存空间,用
free和delete来释放已经使用完毕的内存空间。

常见问题:

由于是通过程序员自己手动创建,所以一定要注意内存释放,问题,在类,main函数中动态分配的内存,
一般都能释放,但是有时在其他函数中new出来的内存空间,、并没有delete掉,导致内存泄露。
多次运行函数,可能程序会崩溃。

#include <iostream>

int* exp(int i){
        int *ptr = new int(i);
        return ptr;
}

int main(){

        int a = 1;
        int *p = exp(a);

        // delete ptr; // 错误! 由于函数中的ptr已经被回收,所以不能delete ptr
        delete p; // 释放定义在函数外部的指针是正确的
        p = NULL;
        return 0;
}

下面这句话是引用:

使用堆时经常出现两种问题:1) 释放或改写仍在使用的内存(“内存破坏”);2)未释放不再使用的内存(“内存泄漏”)。当释放次数少于申请次数时,可能已造成内存泄漏。泄漏的内存往往比忘记释放的数据结构更大,因为所分配的内存通常会圆整为下个大于申请数量的2的幂次(如申请212B,会圆整为256B)。

代码段

代码段也称正文段或文本段,通常用于存放程序执行代码(即CPU执行的机器指令)。一般C语言执行语句都编译成机器代码保存在代码段。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可。代码段通常属于只读,以防止其他程序意外地修改其指令(对该段的写操作将导致段错误)。某些架构也允许代码段为可写,即允许修改程序。

数据段

数据段通常用于存放程序中已初始化且初值不为0的全局变量(也包括静态全局变量)和静态局部变量

bss段

bss段通常存放以下两种情况:
1.未初始化的全局变量(静态全局变量)和局部静态变量
2.初始化为0的全局变量(静态全局变量)和静态局部变量

内存映射区

除了这些之外,夹在stack和heap之间还有一个内存映射段(mmap
以下三段为引用

    此处,内核将硬盘文件的内容直接映射到内存, 任何应用程序都可通过Linux的mmap()系统调用或Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式, 因而被用于装载动态共享库。用户也可创建匿名内存映射,该映射没有对应的文件, 可用于存放程序数据。在 Linux中,若通过malloc()请求一大块内存,C运行库将创建一个匿名内存映射,而不使用堆内存。”大块” 意味着比阈值 MMAP_THRESHOLD还大,缺省为128KB,可通过mallopt()调整。

最后附上一段自己写的代码用来查看内存布局

Linux下在/proc/进程号/maps 查看内存映射

源代码:
    #include <iostream>

    void get(){
            int e;
            int f = 4;
            static int g;
            static int h = 5;
            std::cout << "in get(), the address a is " << &e << std::endl;
            std::cout << "in get(), the address b is " << &f << std::endl;
            std::cout << "in get(), the address c is " << &g << std::endl;
            std::cout << "in get(), the address d is " << &h << std::endl;
            while(1);  // 让程序死在这用来查看内存映射表
    }
    int main(){

            int a = 1;
            int b;
            static int c = 2;
            static int d;
            int *p = new int(3);
            std::cout << "in main, the address a is " << &a << std::endl;
            std::cout << "in main, the address b is " << &b << std::endl;
            std::cout << "in main, the address c is " << &c << std::endl;
            std::cout << "in main, the address d is " << &d << std::endl;
            std::cout << "in main, the address p is " << p << std::endl;
            get();  // 程序在这进入死循环

            delete p;
            p = NULL;
            return 0;
    }

运行结果:
    in main, the address a is 0x7ffd270384a8
    in main, the address b is 0x7ffd270384a4
    in main, the address c is 0x602074
    in main, the address d is 0x60219c
    in main, the address p is 0x1c06c20
    in get(), the address a is 0x7ffd2703844c
    in get(), the address b is 0x7ffd27038448
    in get(), the address c is 0x602198
    in get(), the address d is 0x602070

    
在内存中,各个变量所处在各个段的虚拟地址:
    [m@m ~/practice]$ cat /proc/16138/maps 
    00400000-00401000 r-xp 00000000 08:06 2364212                            /home/m/practice/12.out
    00601000-00602000 r--p 00001000 08:06 2364212                            /home/m/practice/12.out
    00602000-00603000 rw-p 00002000 08:06 2364212                            /home/m/practice/12.out
    01bf5000-01c27000 rw-p 00000000 00:00 0                                  [heap]
    7fa3c231b000-7fa3c24b0000 r-xp 00000000 08:07 2361747                    /lib/x86_64-linux-gnu/libc-2.24.so
    7fa3c24b0000-7fa3c26b0000 ---p 00195000 08:07 2361747                    /lib/x86_64-linux-gnu/libc-2.24.so
    7fa3c26b0000-7fa3c26b4000 r--p 00195000 08:07 2361747                    /lib/x86_64-linux-gnu/libc-2.24.so
    7fa3c26b4000-7fa3c26b6000 rw-p 00199000 08:07 2361747                    /lib/x86_64-linux-gnu/libc-2.24.so
    7fa3c26b6000-7fa3c26ba000 rw-p 00000000 00:00 0 
    7fa3c26ba000-7fa3c26d0000 r-xp 00000000 08:07 2359364                    /lib/x86_64-linux-gnu/libgcc_s.so.1
    7fa3c26d0000-7fa3c28cf000 ---p 00016000 08:07 2359364                    /lib/x86_64-linux-gnu/libgcc_s.so.1
    7fa3c28cf000-7fa3c28d0000 r--p 00015000 08:07 2359364                    /lib/x86_64-linux-gnu/libgcc_s.so.1
    7fa3c28d0000-7fa3c28d1000 rw-p 00016000 08:07 2359364                    /lib/x86_64-linux-gnu/libgcc_s.so.1
    7fa3c28d1000-7fa3c29d4000 r-xp 00000000 08:07 2361751                    /lib/x86_64-linux-gnu/libm-2.24.so
    7fa3c29d4000-7fa3c2bd3000 ---p 00103000 08:07 2361751                    /lib/x86_64-linux-gnu/libm-2.24.so
    7fa3c2bd3000-7fa3c2bd4000 r--p 00102000 08:07 2361751                    /lib/x86_64-linux-gnu/libm-2.24.so
    7fa3c2bd4000-7fa3c2bd5000 rw-p 00103000 08:07 2361751                    /lib/x86_64-linux-gnu/libm-2.24.so
    7fa3c2bd5000-7fa3c2d47000 r-xp 00000000 08:07 3676435                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24
    7fa3c2d47000-7fa3c2f47000 ---p 00172000 08:07 3676435                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24
    7fa3c2f47000-7fa3c2f51000 r--p 00172000 08:07 3676435                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24
    7fa3c2f51000-7fa3c2f53000 rw-p 0017c000 08:07 3676435                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.24
    7fa3c2f53000-7fa3c2f56000 rw-p 00000000 00:00 0 
    7fa3c2f56000-7fa3c2f79000 r-xp 00000000 08:07 2359411                    /lib/x86_64-linux-gnu/ld-2.24.so
    7fa3c3151000-7fa3c3155000 rw-p 00000000 00:00 0 
    7fa3c3176000-7fa3c3179000 rw-p 00000000 00:00 0 
    7fa3c3179000-7fa3c317a000 r--p 00023000 08:07 2359411                    /lib/x86_64-linux-gnu/ld-2.24.so
    7fa3c317a000-7fa3c317b000 rw-p 00024000 08:07 2359411                    /lib/x86_64-linux-gnu/ld-2.24.so
    7fa3c317b000-7fa3c317c000 rw-p 00000000 00:00 0 
    7ffd27018000-7ffd2703b000 rw-p 00000000 00:00 0                          [stack]
    7ffd2704a000-7ffd2704c000 r--p 00000000 00:00 0                          [vvar]
    7ffd2704c000-7ffd2704e000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
    原文作者:Micon
    原文地址: https://segmentfault.com/a/1190000011634766
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞