Process Management
Process Management
One of the philosophies behind Unix is the motto do one thing and do it well. In this spirit, basic
process management is done with a number of system calls, each with a single (simple) purpose.
These system calls can then be combined to implement more complex behaviors.
The following system calls are used for basic process management.
fork
A parent process uses fork to create a new child process. The child process is a copy of
the parent. After fork, both parent and child executes the same program but in separate
processes.
exec
Replaces the program executed by a process. The child may use exec after a fork to
replace the process’ memory space with a new program executable making the child
execute a different program than the parent.
exit
Terminates the process with an exit status.
wait
The parent may use wait to suspend execution until a child terminates. Using wait the
parent can obtain the exit status of a terminated child.
Fork
The fork system call is the primary (and historically, only) method of process creation in Unix-
like operating systems.
#include <unistd.h>
pid_t fork(void);
Return value
On success, the PID of the child process is returned in the parent, and 0 is returned in the
child. On failure, -1 is returned in the parent, no child process is created, and errno is set
appropriately.
• If the return value is 0 the program executes in the new child process.
• If the return value is greater than zero, the program executes in the parent process and the
return value is the process ID (PID) of the created child process.
• On failure fork returns -1.
Template program
In the file module-2/examples/src/fork-template.c you find a template for a typical
program using fork.
Header files
On lines 1-3 a number of header files are included to get access to a few functions and constants
from the C Standard library.
pid_t
One line 7 the variable pid of type pid_t is declared. The pid_t data type is the data type used
for process IDs.
fork
On line 9 the parent process calls fork and stores the return value in the variable pid.
switch
On failure fork returns -1 and execution continues in the case -1 branch of the switch
statement (line 11). The operating system was not able to create a new process. The parent uses
perror to print an error message (line 13) and then terminates with exit status EXIT_FAILURE
(line 14).
Child (case 0)
On success fork returns 0 in the new child process and execution continues in the case 0
branch of the switch statement (line 16). Any code to be executed only by the child is placed
here (line 19). The child terminates with exit status EXIT_SUCCESS (line 21).
Parent (default)
If the value fork returned by fork was neither -1 (error) nor 0 (child), execution continues in the
parent process in the default branch of the switch statement (line 23). In this case, the value
returned by fork is the process ID (PID) of the newly created child process.
int main(void) {
pid_t pid;
The code for the child is in the function child and the code for the parent in the function
parent.
void child() {
printf(" CHILD <%ld> I'm alive! My PID is <%ld> and my parent got PID
<%ld>.\n",
(long) getpid(), (long) getpid(), (long) getppid());
printf(" CHILD <%ld> Goodbye!\n",
(long) getpid());
exit(EXIT_SUCCESS);
}
Both parent and child prints two messages and then terminates. Navigate to the directory
module-2/examples. Compile using make.
$ make
$ ./bin/fork
Run the program multiple times and look specifically at the PPID value reported by the child.
Sometimes the child reports PPID = 1 but sometimes it is equal to the PID of the parent. Clearly
the PID of the parent is not 1? Why doesn’t report the “correct” PPID value all the time?
Orphans
An orphan process is a process whose parent process has terminated, though it remains running
itself. Any orphaned process will be immediately adopted by the special init system process with
PID 1.
• most often the parent terminates before the child and the child becomes an orphan
process adopted by init (PID = 1) and therefore reports PPID = 1
• sometimes the child process terminates before its parent and then the child is able to
report PPID equal to the PID of the parent.
Wait
The wait system call blocks the caller until one of its child process terminates. If the caller
doesn’t have any child processes, wait returns immediately without blocking the caller. Using
wait the parent can obtain the exit status of the terminated child.
#include <sys/types.h>
#include <sys/wait.h>
WIFEXITED
#include <sys/types.h>
#include <sys/wait.h>
WIFEXITED(status);
status
The integer status value set by the wait system call.
Return value
Returns true if the child terminated normally, that is, by calling exit or by returning from
main.
WEXITSTATUS
#include <sys/types.h>
#include <sys/wait.h>
int WEXITSTATUS(status);
status
The integer status value set by the wait system call.
Return value
The exit status of the child. This consists of the least significant 8 bits of the status
argument that the child specified in a call to exit or as the argument for a return
statement in main. This macro should be employed only if WIFEXITED returned true.
On line 6 the parent calls wait(NULL) to wait for the child process to terminate.
Compile and run the program. Now the parent should always wait for the child to terminate
before terminating itself. As a consequence the child should:
One line 2 the parent creates the variable status. On line 7 the parent calls wait(&status) to
wait for the child process to terminate. The & is the address-of operator and &status returns the
address of the status variable. When the child terminates the exit status of the child will be
stored in variable status.
$ make
$ ./bin/fork_exit_wait_status
In the output you should be able to see that the parent obtained the exit status of the child.
Zombies
A terminated process is said to be a zombie or defunct until the parent does wait on the child.
• When a process terminates all of the memory and resources associated with it are
deallocated so they can be used by other processes.
• However, the exit status is maintained in the PCB until the parent picks up the exit status
using wait and deletes the PCB.
• A child process always first becomes a zombie.
• In most cases, under normal system operation zombies are immediately waited on by
their parent.
• Processes that stay zombies for a long time are generally an error and cause a resource
leak.
On line 9 the parent uses getchar to block itself until the user presses a key on the keyboard.
When the child terminates, the exit status of the child is stored in the child process control block
(PCB). The operating system deallocates all memory used by the child but the PCB cannot be
deallocated until the parent does wait on the child.
$ make
$ ./bin/fork_zombie
In the output you should be able to see that the child terminates and that the parent blocks
waiting for a keypress.
The child process has terminated but the parent has yet not read the exit status of the child using
wait. The child process has now become a zombie process.
Monitor
Open a second terminal and navigate to the module-2 directory. The module-2/tools/monitor
tool can be used to view process status information about process. Use the --help flag to see the
documentation.
$ ./tools/monitor --help
A top-like command that only lists USER, PID, STAT and COMM for the
current user and and proceses with a command name with a grep match of cmd.
Options:
-s delay Delay in seconds between refresh, default = 1.
-p pid Include process with PID pid.
The cmd argument is the name of the program executed by the processes we want to monitor.
Use the monitor tool to view process status information for the parent and child, both executing
the fork_zombie program.
$ ./tools/monitor fork_zombie
In the PID column you see the PID of the listed processes. The first line shows information about
the parent and the second line shows information about the child.
• The parent got status S (sleep) meaning the process is waiting for an event to complete. In
this case the parent is blocked waiting for the child to terminate.
• The child got status Z (zombie) meaning the process terminated but not yet reaped by its
parent.
In the terminal used to run monitor the zombie process should have disappear, leaving only the
parent process.
int main(void) {
getchar();
exit(127);
}
$ make
$ ./bin/child
First this program simply prints two messages to the terminal and then wait for a key-press.
After you press any key in the terminal the program terminates.
1 void child() {
2 char *const argv[] = {"./bin/child", NULL};
3
4 printf(" CHILD <%ld> Press any key to make me call exec!\n",
5 (long) getpid());
6
7 getchar();
8
9 execv(argv[0], argv);
10
11 perror("execv");
12 exit(EXIT_FAILURE);
13 }
On line 2 the needed argument vector is constructed. On line 7 the child waits for a key-press.
After the key-press, on line 9, the child use execv to replace the program executed by the child
process by the child executable. If execv is successful control will never be returned and lines
11 and 12 should not be reached.
$ make
$ ./bin/fork_exec
PARENT <33422> Spawned a child with PID = 33423.
CHILD <33423> Press any key to make me call exec!
Open a second terminal and use the ps command with the -p option to see information about the
child process.
$ ps -p 33206
PID TTY TIME CMD
33423 ttys023 0:00.00 ./bin/fork_exec
Note that the child process currently is executing the .bin/fork_exec executable.
$ ps -p 33206
PID TTY TIME CMD
33423 ttys023 0:00.00 ./bin/child
Note that the child process now executes the ./bin/child executable.
In the first terminal, press any key to make the child process terminate. Now the parent performs
wait on the child and reports the child exit status.