现在的位置: 首页 > 综合 > 正文

子进程复制了父进程的什么

2017年06月15日 ⁄ 综合 ⁄ 共 1386字 ⁄ 字号 评论关闭

每次看到多进程时,子进程复制了父进程的所有数据(代码段、数据段、BSS、堆、栈),我们想当然的认为:子进程开辟了一块新的空间,把父进程的所有数据都复制过来。

而且每次我们改变名字相同的变量的值,输出的变量的结果都是不一样的。这也就更加坚定了我们的信心。但是,事实真的是这样吗?

运行下面这段代码,你会发现父子进程相同的变量输出的值不一样,但是地址却是一样的,看下面代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

main(){
    char str[4]="asd";
    pid_t pid=fork();
    if(pid==0){
        str[0]='b';
        printf("子进程中str=%s\n",str);
        printf("子进程中str指向的首地址:%x\n",(unsigned int)str);
    }
    else{
        sleep(1);
        printf("父进程中str=%s\n",str);
        printf("父进程中str指向的首地址:%x\n",(unsigned int)str);
    }
}

输出:

子进程中str=bsd
子进程中str指向的首地址:bfc224dc
父进程中str=asd
父进程中str指向的首地址:bfc224dc

额,看起来貌似很令人费解,为什么同一段地址却存储不同的值?

事实是这样的:

      计算机内部有物理地址和逻辑地址之分。

      物理地址:由计算机内存管理单元分配管理,只有内核才知道

      逻辑地址:又名虚拟地址,是由CPU产生的。而我们通常使用程序打印出来的地址都属于虚拟地址,比如

   printf("address: %x\n",&temp);

      从逻辑地址到物理地址存在一个映射,被称为地址重定向.

       每一个进程产生都有自己的虚拟地址空间,映射到不同的物理空间。多进程时,子进程复制了父进程的虚拟地址,虽然虚拟地址相同,但是映射的物理空间却是不一样的。比如,我现在同时运行IE和QQ,他们都有可能访问自己的0x804a0d4这个虚拟地址,但是映射之后的物理内存就不会是同一个地址。所以,相同的指针不代表物理内存中的同一块区域。在用户看来,同一变量,拥有同样的地址,却存储不同的值,很怪异,但是却是在正常不过的事儿了!

当然,父子进程间的复制并不是我上述所说的那么简单(开辟一个空间,把全部数据复制过去),因为这样的话,如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?这肯定不合理,多进程的目的就是为了高效的处理事件,减轻系统开销。

查阅资料之后,发现存在一个很著名的概念"Copy On Write(写时复制)",父子进程间的复制就是依赖于这个概念,可以这样简单的理解:

       一般CPU都是以“页”为单位分配空间的,象Intel的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行fork()时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。

抱歉!评论已关闭.