Linux中进程地址空间详解

前言

今天我们将要进行关于进程地址空间的知识点的讲解,地址空间相信大多数人都听说过,但是大部分人其实并不知道其究竟是什么东西,今天就让我们一起来好好的分析一下。

一、实验引入

我们先写一个程序,通过查看其输出结果来引入我们今天要讲述的内容
在这里插入图片描述
话不多说,我们直接查看实验结果,然后进行分析
在这里插入图片描述
大家观察上面图片中画框的内容,在家可以看到在程序运行了五秒以后,子进程中的val的值被修改成功,并打印了相关的执行成功的语句,在后面程序的执行中,父进程和子进程中val的值是不一样的,相信大家很清楚这一点,因为子进程或者是父进程中一个进程内变量的值被修改的话,是不会影响另一个进程的,这个很好理解。但是大家再来看一下紫色框中内容,我们发现这两个进程中val变量的地址是相同的,这里相信大家就非常困惑了,为什么会出现这种情况呢??这两个变量的地址是相同的,但是为什么值不一样呢??同时读取的时候,出现了不同的值。
所以说我们看这里的结果就可以推断出这里的地址绝对不是物理内存的地址!!!!,所以这里其实是虚拟地址,我们平时在书上也叫作线性地址。但是我们习惯的将其称为虚拟地址
所以我们这里又可以引出一个结论,几乎所有的语言,如果它有“地址”的概念,这个地址一定不是物理地址,而是虚拟地址。

二、C语言空间布局详解

我们在学习C语言的时候一定听说过堆,栈等空间,下面我们就来看一下C/C++语言中的具体的空间布局图
在这里插入图片描述
可能大家看这个图并不是很理解,下面我们再来做一个实验给大家验证一下这些地址空间的分布。
在这里我们要说明的是我们在使用printf函数打印时其实就是先生成一个进程,然后再进行打印,所以这里的本质其实就是进程打印。
在做实验之前我们先给大家补充一个新的知识点
在这里插入图片描述
假如我们的目标文件proc要由多个文件生成,那么我们就可以使用一种新的语法,就是图中所写的第二行语句,$@的意思就是指的要生成的文件proc, $^指的就是第一行冒号后面的那一堆文件。
main表示代码段的数据
在这里插入图片描述
在这里插入图片描述
通过观察上面的地址并比较其大小我们可以很容易的看出在写代码的过程中我们定义的一些数据是满足地址空间分布图的。环境变量的地址要比命令行参数的地址还要高。而栈区和堆区的地址空间数值变化很大,这个其实是与共享区有关系的,这个我们以后再来细谈,大家只要知道有这么个内容就可以了。另外需要强调的一点是,static修饰局部变量的本质就是讲该变量开辟在全局区域,即堆区。
注意: 其实在写代码的过程中还有一个细节不知道大家有没有看到,我其实是先定义了一个变量但是没有将其初始化,然后又定义了一个变量并将其初始化,虽然顺序不同,但是最后的结果还是一样的,都是初始化数据的地址要比未初始化数据的地址低。
讲完了这一些内容其实还是远远不够的,这里我还要为大家补充一些知识。
在这里插入图片描述
我将代码做了修改,我们来看一下栈和堆上地址空间的规律。
在这里插入图片描述
**补充:**在这里教大家一下如何在vim模式下进行多行的注释以及如何取消多行的注释。
多行注释: 首先esc进入底行模式,然后ctrl+v,左下角就会出现V-BLOCK(视图模式)然后利用hjkl进行上下左右的选择代码,选好以后输入I(注意这里是大写的i),然后就会出现INSERT,然后输入//,在按esc键,这时候就完成了多行的批量化注释。
取消多行注释: 先esc进入底行模式,然后hjkl选中区域,然后输入d就可以把选中的行数全部取消注释了。


这里我们的再来补充一下字符常量区与正文代码区的关系
在这里插入图片描述
以上就是我们对于地址空间的排布的分析。

1.用户空间vs内核空间

在32位下,一个进程的地址空间,取值范围是0x0000 0000~0xFFFF FFFF,[0,3GB]:用户空间,[3GB,4GB]:内核空间(操作系统的内容)

2.Linux vs Windows

上面验证的代码,在Windows下会跑出不一样的结果,以上的结论默认只在Linux有效!

三、什么是地址空间

在这里我可以先给大家举一个例子,帮助大家更好地理解什么是地址空间。假如说从前有一个大富翁,他拥有十个亿的资产,同时他还有三个儿子,但是三个儿子并不知道彼此的存在,这时候父亲分别对三个儿子说要让他们好好学习,或者努力工作,优秀的话就会将自己这十亿的资产给他,这里其实就是富翁为自己的每一个儿子都画了一个大饼。而我们所说的大富翁就是操作系统,儿子指的则是进程,老爹给儿子画的饼就是地址空间,我们不仅有画的饼,同时还有现实中的饼,现实中的饼其实就是指我们能看得见,摸得到的东西,是一些真实存在的,而画的饼其实只是一个想象出来的,到底有没有其实你并不知道,所以现实中的饼指的就是物理内存。
上面的内容大家都明白了的话,我们再来阐述下一个问题,老爹要给三个儿子画饼,那么他一定要记得自己画的是什么样子的饼,不然如果自己忘了的话,或者把三个儿子的弄混了,那岂不是会很尴尬,操作系统的画饼其实也一样,也要以某种方法将其管理起来,这里的管理方式其实就是先描述,在组织。所以内核中的地址空间,本质将来也一定是一种数据结构,将来要将一个一个的进程都关联起来。


