Search code examples
clinuxprocessforkpid

How to control the creation order of fork()?


The problem is to create a tree of processes using C's fork() on Linux in this alphabetical order:

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

Required tree of processes

Currently, by using if, I could see abcdeGFi by observing PIDs in htop, not the correct order.

Results observed using htop

Seeing C's PID is (currently) always B's PID + 1, so I tried to patch by STOP-ing C before forking B and CONT-ing C afterwards:

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

This gives the right order, however, it is ugly and prone to error if C isn't next to B, is there a flawless way to create processes in that order?


Solution

  • If I understand correctly.

    • You want to prevent B from creating E and F until D is created by A.
    • You want to prevent C from creating G until F is created by B.

    That means

    • B must wait for a message from D or A (indicating D was created) before creating E and F.
    • C must wait for a message from F or B (indicating F was created) before creating G.

    The word message is used very loosely here. I mean some form of information transfer.

    A pipe could be used effectively here. Say that R (receiver) needs to wait for S (sender) to be created.

    • Have S close the write end of the pipe.
    • Have R wait for EOF before proceeding.

    There are two circumstances under which R will proceed:

    • S was created.
    • S will never be created (because of a crash or something).

    So, this approach is "crash-proof", meaning you won't have processes waiting forever if things go wrong. And if you so desired, you could even distinguish the circumstances by having S send a byte before closing the pipe. Perfect.

    It just becomes a question of closing the pipes handles at the right time. The following is a demonstration of your goal achieved. (It's written in Perl, but pipe, fork, waitpid, sleep and close are just thin wrappers for the C functions with the same name. Just ignore the $.)

    #!/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 {
       $0 = "a";
       say "$0 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) = @_;
    
       $0 = "b";
       say "$0 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) = @_;
    
       $0 = "c";
       say "$0 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) = @_;
    
       $0 = "d";
       say "$0 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) = @_;
    
       $0 = "e";
       say "$0 is pid $$";
    
       # Not related to E process or its decendants.
       close($f_created_sender);
    
       sleep();
    }
    
    sub f {
       my ($f_created_sender) = @_;
    
       $0 = "f";
       say "$0 is pid $$";
    
       # Allow G to be created.
       close($f_created_sender);
    
       sleep();
    }
    
    sub g {
       $0 = "g";
       say "$0 is pid $$";
    
       my $pid_i = fork_child(\&i);
    
       waitpid($pid_i, 0);
    }
    
    sub i {
       $0 = "i";
       say "$0 is pid $$";
    
       sleep();
    }
    
    a();
    

    Output:

    Output