COP4610: Operating Systems & Concurrent Programming up ↑

Unix Process Management


Unix/Linux Views of Process Control

Conceptual View of Unix/Linux Process Control

Effect of fork(I) on buffered output

Shell View of Process Control (Bourne Shell)

Shell is a program that reads in commands and interprets them to create and manage processes.

Some commands are built into the shell. Others require the shell to fork off a child process to exec a program that does the command.

A shell command language can be quite general, allowing a person to write sophisticated programs that are executed directly by the shell.

An Example

An abbreviated version of simple_fork_shell_script:

echo the process number of this shell is $$
if [ $# -eq 0 ]; then 
   echo command has no parameters
   echo command has $# parameters: $@
if [  \( $# -eq 1 \) -a \( "$1" = "dofork" \) ]; then
   echo doing recursive call of this script, in background
   ./simple_fork_shell_script child&
   echo waiting for child $!
   wait $! 
elif [  \( $# -eq 1 \) -a \( "$1" = "child" \) ]; then
   echo child is about to sleep for one second
   sleep 1
   echo child is done sleeping
if [  \( $# -eq 1 \) -a \( "$1" = "comments" \) ]; then
cat - > tmpfile <<EOF
# Try this script with parameter "dofork", and then again
# with parameter "comments".
# Does the script work equally well on Linux and Solaris?
cat tmpfile; rm tmpfile

New Shell Features Used in the Example

The notation $# designates the number of command-line arguments (like arc in the API). The notation $1 designates the first argument. The notation $@ designates the entire list of arguments.

Normally, when the shell executes a program it waits for the program to complete before going on the the next line of shell command input. You can cause the shell to go on by putting the command into the "background". The character '&'at the end of the command "./simple_fork_shell_script child&" puts the child process into the background. You can also do this interactively, by interrupting the shell program using the Ctl-Z combination at the keyboard, and then executing the shell command "bg".

The notation $! gives the process id of the last-executed command. It is used here in "wait $!" to wait for the backgrounded child process.

The program cat just copies from its standard input file to its standard output file.

The notation "cat - > tmpfile <<EOF" is used to tell the shell to run the program cat and pipe to standard input of cat everything in the shell input stream up to the next line containing only the delimiter, "EOF". The net effect is to create a file named tmpfile containing the portion of the shell shell script between this command and the delimiter line.

Make certain you understand everything in this example. When you have finished, you should look at another example, test1_sh. This shell script was written by the instructor of a course for testing student submissions of a program simple_fork.c which were submitted using the shell-script submit1_sh.

API View of Process Control

The first four examples were already covered, though not in exactly the same form, in previous class meetings. In particular, the example fork_wait.c was covered in the Friday recitation class. I plan to review these examples, briefly, in class. We will then look at the example fork_exec.c in detail.

The latter example introduces the execve() system call.

The execve() Operation

if (execve ("child", argument_list, environ) == -1) {
   perror ("execve failed");
fprintf (stderr, "execution should never reach here");

The execve() function is the most general of several different forms of exec call. It allows the caller to specify the file to be executed, the arguments to be passed, and the environment variable values to be passed. All of the exec operations cause the current process (i.e., the one that requests exec) to load and start executing a new program, from the beginning. If the operation succeeeds, there is no return from the call, since the old program is replaced by a new one. The operation can fail if the new program file cannot be found, or if the caller does not have permission to execute it.

The fork() and exec() operations are used together to create a new process that executes a new program. This is at the heart of the implementation of a shell program. For example, consider what happens when a user enters a command "ls -a". The shell forks off a child, and the child execs the program named "ls" with the argument list {"ls", "-a", NULL}.

The waitpid() Operation

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);


waitpid (child, &status, 0);

Macros for use with waitpid() Status

These macros have two forms: which I'll call WIFxxxxx and Wyyyyyyy, and they occur in pairs. The WIFxxxxx macro returns the C equivalent of a Boolean value, suitable for use in an if-statement. (Hence the "IF" in the name.) You use it to determine whether the status is of the xxxxx kind. If the value is true, you use the paired Wyyyyyy macro to extract the appropriate component of the status.

Specifically, the value of WEXITSTATUS(x) is valid if and only iff WIFEXITED(x) returns a nonzero value. If it is valid, the value returned by WEXITSTATUS(x) is whatever value the main program returned with a return statement, or whatever value was returned via the terminating call to exit(). For example, if x is the status returned by waitpid() for a process that terminated by calling exit (-2), WIFEXITED(x) should return a true value, and WEXITSTATUS(x) should return -2. If x is the status returned by waitpid() for a process that terminates due to some other reason than a "normal exit" (meaning return from the main function or a call to exit or _exit), such as a segmentation violation, WIFEXITED(x) should be false.

Linux Internal View of Process Control (kernel version 2.4.7)

You should look through these files after reading about the process control block and dispatcher in Chapter 3 of the Stallings' textbook. You should be able to recognize or guess the meaning of some of the names. For example, find the following items listed in the slide below in task_struct.

You can click on each item to find the declaration highlighted in red, but please do this only after you have first tried looking through the unmarked file to find the answer.

In some of these cases, the information is scattered over more than one component. For example, the process state is more than just the one component highlighted.

Process Control Block Components in Linux

In the declaration of struct task_struct

By browsing this code you will see that writing a real operating system requires attention to quite a few more details than are mentioned in operating systems texts, and get a feeling for the real level of complexity. However, don't be overwhelmed. After a few months of study, any computer science student -- including you -- can learn to read and navigate this code.

T. P. Baker. ($Id)