目录
一. 前言:
二.进程地址空间
1.通过一个例子去初步的了解进程地址空间:
使用VS写了一段代码:
在Linux中使用vim编辑器写类似的代码:
结果解析:
2.什么是进程地址空间?
举个例子大家就明白了画饼的意义:
如何画大饼?
3.详谈进程地址空间:
编辑
进程地址空间的结构体源码:
一. 前言:
当我们在学习C语言/C++的过程中,想必经常会在书中见过上面这副图吧,我想问问,大家眼中的这幅图代表着什么?
其实这是个地址空间,从低到高,从下而上,有各个区域,例如:代码区、堆区、栈区、静态区等,有的同学会说:“这不就是计算机中的内存(物理)地址空间吗?我们写的代码数据被运行后都会加载进内存”。其实这块地址空间并不是内存,而是进程的虚拟地址空间,是每个进程专属拥有的空间!
二.进程地址空间
1.通过一个例子去初步的了解进程地址空间:
使用VS写了一段代码:
在该程序中,在局部中修改全局变量的值,并打印修改前后的变化。
在Linux中使用vim编辑器写类似的代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int gloval=100; //全局变量
int main(){
pid_t id=fork(); //创建子进程
if(id<0){
printf("create process perror!");
return -1;
}
//子进程执行流代码:
else if(id==0){
int cnt=0;
while(1){
printf("我是子进程,pid:%d ppid:%d,gloval:%d,gloval地址:%p\n",
getpid(),getppid(), gloval, &gloval);
cnt++;
if(cnt==10) gloval=300;
}
}
//父进程执行流代码
else{
while(1){
printf("我是父进程 , pid: %d , ppid: %d , gloval: %d , gloval地址:
%p\n",getpid(),getppid(),gloval,&gloval);
sleep(2);
}
return 0;
}
运行结果:
结果解析:
1. 我们从代码中得知父进程的gloval为100,地址0x60105c,子进程的gloval和地址与父进程的一样,没问题,因为gloval是全局变量
2.因为代码中用了fork函数(创建子进程,表示当前该程序中有两个执行流),父子进程各自执行各自的while循环代码,但是从绿框那部分的显示结果中可以发现:子进程在循环中对全局变量gloval进行修改,变为了300没问题,但是纵观父进程的执行结果来看,gloval却还是100,gloval既然是全局变量,那为什么父子进程在读取同一个地址/变量的过程中,双方怎么会出现不同的结果呢?
按理说,gloval作为全局变量,子进程对其进行修改,那么父进程读取的就应该是gloval被修改过之后的值了(注gloval的地址从始至终没有变过,所以肯定不是变量的问题) 。
那么根据这个结果,我们可以得出一个结论:
之前我们在C/C++学习的过程中,从屏幕中打印出来的地址,绝对不是对应的物理地址,而叫做虚拟地址,那么上面的那个图所表示的也根本不是内存地址空间,而叫做虚拟地址空间。
知识解析:任何我们学过的语言,C/C++/Java/Python等,从代码中获取变量/指针/函数地址都绝对不会是物理地址。虚拟地址是由操作系统提供的,既然是虚拟地址,那么一定有某种途径会将虚拟地址转化为物理地址,因为程序在被启动时数据和代码必须被加载到物理内存中,那么在内存中,代码中的各种变量和函数也就有了自己的物理地址——这是冯诺依曼体系所规定的,所以在加载到内存之前,肯定是将虚拟地址转换为物理地址,这里的转化工作由操作系统完成。
2.什么是进程地址空间?
进程地址空间——又叫虚拟地址空间,就是从进程的视角去看地址空间,是进程运行时就用到的代码数据虚拟地址的集合。操作系统会给每个进程都创建一个独立的虚拟地址空间,而虚拟地址空间的大小与内存空间的大小相同(一般为4GB)。那为什么虚拟地址空间会和内存一样大呢?操作系统会给进程画大饼,让进程以为它可以使用整个内存的资源空间。
每个进程总是认为自己是独占内存资源的,但其实只是操作系统画给进程的大饼,让进程相信自己是独占内存资源的,这样可以提升进程的使用效率。
举个例子大家就明白了画饼的意义:
一个外国的富豪拥有十个亿的身价,这个富豪没有结婚已经年迈老矣,但在外面有3个私生子,其中有两个儿子被父亲安排到他手底下的公司去锻炼,大儿子是麾下某个工厂的老板,二儿子是金融公司的总经理,三儿子年龄比较小在大学读书。这三个私生子彼此并不知道其他两个的存在,富豪对自己的大儿子子勉励说:“你要积极上进,好好努力,为自己的未来做出业绩,等老爸以后走了,我的十亿遗产就全是你的了!” ;对二儿子也勉励说:“你要多向公司有经验的前辈汲取经验,把握好每一个项目的分寸,冷静思考,等老爸以后走了,公司就靠你了,我这几十年所获得的一切也就是你的了!”;对三儿子也勉励说:“你在学校好好读书,努力往上考到更高的地方,未来成为一名有名望的学者,这样一来老爸也就能了无遗憾,将一切托付给你了!”。
所以从这三个私生子的视角来看,他们认为他们未来都能继承父亲的10亿家产,于是更加努力的为父亲手下的公司卖力,为公司赚更多的钱。
解析上面的结构图:大富翁的10亿财产就是操作系统,三个儿子就好比三个进程,这3个儿子问父亲要钱,就是进程占用内存的各种资源。 当大儿子问富翁要钱时,肯定不可能一下子问他爹要10亿,这是不可能给的并且也不现实,但问老爸要个10万美金还是可以的,老爸给了;同理二三儿子也是一样。某个进程申请要资源,操作系统也只是一点一点的给,因为还要满足其它进程的需求。
如何画大饼?
画大饼,就是给你描绘一个美好的未来,给你描绘一幅蓝图,比如上面的故事中,富豪在画的大饼里应该包含着:饼是给谁的? 什么时候给?在什么条件下能给? 打算给多少钱?”所以把这些属性汇总起来就像是给每个私生子描绘的一幅蓝图,而这副蓝图这块大饼对应到计算机数据结构的对象,就形成了一个完整的结构体。
并且他在画饼的同时也需要管理,富豪给3个私生子画饼,同时富豪也得对他自己画的饼进行管理吧:比如要记住哪个大饼对应的是哪个私生子,画的大饼是否需要随着私生子的成长而进行相对应的改变? 相对应的就是操作系统给每个进程提供的地址空间,每个进程地址空间里面的具体属性不相同,并且地址空间的属性能够动态改变。而操作系统对这些地址空间(大饼)也需要进行管理,那么就需要用到我之前讲到的操作系统的管理理念:先描述,再组织。
对管理对象先进行描述,所以在Linux中地址空间的本质是内核中的一个结构体,名叫: struct mm_struct{ }
3.详谈进程地址空间:
经过上面的讲解,我们已经明白了在上面这个地址空间中共有4GB大小(和内存大小一样),是2的32次方个字节大小,而每个字节都表示一个地址,也就是说共有2的32次方个地址。
这2的32次方个地址都是虚拟地址,且每个地址是由32位bit位组成,从0x0000....0000到0xFFFF....FFFF由低到高。
案例验证:
运行结果:
地址空间的组成涉及到区域的划分:
举个例子,小学某个班中一个男生和一个女生做同桌,两人共用一张100厘米长的桌子,两人为了公平起见划定了三八线,各占桌子的一半长度。但是这个男生有些胖,导致他总是超出划定的范围,女生很苦恼,总是强调该男生,但他总是超了范围。于是她又规定两人双方各占45厘米,中间有10厘米的缓冲地带,但是该男生还是占到女生的课桌,女生彻底生气了,规定男生到最后只有30厘米的位置可用。
从这个例子可知:桌子好比虚拟地址空间,男女生划的三八线就是在进行区域的划分
基于此,进程地址空间中那些堆区、栈区、数据区、代码区也都有各自的区域划分:
进程地址空间的结构体源码:
未完待续......,接下来请看我写的下一篇博客!