fork函数详解与进程替换(exec)

<1>fork定义

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。

我们经常说fork后的子进程相当于是子进程的一个克隆,fork出来的父子进程并行fork之后的代码,但是子进程真的是完全复制了父进程吗?答案是不,那么到底子进程复制了父进程的那些东西?那些东西又没有复制呢?

<2>fork之后子进程到底复制了父进程什么?

我们先来看看这段代码


#include
#include
#include
#include

void main() 
{
    char str[6]="hello";
    pid_t pid=fork();

    if(pid==0)
    {

        str[0]='f';
        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);
    }
}

这个打印出来的结果是什么呢?我们一起来看看

fork函数详解与进程替换(exec)_第1张图片

可以看出,父子进程之间打印的数据并不相同,说明子进程复制了父进程栈区的空间。但是为什么地址又是一样的呢?实际上这个是逻辑地址(虚拟地址),既然是逻辑地址(虚拟地址),那么又有什么所谓呢?映射到物理内存是不一样滴。地址映射-将程序地址空间中使用的逻辑地址变换成内存中的物理地址的过程。由内存管理单元(MMU)来完成。

实际上fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用(下文会讲),出于效率考虑,linux中引入了“写时拷贝“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(因为两者的代码完全相同)。但如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。所以也就为什么不是直接在内存上给子进程也复制完完全全相同的区域呢,这就是原因。fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)相同没什么奇怪。

具体过程是这样的:
fork子进程完全复制父进程的虚拟地址空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,直到其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。(简洁来说就是:内核只为新生成的子进程创建虚拟空间,它们来复制于父进程的虚拟空间,但是不为这些段分配物理空间,它们共享父进程的物理空间,当父子进程中有写内存的行为发生时,再为子进程相应的段分配物理空间(复制更改的变量所在的一页),这就是写时拷贝技术

你可能感兴趣的:(fork函数详解与进程替换(exec))