next up previous contents Back to Operating Systems Home Page
Next: Process Description and Control Up: Process Description and Control Previous: Threads

Process creation in UNIX

The seven-state logical process model we considered in a previous lecture can accommodate the UNIX process model with some modifications, actually becoming a ten state model.

First, as we previously observed, UNIX executes most kernel services within a process's context, by implementing a mechanism which separates between the two possible modes of execution of a process. Hence our previously unique ``Running'' state must actually be split in a ``User Running'' state and a ``Kernel Running'' state. Moreover a process preemption mechanism is usually implemented in the UNIX scheduler to enforce priority. This allows a process returning from a system call (hence after having run in kernel mode) to be immediately blocked and put in the ready processes queue instead of returning to user mode running, leaving the CPU to another process. So it's worth considering a ``Preempted'' state as a special case of ``Blocked''. Moreover, among exited processes there's a distinction between those which have a parent process that waits for their completion (possibly to clean after them), and those which upon termination have an active parent that might decide to wait for them sometime in the future (and then be immediately notified of its children's termination)gif. These last processes are called ``Zombie'', while the others are ``Exited''. The difference is that the system needs to maintain an entry in the process table for a zombie, since its parent might reference it in the future, while the entry for an exited (and waited for) process can be discarded without further fiddling. So the much talked about ``Zombie'' processes of UNIX are nothing but entries in a system table, the system having already disposed of all the rest of their image. This process model is depicted in fig. 5.

  
Figure 5: UNIX process state model

All processes in UNIXgif are created using the fork() system call. System calls in UNIX can be best thought of as C functions provided with the standard C library. Even if their particular implementation is depends on the particular UNIX flavor (and on hardware, for many of them), a C APIgif is always provided, and is consistent among the different unices, at least in the fundamental traits.

UNIX implements through the fork() and exec() system calls an elegant two-step mechanism for process creation and execution. fork() is used to create the image of a process using the one of an existing one, and exec is used to execute a program by overwriting that image with the program's one. This separation allows to perform some interesting housekeeping actions in between, as we'll see in the following lectures.

A call to fork() of the form:

#include <sys/types.h>

pid_t childpid;
...
childpid = fork(); /* child's pid in the parent, 0 in the child */
...
creates (if it succeeds) a new process, which a child of the caller's, and is an exact copy of of the (parent) caller itself. By exact copy we mean that it's image is a physical bitwise copy of the parent's (in principle, they do not share the image in memory: though there can be exceptions to this rule, we can always thing of the two images as being stored in two separate and protected address spaces in memory, hence a manipulation of the parent's variables won't affect the child's copies, and vice versa). The only visible differences are in the PCB, and the most relevant (for now) of them are the following:

The fork() call returns in both the parent and the child, and both resume their execution from the statement immediately following the call. One usually wants that parent and child behave differently, and the way to distinguish between them in the program's source code is to test the value returned by fork(). This value is 0 in the child, and the child's pid in the parent. Since fork() returns -1 in case the child spawning fails, a catch-all C code fragment to separate behaviours may look like the following:

#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
...
pid_t childpid;
...
childpid=fork();
switch(childpid)
{
   case -1:
      fprintf(stderr,"ERROR: %s\n", sys_errlist[errno]);
      exit(1);
      break;
   case 0:
      /* Child's code goes here */
      break;
   default:
      /* Parent's code goes here */
      break;
}

The array of strings char *sys_errlist[] and the global integer variable int errno are defined in the errno.h header. The former contains a list of system error messages, and the latter is set to index the appropriate message whenever an error occurs. For each system call several possible error conditions are defined. Each of them is associated to an integer constant - defined via a #define directive in one system header file - whose value is exactly the one that errno takes when an error occurs. A sample definition (from the sys/errno.h header) is:

...
#define ENOMEM 12
...
which defines the error that might occur when a process creation fails because there's not enough memory availablegif.

Note that a child (i.e. a process whatsoever, since they are all children of some other process, with the exception of processes 0, swapper and 1, init) cannot use the value returned by fork() to know its pid, since this is always 0 in the child. A system call named getpid() is provided for this purpose, and another one, named getppid() is used to ask the system about the parent's id. Both functions take no arguments and return the requested value in pid_t type, or -1 in case of failure.

