【问题标题】:Confusion in the output of fork()fork() 的输出混乱
【发布时间】:2012-01-12 03:48:21
【问题描述】:

过去几天我一直在学习 fork() 函数,并且一直在做一些实验以了解它的实际工作原理。在这样做的过程中,我发现了一段我无法理解的有趣代码。代码如下:

int main(int argc, char *argv[])

{
int p,m;
    p = getppid();
    printf("%d\n",p);

if(fork() == 0) {
    p = getppid();
    printf("%d\n",p);
}

if(fork() == 0) {
    p = getppid();
    printf("%d\n",p);
}

if(fork() == 0) {
    p = getppid();
    printf("%d\n",p);
}

if(fork() == 0) {
    p = getppid();
    printf("%d\n",p);
}



return 0;
}

对此的输出是:

$./a.out
6117
6460
1
user@ubuntu:~/forkbomb$ 1
1
1
1
1
1
1
1
1
1
1
1
6473

如果您向我解释为什么 init 的 pid 为 1 会出现在输出中,那您会非常好。 如果有帮助,我想澄清一下我试图从给定的进程创建 5 个进程。那么你能告诉我正确的方法吗? 谢谢

【问题讨论】:

  • 对于初学者,您创建 5 个进程。我认为您正在创建 16 个进程。由于您没有在父节点中等待 产生子节点,因此子节点被 init 收养,因此 ppid 为 1。

标签: c operating-system posix fork system-calls


【解决方案1】:

父进程首先打印其父进程的 PID。然后它继续 fork 四个孩子 (C1..4),然后退出。

C1 打印它的父 PID,然后继续 fork 它自己的三个孩子。 C2 打印它的父 PID,然后继续 fork 自己的两个孩子。 C3 打印其父 PID ...

每个分叉的孩子在创建它的 if 块之后继续运行,因此会创建很多孩子。

当父进程退出时,子进程将重新设置为init,其进程ID 为1。确切的输出会因运行而异,具体取决于孩子被安排的确切时间/方式以及父母退出的时间。

如果您只想创建五个进程,请确保子进程完成后退出!

if(fork() == 0) {
    p = getppid();
    printf("%d\n",p);
    exit(0);
}

如果您希望父级在其退出之前等待其子级全部完成,您应该查看wait 系列函数。

【讨论】:

    【解决方案2】:

    如果子进程的父进程在子进程之前退出或死亡,则子进程被称为孤儿进程并被init 收养。这意味着子进程的 PPID(父 PID)变为 1。这解释了您的输出,因为它来自 getppid()

    为了解释显示的行数,让我们给代码行编号:

     1  int p,m;
     2      p = getppid();
     3      printf("%d\n",p);
     4  
     5  if(fork() == 0) {
     6      p = getppid();
     7      printf("%d\n",p);
     8  }
     9  
    10  if(fork() == 0) {
    11      p = getppid();
    12      printf("%d\n",p);
    13  }
    14  
    15  if(fork() == 0) {
    16      p = getppid();
    17      printf("%d\n",p);
    18  }
    19  
    20  if(fork() == 0) {
    21      p = getppid();
    22      printf("%d\n",p);
    23  }
    

    现在让我们计算执行每个printf() 的所有进程。第 3 行的printf() 显然只由原始父进程执行。第 7 行的 printf() 仅由原始父进程的第一个子进程执行,因为 fork() 在子进程中返回 0。现在,有两个进程到达第 9 行:原始父进程和它的第一个子进程。他们都 fork 和他们的两个孩子(即原始父母的第二个孩子和原始父母的第一个孩子的第一个孩子)在第 12 行执行printf()。第 14 行由 4 个进程(原始父母,它的两个孩子和原始父母的第一个孩子的第一个孩子)。它们都在第 15 行产生了一个子进程,所有四个子进程都在第 17 行执行printf()。多达 8 个进程到达第 19 行。每个进程都派生出最年轻一代中的 8 个子进程并执行最终的printf()在第 22 行。

    第一个 printf() 被执行 1 次。 第二个printf() 被执行1 次。 第三个 printf() 被执行了 2 次。 第四个printf() 被执行了4 次。 第五个printf() 被执行了8次。

    总共 16 个,与您的输出一致。显示的一些 PPID 等于 1,表示给定的父级执行得如此之快,以至于在到达给定的printf() 之前,它就被 init 收养了。其他大于 1 表示在子进程中达到printf() 时,给定进程的父进程仍在运行。程序的多次执行极有可能会导致输出有所不同。

    因此,您没有创建 4 个子进程,而是创建了 15 个。这是因为您的子进程从产生它们的 fork() 返回时继续执行。这意味着一些fork()s 不仅会由原始父进程执行,还会由其子进程执行,从而创建一系列新进程。如果您只想创建 4 个子级,则应确保剩余的分叉仅发生在父级中。

    【讨论】:

      【解决方案3】:

      您错过的是,当您分叉时,有 2 个进程具有相同的代码副本,从您分叉的点继续。

      父母得到孩子的pid返回,孩子得到0返回。 因此,您分叉了 16 个进程(4 个阶乘),因为在每个分叉处,您的进程数都增加了一倍。

      如果您在最后添加 sleep() 以确保父进程挂起足够长的时间,那么您将在子进程中获得实际的父 pid(只需在 return 之前添加 sleep(2))。

      我的输出是:

      > ./x
      19291
      21686
      21686
      21687
      21688
      21687
      21687
      21688
      21689
      21691
      21690
      21689
      21695
      21686
      21686
      21694
      

      【讨论】:

        【解决方案4】:

        首先,1 来自已经退出的父进程。然后父进程成为系统初始化进程1

        如果你也对1s 的数量感到恼火,你会得到: 初始过程分叉了 4 个孩子,第一个孩子分叉了 3 个,有两个孩子每个分叉 2,三个孩子分叉 1。还有四个不再分叉。这使得 1+1+4+3+2+1+4 = 总共 16 个进程。

        【讨论】:

          【解决方案5】:

          这个程序将在四个 fork() 部分中创建 1 + 2 + 4 + 8 = 15 个进程(如果我们加上原来的部分,则为 16 个)。也许你想做的是这样的:

          if(fork() == 0) {
              p = getppid();
              printf("%d\n",p);
          } else
              return 0;
          

          getppid() 如果其父级已经消失,将返回 1。由于我们不知道系统将如何安排这些进程,您可以看到这可能会在您的程序中发生。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-03-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多