这里想必大家已经对于地址空间有一点点了解了。下面我们还要从历史与现代的时代角度带大家更好地去理解一下。如下图
在这里插入图片描述
下面我们再来看一下现代计算机。
在这里插入图片描述
有了以上的只是为大家做铺垫后,我们来谈一谈虚拟地址空间到底是什么。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有了以上的知识储备以后,我们就可以回答一下最开始的现象了,为什么子进程和父进程中同一个名称的变量值相同,但是地址不同。
这里其实发生了以下的过程,我们简单的来分析一下。
在这里插入图片描述

最开始的时候我们是只有父进程,然后在父进程中定义了val变量并为其赋值,然后我们利用fork()函数创建了子进程,其实在子进程创建的时候大部分的数据是从父进程直接拷贝拿过来用的,他们的地址空间基本相同,页表基本也相同,知识有一部分数据不同而已,这也就是为什么父进程的val与子进程的val地址相同,当我们在子进程中修改val的值的时候,就会发生写时拷贝,在这里我们先来简单的解释一下写时拷贝是什么意思。就拿我们实验时做的操作来说,我们不修改val的值的时候,其实子进程的页表还是对应着父进程的val的物理地址的,但是当我们要修改val的值的时候,操作系统就会修改页表,为子进程在物理地址中重新开辟一块空间,然后让子进程的页表指向新开辟的地址,但是地址空间与页表之间的关系并没有发生变化,也就是说val的虚拟地址其实并没有发生变化,但是他的物理地址已经发生变化了。这也就有了后面的实验现象,父子进程的val的地址是相同的,但是他们的数值却并不相同。


有了上面的知识储备以后,我们也就可以解决一下历史遗留下来的问题了。我们在使用fork()的时候一个变量怎么可能会同时保存不同的值??
在这里插入图片描述

四、为什么要有地址空间

1.凡是非法的访问或者映射,OS都会识别到,并且终止这个进程!!!
(所有的进程崩溃,都是进程的退出,其实在这里就是OS杀掉了这个进程)这样就有效的保护了物理内存,因为地址空间和页表是OS创建并维护的,也就意味着凡是想使用地址空间和页表进行映射,也一定要在OS的监管之下来进行访问。这时也保护了物理内存中的所有的合法数据,包括各个进程,以及内核的相关有效数据
2.因为地址空间的存在,因为有页表的映射的存在,我们的物理内存中,就可以对未来的数据进行任意位置的加载,其实物理内存的分配和进程的管理就可以做到没有任何的关系!!!(简单来讲就是我们可以把数据加载到物理内存的任意位置,因为有了页表的存在,可以通过页表将地址空间与物理内存相映射)。所以在这里的设计使得内存管理模块和进程管理模块完成了解耦合!(解耦合:减少模块和模块间的关联性)。所以我们在C/C++语言上new、malloc空间的时候,本质上是在虚拟地址空间申请的。
这里就又会出现一个新的问题,如果我申请了物理空间,但是如果我不立马使用是不是就是空间的浪费呢?是的!!!
本质上,(因为有地址空间的存在,所以上层申请空间,其实是在地址空间上申请的,物理内存甚至可以一个字节都不给你!!而当你真正进行对物理地址空间访问的时候,才执行内存的相关管理算法,帮你申请内存,构建页表映射关系),然后,再让你进行内存的访问。
上面这一段所说其实就是延迟分配的策略,这样可以提高整机的效率,几乎内存的有效使用是100%的!!!
括号内的操作是由操作系统自动完成的,用户包括进程完全是0感知的。这里的专属名词叫做缺页中断,当然大家肯定不知道这个的具体内容是什么,我们以后会讲到的,大家只需要知道这里的操作就可以了。
3.
在这里插入图片描述

五、重新理解什么是挂起

在这里插入图片描述

总结

这就是关于Linux中进程地址空间的全部内容了,这一节学习完成之后大家一定对地址空间有了更加深刻的了解,明白这些地址空间的知识后对于我们以后写代码也会有很大的帮助,大家可以清楚的了解到我们在写代码的时候定义的一些变量以及开辟的空间到底在哪里。如果打击有时间的话一定要把这一些实验自己做一下,可以加深自己的理解,那么本节的内容到这里就结束了,如果我写的内容对你有帮助的话,记得给波三连呦!!!

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熬夜学C++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值