In the above program fragment, a system call to exit() is made in case of failure, which causes the program to abort (you might want to deal with the errors in a smoother way, depending on your application, and perform some application-dependent error recovery action). We'll see later that the exit() call returns the lower 8 bits of its argument (1, in the above example) to a waiting parent process, which can use them to determine the child's exit status and behave accordingly. The usual convention is to exit with 0 on correct termination, and with a meaningful (for the parent) error code on abort.

It is often the case that a parent process must coordinate its actions with those of its children, maybe exchanging with them various kind of messages. UNIX defines several sophisticated inter-process communication (IPC) mechanisms, the simplest of which is a parent's ability to test the termination status of its children. A synchronization mechanism is provided via the wait() system call, that allows a parent to sleep until one of its children exits, and then get its exit status. This call actually comes in three flavors, gif one simply called wait() and common to all version of UNIX (that i know of), one called waitpid(), which is a POSIX extension, and one called wait3(), and it's a BSD extension.

Here's an example call to wait(): a program spawns two children, then waits for their completion and behaves differently according to which one is finished. Try to compile and execute it (no need to type: you can cut and paste from your web browser...).

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    pid_t whichone, first, second;
    int howmany;
    int status;
    
    if ((first=fork())==0) /* Parent spawns 1st child */
    {
        printf("Hiya, I am the first child, "
               "and my id is %d\n", 
               getpid()
               );
        sleep(10); /* Sleep 10 sec, then exit */
        exit(0);   
    }
    else if (first == -1)
    {
        perror("1st fork: something went bananas\n");
        exit(1);
    }
    else if ((second=fork())==0) /* Parent spawns 2nd  child */
    {
        printf("Hiya, I am the second child, "
               "and my id is %d\n", 
               getpid()
               );
        sleep(15); /* Sleep 15 sec, then exit */
        exit(0);   
    }
    else if (second == -1)
    {
        perror("2nd fork: something went bananas\n");
        exit(1);
    }
           
    printf("This is the parent\n");
    
    howmany=0; 
    while (howmany < 2) /* Wait twice */
    {
        whichone=wait(&status);
        howmany++;
        
        if (whichone==first)
           printf("First child exited ");
        else
           printf("Second child exited ");
    
        if ((status & 0xffff)==0)
           printf("correctly\n");
        else
           printf("uncorrectly\n");
    }

    return 0;
}

The first part of this example, up to the howmany=0 statement, contains nothing new: just make sure you understand what the instruction flow is in the parent and in the children. The parent then enters a loop waiting for the children's completion. The wait() system call blocks the caller process until one of its immediate children (not children's children, or other siblings) terminates, and then returns the pid of the terminated process. The argument to wait() is the address on an integer variable or the NULL pointer. If it's not NULL, the system writes 16 bits of status information about the terminated child in the low-order 16 bits of that variable. Among these 16 bits, the higher 8 bits contain the lower 8 bits of the argument the child passed to exit()gif, while the lower 8 bits are all zero if the process exited correctly, and contain error information if not (see the wait(2) man page for details). Hence, if a child exits with 0 all those 16 bits are zero. To reveal if this is actually the case we test the bitwise AND expression (status & 0xffff), which evaluates as an integer whose lower 16 bits are those of status, and the others are zero. If it evaluates to zero, everything went fine, otherwise some trouble occurred. Try changing the argument passed to exit() in one of the children.

The Posix and BSD extensions to wait() are useful when a parent must not block waiting for children, but still wants to know about the children's termination status values via the wait mechanism. We'll treat only the Posix waitpid() call, and you are referred to the man page for the BSD call.

The waitpid() call is declared as follows in the sys/wait.h header:

pid_t waitpid(pid_t pid, int *statptr, int options);

Here the meaning of the the return value and of the pointer to the status statptr is exactly the same in wait(). However this call allows to specify which children should be waited for and how. Specifically, the first argument pid specifies the process(es) that must be waited for. The relevant (for now) cases are:

The relevant (for now) value for the third argument is a constant called WNOHANG, that causes the function not to suspend the caller's execution if status is not immediately available for one of the child processes. This allows to implement a loop in which the parent can do something useful and periodically poll the children's status as well.

Note that the use of a loop also allows to use wait() and yet wait for one particular child: try to figure out yourself how.


next up previous contents Back to Operating Systems Home Page
Next: Process Description and Control Up: Process Description and Control Previous: Threads

Franco Callari