前言:
在之前学编程的时候,我们就知道了数据的几个存储位置,大概是这样的一个图:
然后我们运行程序:
我们可以看到内存分布确实是按照左边的表来分配的
-----------
感知地址空间
现在,我们写了这样一段代码:
很简单的代码,创建一个子进程,然后打印pid\ppid,同时再打印父进程的pid\ppid
看看效果:
这些都没问题,但是刚刚代码里面我又多定义了一个变量,假设现在我要看看这个变量,我就直接打印嘛,如下图:
运行:
还是没问题嘛,那你演示这些有什么用吗?
现在val的值是100,没变,父子进程读到的值都是一样的,地址也是一样的,但是如果在进程里面修改了值呢?
再看:
运行:
这就是一个问题了,我们学过编程,知道一个地址只会有一个确定的值,但是上面父子进程打印的val怎么不一样呢?
很明显是地址的问题,C/C++里面,我们使用的地址,绝对不是物理地址,就是真实地址,如果是真实地址,这种一个地址两个值是不可能出现的。
那这是什么?
可以理解为,虚拟地址。
-----------
那为什么操作系统不让直接看到物理地址呢?
因为内存其本身是一个硬件,只能被动的写入读取,这里我举个例子:
这里有一块地,你跟邻居说好一人分一半,然后你在地上种玉米什么的植物,你跟邻居划分的很明确,不会出现什么我种你那边去了之类的情况,但是有一天,又来一个人,他也要种地,然后你跟邻居一商量,行,分了一块地给他。
但是这个人不会种,也不知道什么界限,他就在地上随便种,你去看自己的玉米的时候发现玉米已经被拔掉了,邻居肯定不会做这种事,但是那个新来的会不会做呢?
系统也是如此,早期的系统分配空间是直接分配的,但是这就很依靠程序员自身掌控能力,如果程序员写错了一个地方,修改了一块本不该被修改的空间,那就会出现错误。
为了解决这种问题,就有人想了个办法,这样,你要空间,可以,我给你开,但是这块空间的真实地址跟你看到的地址可能会有偏差,可以看下图:
这么看地址空间可能有点抽象,那我们是这样子呢:
每一个虚拟地址都映射了一个在内存中真实存在的物理地址,而这个,我们叫做页表 。
系统先将程序加载到内存,然后将其变为进程,每一个进程,操作系统都会为其构建一个页表结构。
通过这个页表的映射,就可以通过虚拟地址找到真实的物理地址。
那为什么这里的val会有两个值?
不知道你还记不记得,进程的几大特效里面,有一条叫做:独立性
这个独立性是怎么确保的呢?
页表,通过这个,虚拟地址映射的物理地址不一样,这里可以看下面的图。
原本不修改时,他们指向的同一块空间:
但是如果修改了,那就要出写时拷贝,怎么理解?
就是将修改的那个值重新分配一块空间,然后将原本指向val的指针指向新的空间:
所以这里就回答一下fork的两个返回值,pid_t id同一个变量为什么会有两个值。
pid_t本质是父进程定义的变量,fork内部,return会被执行两次,return的本质是将值写到接收返回值的变量中。
所以当id = fork()的时候,谁先返回,谁就要发生写时拷贝,就跟上面一样,这就说明了为什么地址是一样的,但是值不一样。
因为其本质是一个虚拟地址,真实对应的物理地址是不一样的。
------
为什么要有虚拟地址空间,就有一下几个原因:
1、保护了内存,就好像上面说的,当你的程序出现了错误,非法访问之类的,它就可以审核之后将其拦截。
2、进程管理,你的进程运行到哪一部分,我加载哪一部分,就不会出现你占了一大块内存,但是又不用的情况。
3、当程序运行起来,变成进程之后,操作系统是随机分配的一块空间,这样就会出现程序运行的地址是不确定的情况,如果用了页表,进行统一管理,就可以直接找到对应的地址来编译加载所有程序,简化了进程的设计和实现。
--------
感谢观看,希望对你有所帮助