5.2.1 POSIX Process Creation and Destruction
Keywords: perfbook_html

##
5.2.1 POSIX Process Creation and Destruction

Processes are created using the fork() primitive, they may be destroyed using the kill() primitive, they may destroy themselves using the exit() primitive. A process executing a fork() primitive is said to be the ``parent'' of the newly created process. A parent may wait on its children using the wait() primitive.

Please note that the examples in this section are quite simple. Real-world applications using these primitives might need to manipulate signals, file descriptors, shared memory segments, and any number of other resources. In addition, some applications need to take specific actions if a given child terminates, and might also need to be concerned with the reason that the child terminated. These concerns can of course add substantial complexity to the code. For more information, see any of a number of textbooks on the subject [Ste92].

Figure: Using the fork() Primitive

If fork() succeeds, it returns twice, once for the parent and again for the child. The value returned from fork() allows the caller to tell the difference, as shown in Figure (forkjoin.c). Line 1 executes the fork() primitive, and saves its return value in local variable pid. Line 2 checks to see if pid is zero, in which case, this is the child, which continues on to execute line 3. As noted earlier, the child may terminate via the exit() primitive. Otherwise, this is the parent, which checks for an error return from the fork() primitive on line 4, and prints an error and exits on lines 5-7 if so. Otherwise, the fork() has executed successfully, and the parent therefore executes line 9 with the variable pid containing the process ID of the child.

Figure: Using the wait() Primitive

The parent process may use the wait() primitive to wait for its children to complete. However, use of this primitive is a bit more complicated than its shell-script counterpart, as each invocation of wait() waits for but one child process. It is therefore customary to wrap wait() into a function similar to the waitall() function shown in Figure (api-pthread.h), this waitall() function having semantics similar to the shell-script wait command. Each pass through the loop spanning lines 6-15 waits on one child process. Line 7 invokes the wait() primitive, which blocks until a child process exits, and returns that child's process ID. If the process ID is instead -1, this indicates that the wait() primitive was unable to wait on a child. If so, line 9 checks for the ECHILD errno, which indicates that there are no more child processes, so that line 10 exits the loop. Otherwise, lines 11 and 12 print an error and exit.

Quick Quiz 5.4: Why does this wait() primitive need to be so complicated? Why not just make it work like the shell-script wait does? End Quick Quiz

Figure: Processes Created Via fork() Do Not Share Memory

It is critically important to note that the parent and child do not share memory. This is illustrated by the program shown in Figure (forkjoinvar.c), in which the child sets a global variable x to 1 on line 6, prints a message on line 7, and exits on line 8. The parent continues at line 14, where it waits on the child, and on line 15 finds that its copy of the variable x is still zero. The output is thus as follows:

    Child process set x=1
    Parent process sees x=0

Quick Quiz 5.5: Isn't there a lot more to fork() and wait() than discussed here? End Quick Quiz

The finest-grained parallelism requires shared memory, and this is covered in Section . That said, shared-memory parallelism can be significantly more complex than fork-join parallelism.