12

When using fork(), is it possible to ensure that the child process executes before the parent without using wait() in the parent?


This is related to a homework problem in the Process API chapter of Operating Systems: Three Easy Pieces, a free online operating systems book.

The problem says:

  1. Write another program using fork(). The child process should print "hello"; the parent process should print "goodbye". You should try to ensure that the child process always prints first; can you do this without calling wait() in the parent?

Here's my solution using wait():

#include <stdio.h>
#include <stdlib.h> // exit
#include <sys/wait.h> // wait
#include <unistd.h> // fork

int main(void) {
    int f = fork();
    if (f < 0) { // fork failed
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (f == 0) { // child
        printf("hello\n");
    } else { // parent
        wait(NULL);
        printf("goodbye\n");
    }
}

After thinking about it, I decided the answer to the last question was "no, you can't", but then a later question seems to imply that you can:

  1. Now write a program that uses wait() to wait for the child process to finish in the parent. What does wait() return? What happens if you use wait() in the child?

Am I interpreting the second question wrong? If not, how do you do what the first question asks? How can I make the child print first without using wait() in the parent?

5
  • 1
    Does calling waitpid(), or waitid() (or, on some systems, wait3() or wait4()) count as 'not calling wait()'? How inventive do you want to get? (You could open a pipe, close the write end, and execute a read on the read end; when the child exits, the write end of the pipe will be closed, so the parent will get EOF — 0 bytes read — and it knows that the child died, and all its children, etc. That's not completely foolproof, but it depends on the context; do you know what the child processes will be doing?) Commented Aug 30, 2017 at 5:33
  • There are a lot of process synchronization mechanisms. Another option (besides having the parent block on a read) is to have the child send the parent a signal after it has written the data (just calling printf is not enough to guarantee that the data is written). Or have the parent wait for some event in the filesystem which the child triggers. (eg, parent tries to open a fifo, which will block until the child opens the fifo, but this is fundamentally the same as blocking on a read) Commented Aug 30, 2017 at 5:47
  • 1
    I'm assuming doing nothing until SIGCHLD doesn't count, even though the siginfo_t has a lot of useful info. Commented Aug 30, 2017 at 5:56
  • Note also that, in general, the WNOWAIT flag can often be used when you want to preserve waitability. Commented Aug 30, 2017 at 5:58
  • You can use something like sleep() since you are doing this for a course. Commented Jan 14, 2023 at 4:45

6 Answers 6

10

Just a few minutes ago, I sent an email to Remiz, the author of this book, and received the following response:

Without calling wait() is hard, and not really the main point. What you did -- learning about signals on your own -- is a good sign, showing you will seek out deeper knowledge. Good for you!

Later, you'll be able to use a shared memory segment, and either condition variables or semaphores, to solve this problem.

Sign up to request clarification or add additional context in comments.

Comments

5

Create a pipe in the parent. After fork, close the write half in the parent and the read half in the child.

Then, poll for readability. Since the child never writes to it, it will wait until the child (and all grandchildren, unless you take special care) no longer exists, at which time poll will give a "read with hangup" response. (Alternatively, you could actually communicate over the pipe).

You should read about O_CLOEXEC. As a general rule, that flag should always be set unless you have a good reason to clear it.

5 Comments

could you explain in detail with example how to use poll() in this specific case?
@hoholee12 er, just the same as any other time? Have you read the man page?
With just the one pipe and no timeout, just reading from it would be simpler. (It would always return EOF, of course.)
@DavisHerring Using read directly either requires a busy loop (which should be avoided at all costs) if you're nonblocking, or blocking (in which case you might as well just use wait in real-world scenarios) and looping on transient errors. This problem does come up in the real world, especially if you don't have pidfd.
@o11c: It’s legitimate to block on what might be only part of the child’s operation; the technique also generalizes to waiting on (actions of) non-children, aside from answering the pedagogical exercise. I don’t know what transient errors something as simple as a pipe can produce.
3

I can't see why second question would imply that answer is "yes" to the first.

Yes there is plenty of solutions to obtain what asked, but of course I suspect that all are not in the "spirit" of the problem/question where the focus in on fork/wait primitives. The point is always to remember that you can't assume anything after a fork regarding the way processes ran relatively to each other.

To ensure the child process print first you need a kind of synchronization in between both processes, and there is a lot of system primitives that have a semantic of "communication" between processes (for example locks, semaphores, signals, etc). I doubt one of these is to be used her, as they are generally introduced slightly later in such a course.

Any other attempt only that will only rely on time assumption (like using sleep or loops to "slow" down the parent, etc) can lead to failure, means that you will not be able to prove that it will always succeed. Even if testing would probably show you that it seems correct, most of the runs you would try will not have the bad characteristics that lead to failure. Remember that except in realtime OSes, scheduling is almost an approximation of fair concurrency.

NOTE:

As Jonathan Leffler commented, I also suppose that using other wait-like primitives is forbidden (aka wait4, waitpid, etc) -- "spirit" argument.

4 Comments

My thought was that since it asks "Now write a program that uses wait() to wait for the child process to finish", it expected you to have written a program that didn't use wait() for question 3.
But I can see how it might not imply that, since the rest of the question is about the particular behavior of wait().
Exercises are not tightly linked like you think. Ex.5 doesn't suppose anything about provided solution in Ex.3. Ex.3 is about experiencing the waiting. Ex.5 is about values caught by a call to wait (questions are explicitly about this point).
I have found out what author wants to express by mailing him and post it in my answer : )
0

You can use signals, which basically are being sent from the OS to the process. There are different types of signals. One that I am using here is SIGCHLD, which is a signal sent to the parent when the child exits. Normally by default it just ignores it. With the help of signal handlers you can determine how to handle that signal, or basically what to do when that signal is being sent.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>

volatile int child_finished = 0;

void sigchild_handler(int sig) {
    child_finished = 1;
}

int main(int argc, char *argv[]) {
    int rc = fork(); // rc in short for return code
    // normally when a SIGCHLD is being sent, default is to ignore it
    // SIGCHLD is basically the signal that the child is finished executing
    signal(SIGCHLD, sigchild_handler);
    if (rc < 0) {
        fprintf(stderr, "fork failed\n");
        exit(1); // terminates the program from wherever you are
    } else if (rc == 0) {
        printf("hello\n");
        exit(0);
    } else {
        while (!child_finished) {};
        printf("goodbye\n");
    }
    return 0;
}

Comments

0

I'm using the idea of using signals that was proposed here earlier. A thing to note is that you will have to make the call to fork() after setting up the signal handler to avoid a race condition (otherwise, there is a chance the child terminates first before the parent sets up the signal handler, in which case the signal would be ignored).

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>

volatile int child_finished = 0;

void sigchild_handler(int sig) {
    child_finished = 1;
}

int main(int argc, char *argv[]) {
    // normally when a SIGCHLD is being sent, default is to ignore it
    // SIGCHLD is basically the signal that the child is finished executing
    signal(SIGCHLD, sigchild_handler);
    int rc = fork(); // rc in short for return code
    if (rc < 0) {
        fprintf(stderr, "fork failed\n");
        exit(1); // terminates the program from wherever you are
    } else if (rc == 0) {
        printf("hello\n");
        exit(0);
    } else {
        while (!child_finished) {};
        printf("goodbye\n");
    }
    return 0;
}

Comments

-1

You can use the sleep() function to sleep the parent process for some time, like in seconds, sleep(2). This will sleep or act like a wait for a number of seconds that you have passed as an argument in the sleep function.

Here is the code example:

   int main(int arg_counter, char *argv[]) {
    int child_process = fork();
    if(child_process < 0) {
        printf("Could not create child process");
        return 0;
    } else if(child_process == 0) {
        printf("Hello\n");
        return 0;
    } else {
        sleep(1);
        printf("Good Bye\n");
    }
    return 0;
}

Do not forget to include <signal.h> library first. 😊

1 Comment

Arbitrary sleep is usually a bad idea. You can't ever know whether you've waited long enough, particularly on a heavily loaded host.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.