如何控制fork()的创建顺序?

How to control the creation order of fork()?

问题是使用 C 的 fork() 在 Linux 上按以下字母顺序创建进程树:

A: B, C, D
-B: E, F
-C: G
--G: I
-D:

Required tree of processes

目前,通过使用 if,我可以通过观察 htop 中的 PID 来看到 abcdeGFi,而不是正确的顺序。

Results observed using htop

看到 C 的 PID(当前)总是 B 的 PID + 1,所以我尝试通过在分叉 B 之前停止 C 并在之后继续 C 来打补丁:

    int b = getpid();
    kill(b + 1, SIGSTOP);
    fork(); /* E created */
    if (getpid() == b) {
        fork(); /* F created */
    }
    kill(b + 1, SIGCONT);

这给出了正确的顺序,但是,如果 C 不在 B 旁边,它很难看并且容易出错,有没有一种完美的方法可以按该顺序创建进程?

如果我没理解错的话

  • 您想阻止 B 创建 EF 直到 DA.
  • 创建
  • 您想阻止 C 创建 G 直到 FB.

也就是说

  • B 必须等待来自 DA 的消息(表示 D 是在创建 EF.
  • 之前创建的)
  • C 必须等待来自 FB 的消息(表示 F 已创建)在创建 G.
  • 之前

消息这个词在这里使用得非常松散。我的意思是某种形式的信息传输。

在这里可以有效地使用管道。假设R(接收方)需要等待S(发送方)被创建。

  • S关闭管道的写入端。
  • R 在继续之前等待 EOF。

R会在两种情况下进行:

  • S 已创建。
  • S 将永远不会被创建(因为崩溃或其他原因)。

因此,这种方法是 "crash-proof",这意味着如果出现问题,您不会让进程永远等待。如果您愿意,您甚至可以通过让 S 在关闭管道之前发送一个字节来区分情况。完美。

这只是在正确的时间关闭管道句柄的问题。以下是您实现目标的演示。 (它是用 Perl 编写的,但是 pipeforkwaitpidsleepclose 只是同名 C 函数的简单包装。只是忽略 $.)

#!/usr/bin/perl

use strict;
use warnings;
use feature qw( say );

sub fork_child {
   my $sub = shift;

   my $pid = fork();
   if (!$pid) {
      if (!eval { $sub->(@_); 1 }) {
         warn( eval { "$@" } // "Unknown error" );
         exit(($? >> 8) || $! || 255);
      }

      exit(0);
   }

   return $pid;
}

sub a {
   [=10=] = "a";
   say "[=10=] is pid $$";

   pipe(my $d_created_recver, my $d_created_sender);
   pipe(my $f_created_recver, my $f_created_sender);

   my $pid_b = fork_child(\&b, $d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender);
   my $pid_c = fork_child(\&c, $d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender);
   my $pid_d = fork_child(\&d, $d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender);

   close($_) for $d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender;

   waitpid($pid_b, 0);
   waitpid($pid_c, 0);
   waitpid($pid_d, 0);
}

sub b {
   my ($d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender) = @_;

   [=10=] = "b";
   say "[=10=] is pid $$";

   # Not related to B or its descendants.
   close($_) for $d_created_sender, $f_created_recver;

   # Wait for D to be created.
   read($d_created_recver, my $buf, 1);
   close($d_created_recver);

   my $pid_e = fork_child(\&e, $f_created_sender);
   my $pid_f = fork_child(\&f, $f_created_sender);

   # Allow G to be created.
   close($f_created_sender);

   waitpid($pid_e, 0);
   waitpid($pid_f, 0);
}

sub c {
   my ($d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender) = @_;

   [=10=] = "c";
   say "[=10=] is pid $$";

   # Not related to C or its descendants.
   close($_) for $d_created_sender, $d_created_recver, $f_created_sender;

   # Wait for F to be created.
   read($f_created_recver, my $buf, 1);
   close($f_created_recver);

   my $pid_g = fork_child(\&g);

   waitpid($pid_g, 0);
}

sub d {
   my ($d_created_recver, $d_created_sender, $f_created_recver, $f_created_sender) = @_;

   [=10=] = "d";
   say "[=10=] is pid $$";

   # Not related to D or its descendants.
   close($_) for $d_created_recver, $f_created_sender, $f_created_recver;

   # Allow E to be created.
   close($d_created_sender);

   sleep();
}

sub e {
   my ($f_created_sender) = @_;

   [=10=] = "e";
   say "[=10=] is pid $$";

   # Not related to E process or its decendants.
   close($f_created_sender);

   sleep();
}

sub f {
   my ($f_created_sender) = @_;

   [=10=] = "f";
   say "[=10=] is pid $$";

   # Allow G to be created.
   close($f_created_sender);

   sleep();
}

sub g {
   [=10=] = "g";
   say "[=10=] is pid $$";

   my $pid_i = fork_child(\&i);

   waitpid($pid_i, 0);
}

sub i {
   [=10=] = "i";
   say "[=10=] is pid $$";

   sleep();
}

a();

输出:

赞成ikegami对所需持有点和信息传递的分析。这是一个在 C:

中使用信号量的示例(省略错误检查)
#include <unistd.h>
#include <sys/sem.h>

int main()
{   // Create set of 2 semaphores; Linux initializes the values to 0.
    int ss = semget(IPC_PRIVATE, 2, 0600);
    pid_t B, C, D, E, F, G, I;                      // the child processes
    if ((B = fork()) == 0)
    {   // wait for semaphore number 0 (D)
        semop(ss, &(struct sembuf){.sem_num=0, .sem_op=-1}, 1);
        if ((E = fork()) == 0) return sleep(9);     // E stay for a while
        if ((F = fork()) == 0)
        {   // C may now start G - unlock semaphore number 1
            semop(ss, &(struct sembuf){.sem_num=1, .sem_op=+1}, 1);
            return sleep(9);                        // F stay for a while
        }
        return sleep(9);                            // B stay for a while
    }
    if ((C = fork()) == 0)
    {   // wait for semaphore number 1 (F)
        semop(ss, &(struct sembuf){.sem_num=1, .sem_op=-1}, 1);
        if ((G = fork()) == 0)
        {
            if ((I = fork()) == 0) return sleep(9); // I stay for a while
            return sleep(9);                        // G stay for a while
        }
        return sleep(9);                            // C stay for a while
    }
    if ((D = fork()) == 0)
    {   // B may now start E, F - unlock semaphore number 0
        semop(ss, &(struct sembuf){.sem_num=0, .sem_op=+1}, 1);
        return sleep(9);                            // D stay for a while
    }
    sleep(9);                                       // A stay for a while
    semctl(ss, 0, IPC_RMID);                        // remove the semaphore set
}