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)
. 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 UNIX
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 API
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 available
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,
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()
, 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:
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.