目录
现象
来看代码:
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int val = 50;
int main()
{
printf("father process is running,pid is:%d,ppid is:%d,val:%d,&val:%p\n", getpid(), getppid(), val, &val);
pid_t id = fork();
if (id == 0)
{
int cnt = 0;
while (1)
{
printf("child process,val:%d,&val:%p\n", val, &val);
cnt++;
sleep(1);
if (cnt == 3)
{
val = 100;
printf("child proess change 50->100\n");
printf("child process,val:%d,&val:%p\n", val, &val);
sleep(1);
}
}
}
else if (id > 0)
{
while (1)
{
printf("father process,val:%d,&val:%p\n", val, &val);
sleep(1);
}
}
return 0;
}
如上述代码,定义一个全局变量val,分别在父子进程中打印内容和地址,当在子进程运行3s后,更改其值。来看看结果。
从上图不难看出,没修改前父子进程的val都是50,地址也一样;当在子进程修改后,子进程打印出来了100,而父进程还是50,但是地址还是一样的!!很奇怪!!?
- 同一变量名输出不同的内容,说明父子进程中用的并不是同一个变量。
- 每一个变量都会有属于自己的物理空间(地址)。。说明,使用程序代码打印出来的这个地址绝对不是物理地址!
- 实际上,在Linux中,对于这样的地址就被称为虚拟地址!平时我们采用C/C++打印出来的地址,都是虚拟地址,并不是数据真正在内存中的地址!
- OS在创建进程时,不仅仅将进程的代码和数据加载到内存,还会为每一个进程创建一个地址空间,这个地址空间就是虚拟地址空间!!
既然是虚拟地址,那又怎么找到当前数据所在内存的位置?只有知道位置,才能进行数据的访问和修改。因为内存和代码被加载到了物理内存中。。
实际上OS也会为每个进程建立一个页表,这个页表能够将虚拟地址和物理地址建立起映射的关系!操作系统会自动将虚拟地址转化为物理地址。。
底层原因
数据不发生修改
- 这种情况父子进程的页表映射的是同一个地址。因为子进程会继承父进程内容,父进程的代码和数据子进程都能看到(不能修改)
数据修改
- 这种情况会发生OS发生写时拷贝。OS会在内存重新找一块空间,将数据拷贝下来再做修改,并重新更新子进程的页表,重新建立新的映射关系。
为什么要发生写时拷贝?
因为进程具有独立性,多进程运行,都需要独享各种资源,进程之间不能相互干扰。
能不能在创建子进程的时候就将父进程的数据全部写时拷贝呢?可以,但是没必要,因为子进程不一定会全部修改父进程的数据,写时拷贝是有一定的消耗的,应该要按需分配,修改的时候在写时拷贝,这样更能节省消耗!
小总结
①上述程序运行的结果足矣说明,同一个变量,地址相同,本质是虚拟地址相同,内容不同本质是被映射到的物理地址不同!
②这也解释了fork函数,同一个id变量名为什么既能>0,又能小于0的原因。因为fork函数会返回两次,即return,return的本质就是写入(修改),发生写时拷贝!
cpp
//fork会return,对父进程的id写入,发生写时拷贝!
pid_t id=fork();
if(id==0)
{
//child
....
}
else if(id>0)
{
//father
.....
}
地址空间本质
进程地址空间,在内存中是一种内核数据结构,在Linux中本质就是一个mm_struct的结构体,这个结构体内部的属性都是表示某一个区域的范围。。
就好像一把直尺,每一部分的内容都有属于自己的范围!
为什么要有地址空间
①拦截非法请求,变相保护物理内存!
- 如果没有地址空间,进程直接访问物理内存,极大的增加系统异常的概率。
- 加上地址空间,进程首先得先通过地址空间这一关,比如访问数组越界等异常问题,地址空间会立马报错,阻止进程继续访问物理内存!
②进程管理模块和内存模块的解耦,实际是提高内存利用率。
- 如果在创建进程的时候,直接申请物理内存。若进程长时间不使用物理内存,那必然会造成资源的浪费
- 有了虚拟地址空间,可以在页表先申请出虚拟地址,当进程实际需要使用内存,再申请内存空间,并建立映射关系,极大提高内存利用率。
③将无序变有序,让进程以统一的视角看待物理内存以及自己运行的各个区域。
- 有了地址空间,每个进程都无需关心具体的数据在物理内存中的位置,只需知道虚拟地址,OS会通过页表映射到对应的物理位置!
好了,今天的内容就分享到这里,如果你有所收获,欢迎三连,xd!