1.程序地址空间
先来就看这张图
这是一张程序地址分布的图,通过一段代码来证明地址空间的分布情况
编译结果:
可以看出的是,父子进程中对于同一个变量打印的地址是一样的,这是因为子进程以父进程为模板,因为都没有对数据进行修改,所以这里变量地址也是一样的。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int g_val = 10;
int main()
{
int ret = fork();
if (ret < 0){
// error
perror("fork error");
return 0;
}
else if (ret == 0){
// child
int count = 0;
while (1){
printf("修改数据前:\n");
printf("I am child,pid:%d %d:%p\n", getpid(), g_val, &g_val);
sleep(1);
count++;
if (count == 3){
g_val = 20;// 子进程对数据进行修改
printf("修改数据后:\n");
}
}
else{
// parent
while (1){
printf("I am parent:pid:%d %d:%p\n", getpid(), g_val, &g_val);
sleep(1);
}
}
return 0;
}
通过这段代码可以进一步看出父子进程共用一段代码,子进程是父进程的副本,获得了父进程数据空间、堆和栈的副本;父子进程并不共享这些存储空间,共享正文段(即代码段);因此子进程对变量的所做的改变并不会影响父进程。 当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同).事实上,在Linux地址下,这种地址叫做虚拟地址(下面讨论)。且平时我们在C/C++语言打印所看到的地址都是虚拟地址,物理地址对于我们用户是看不到的,由OS管理.
2.进程地址空间
父子进程各种有一份虚拟空间地址,在子进程刚被创建时,父子进程代码和数据共享,所以此时虚拟地址空间的内容是基本一样的(当然有部分数据不同,比如各子的id等),且映射关系也是一样的,但是当子进程对数据进行修改时,子进程对那份数据进行写时拷贝,所以物理空间地址发生了变化,但是虚拟地址还是没有发生变化,只是改变了子进程的页表中那份虚拟地址的映射关系而已,所以两个相同的虚拟地址在父子进程分别看到了不同的物理地址空间。