一起来学内存(二)

来源:互联网 发布:linux python 编辑器 编辑:程序博客网 时间:2024/04/27 23:52

————————————–尊重原创,从我做起;分享知识,标明出处。————————————–

在上一篇的博文中,博主和大家一起理清了物理内存虚拟内存的概念,并介绍了虚拟内存、物理内存的取址范围。在这里强烈建议,博友们先去看了上一篇“一起来学内存(一)”之后,再来看本篇博文,概念会更加清晰。

现在我们已经了解到,作为程序员,我们平常操作的内存地址都是虚拟内存的地址,该地址会由MMU映射称为物理内存地址。

那么平常我们经常听到的栈内存堆内存究竟是什么呢?与虚拟内存和物理内存又有什么关系呢?

要想搞清楚这些复杂的概念,我们首先得理清程序进程的关系。

这里博主以编译型语言C++、Linux操作系统为例,为大家讲一下整个程序运行的过程。

下图就是从C++源代码到可执行文件的全过程

这里写图片描述

1.书写C++源代码 (hello.cpp)

    #include<iostream>    using std::cout;    int main(){        cout<<"hello world";        return 0;    }

这里写图片描述

2.将 .cpp源文件预处理为 .ii文件

这里写图片描述

注:若是 .c的源文件文件,应预处理为 .i文件

3.将 .ii文件生成 .s汇编文件

这里写图片描述

4.将 .s汇编文件生成 .o目标文件

这里写图片描述

5.将 .o目标文件链接需要的动态库或动态库生成 .out可执行文件

这里写图片描述

经过了这4步工作后,写好的.cpp文件终于变成了可执行程序了。

但是可执行程序(文件)又和进程有什么关系呢?

首先我们要搞清楚程序与进程的区别。

程序是一种静态的概念,而进程是一种动态的概念。进程是操作系统中能够竞争资源的最小单位。这些资源包括什么呢?当然就包括我们一直讨论的内存空间啦。

Linux系统下进程被分配的内存布局大体如下图所示:

这里写图片描述

栈区(Stack): 用来存放临时变量和一些函数参数等

堆区(Heap): 存放通过 new 或 malloc动态申请的变量,此块内存空间一般由程序员手动 delete或是 free

静态区(BSS Segment): 存放未被初始化的全局变量和静态变量

数据区(Data Segment): 存放被程序员初始化过的全局变量和静态变量

代码区(Text Segment): 存放可执行的二进制指令操作

所以我们平常所谈论的堆栈,其实是加载运行我们所写的可执行程序的进程所被分配的内存空间。

那么现在我们就来看看我们在执行可执行程序时,是如何将数据写入为进程分配的内存中去的。

以Linux 的BASH为例,我们会以shell 语句来执行上述生成的 a.out文件

    Shell> ./a.out
  1. 当执行一个可执行文件时,shell父进程首先会调用fork()方法,生成一个子进程。这个新生成的进程也就是执行 a.out程序的进程。这里简单提一下fork()方法,当父进程通过fork()方法创建子进程时,子进程会复制父进程的页目录与页表。之后通过写时复制(copy on write)技术,子进程就能拥有独立的页目录项。
  2. 在新的进程创建出来后,它将拥有自己的PID和虚拟空间。这时已经切换到了新的进程执行execve系统调用,来加载可执行文件 a.out的头部信息,并为其建立新的页表结构。通过可执行文件的头部信息,系统就能建立进程的地址空间与可执行文件的映射关系。但此时新进程的进程地址空间视图中代码段、数据段仍然为空,可执行文件相应的数据仍未拷贝到主存中。原来系统会通过可执行文件中头部信息指定的执行程序入口,逐步将相应的段数据拷贝到主存中,并利用缺页中断位置建立相应的页表结构。

这就是我们在执行一个可执行程序时,对于操作系统来说发生了什么。这篇博文可能对于一些基础不太牢固的同学来说有些深奥,但是大家可以根据自己不懂的地方去专项搜索一下。只要坚持看下去,总会理解的。

1 0
原创粉丝点击