关于连续使用fork()到底产生多少个子进程问题的实践

关于连续使用fork()到底产生多少个子进程问题的实践

原问题来自网上的提问,近来也有不少相关的IT公司笔试题要求在使用fork()多次的情况下写出输出某个字符串的次数。针对这个问题,不少网友有不同的看法。我对于这个问题的探究主要放在fork()到底产生多少个子进程这个问题上。

实验环境:64-Ubuntu/Linuxversion 3.13.0-37-generic/gcc version 4.8.2

以下代码用于在连续fork()两次的程序中跟踪当前进程ID(getpid())、当前进程的父进程ID(getppid())、当前进程中的变量child_process1与child_process2。

关于连续使用fork()到底产生多少个子进程问题的实践_第1张图片

   从运行结果来看,主父进程ID为3519、第一个子进程ID为3520、第二个子进程ID为3521,还有一个进程ID为3522的进程居然还输出了两行结果。

   从运行过程来看主父进程(3519)从头执行到尾,在“almostend”的时候进入等待(等待两个子进程结束)。接着是主父进程的第二个子进程(3521)开始执行(为什么不是第一个子进程先执行到输出语句,这个跟进程调度有关),然后第二个子进程一直执行到“end”结束。再下来就是第一个子进程(3520)执行,执行到“almost end”的时候停住了,蹦出来一个子进程(3522)执行了两次输出,接着这个进程结束。再下来就是第一个子进程跟主父进程依次结束。

关于连续使用fork()到底产生多少个子进程问题的实践_第2张图片

先看看这几个进程的关系怎么样,从输出的跟踪信息来看,主父进程(3519)fork出来了两个进程(3520、3521),而进程(3522)是从第一个子进程(3520)中产生的。

那么就有两个主要的问题:

1、3522为什么会从3520中产生,为什么3521不产生进程?

2、为什么进程3522会连着输出两个语句?

 

根据Linux的内存分配规则,进程的fork()使用的是共享映射的技术。Linux内核将父进程逻辑地址空间所对应的那一片内存原样地共享映射到子进程的逻辑地址空间里面。(可能有人会疑惑,子进程应该跟父进程是独立的两个进程,如果共享映射就会导致一个进程对于内存区域的访问(读写之类)会影响到另外一个进程,这跟数据独立、代码共享的父子进程关系解释有矛盾。没错,关于这一点的说明在文末)。既然是共享映射,那么PC程序计数器是父子进程共有的。这会影响刚fork()出来的子进程是从哪里开始执行程序,当然不是int main,而是PC程序计数器指向的地方!在fork()出第一个子进程之后,程序计数器是指向下一个fork()语句,而在fork()出第二个子进程的时候程序计数器指向的是if(child_process1==0)这个判断语句。总而言之,第一个子进程最开始执行的语句是原程序的第二个fork(),因此会产生额外的一个子进程(3522)。而第二个子进程最开始执行的语句是判断语句,并不会产生额外的子进程。

那么这个额外的进程为什么会产生两个输出?可以看到,是否产生输出取决于child_process1跟child_process2这两个变量。现看看各个进程的child_process1跟child_process2。

关于连续使用fork()到底产生多少个子进程问题的实践_第3张图片

    主父进程的各项数据很好理解。

    第一个子进程的child_process1是怎么确定的呢?在fork子进程的时候已经有规定,在子进程中的原父进程fork这个子进程的这个语句(即child_process1 = fork();)中的pid_t变量置为0(这应该是常识),而第一个子进程的child_process2则被fork出来的3522代替(这是确确实实执行了的语句)。

解释一下第二个子进程的child_process1,事实上,对于第二个子进程,child_process1=fork();是没有执行的,所以第二个子进程的child_process1是个从主父进程来的原始值。

而3522是3520 fork而来,同样是没有执行两个child_process的赋值语句,其中child_process2被置为0,child_process1则使用从3520来的原始值。从而会执行两句以child_process1、child_process2判断的输出语句。

这样一来,也揭示了在等待子进程的时候出现error的情况。

 

以上可以解释以下代码输出几个-的问题

关于连续使用fork()到底产生多少个子进程问题的实践_第4张图片


规律如下:

i = 0循环内(2个)

Parent Print

         Child1Print

i = 1循环内(4个)

Parent Print

         Child1 Print

                   Child1_1 Print

         Child2 Print

i = 3循环内(8个)

Parent Print

         Child1 Print

                   Child1_1 Print

                            Child1_1_1 Print

                   Child1_2 Print

         Child2 Print

                   Child2_1 Print

         Child3 Print

…每一层的进程数都fork了一次,所以下一层的进程数=上一层进程数*2,故总进程数2+4=6。

 

   Linux内核提供共享内存的时候,会将相应的内存区域设定为写保护。如果有某一方进程试图进行写入操作,就会抛出一个异常。内核检测到这个异常之后;就会立即复制写入对象的内存页,使两个进程都处于可独立写入的状态,这也被称为写时复制(Copy-on-Write)。这个机制在应用到fork以前,fork的实现是拷贝,因此诞生了代价比较小的vfork,后来使用了COW之后,fork不再是“重型函数”了。

你可能感兴趣的:(Linux相关的问题)