Processes are at the very heart of the operating system. As we have seen, all but a very few special processes are generated by the fork
system call. If successful, the fork
system call produces a child process that continues its execution at the point of its invocation in the parent process. In this chapter, we explore the generation and use of child processes in detail. In Chapter 11, “Threads,” the creation of threads will be discussed.
The fork
system call is unique in that while it is called once, it returns twice—to the child and to the parent processes. As noted in Chapter 1, “Programs and Processes,” if the fork
system call is successful, it returns a value of 0 to the child process and the process ID of the child to the parent process. If the fork
system call fails, it returns a −1 and sets the global variable errno
. The failure of the system to generate a new process can be traced, by examination of the errno
value, to either exceeding the limits on the number of processes (systemwide or for the specific user) or to the lack of available swap space for the new process. It is interesting to note that in theory the operating system is always supposed to leave room in the process table for at least one superuser process, which could be used to remove (kill) hung or runaway processes. Unfortunately, on many systems it is still relatively easy to write a program (sometimes euphemistically called a fork
bomb) that will fill the system with dummy processes, effectively locking out system access by anyone, including the superuser.
After the fork
system call, both the parent and child processes are running and continue their execution at the next statement after the fork
. The return from the fork
system call can be examined, and the process can make a decision as to what code is executed next. The process receiving a 0 from the fork
system call knows it is the child, as 0 is not valid as a PID. Conversely, the parent process will receive the PID of the child. An example of a fork
system call is shown in Program 3.1.
Example 3.1. Generating a child process.
File : p3.1.cxx | /* | Generating a child process | */ | #include <iostream> + #include <sys/types.h> | #include <unistd.h> | using namespace std; | int | main( ){ 10 if (fork( ) == 0) | cout << "In the CHILD process" << endl; | else | cout << "In the PARENT process" << endl; | return 0; + }
There is no guarantee as to the output sequence that will be generated by this program. For example, if we issue the command-line sequence
linux$ p3.1 ; echo DONE ; p3.1 ; echo DONE ; p3.1
numerous times, sometimes the statement In the CHILD process
will be displayed before the In the PARENT process
, and other times it will not. The output sequence is dependent upon the scheduling algorithm used by the kernel. Keep in mind that commands separated by a semicolon on the command line are executed sequentially, with the shell waiting for each command to terminate before executing the next. The effects of process scheduling are further demonstrated by Program 3.2.
Example 3.2. Multiple activities parent/child processes.
File : p3.2.cxx | /* | Multiple activities PARENT -- CHILD processes | */ | #include <iostream> + #include <cstring> | #include <sys/types.h> | #include <unistd.h> | using namespace std; | int 10 main( ) { | static char buffer[10]; | if (fork( ) == 0) { // In the child process | strcpy(buffer, "CHILD..."); | } else { // In the parent process + strcpy(buffer, "PARENT.."); | } | for (int i=0; i < 3; ++i) { // Both processes do this | sleep(1); // 3 times each. | write(1, buffer, sizeof(buffer)); 20 } | return 0; | }
Figure 3.1 shows the output of this program when run twice on a local system.
Example 3.1. Output of Program 3.2.
linux$ p3.2 PARENT..CHILD...CHILD...PARENT..PARENT..CHILD...linux$ linux$ p3.2 PARENT..CHILD...PARENT..CHILD...PARENT.. $ CHILD...
There are several interesting things to note about this program and its output. First, the write
(line 19) system call, not the cout
object, was used in the program. The cout
object (an instance of the ostream
class defined in <iostream>
) is buffered and, if used, would have resulted in the three-message output from each process being displayed all at one time without any interleaving of messages. Second, the system call sleep
(sleep a specified number of seconds) was used to prevent the process from running to completion within one time slice (which again would produce a homogenous output sequence). Third, one process will always end before the other. If there is sufficient intervening time before the second process ends, the system will redisplay the prompt, thus producing the last line of output where the output from the child process is appended to the prompt (i.e., linux$ CHILD...
).
Keep in mind the system will flush an output stream (write its data to the physical media) in a variety of circumstances. This synchronization occurs when (a) a file is closed, (b) a buffer is full, (c) in C++ the flush
or endl
manipulators are placed in the output stream, or (d) a call is made to the sync
system call.
Processes generate child processes for a number of reasons. In a Linux environment, there are several long-lived processes, which run continuously in the background and provide system services upon demand. These processes, called daemon processes, frequently generate child processes to carry out the requested service. Some daemon processes commonly found in a Linux environment are lpd
, the line printer daemon; xinetd
, the extended Internet services daemon; and syslogd
, the system logging daemon. Some problems (such as with databases) lend themselves to concurrent type solutions that can be effected via multiple child processes executing the same code. More commonly, such as when the shell processes a command, a process procreates a child process because it would like to transform the child process by changing the program code the child process is executing.
In Linux, any one of five library functions and one system call can be used to replace the current process image with a new image.[1] The library functions act as a front end to the system call. The library functions are discussed in the exec
manual pages (Section 3), while the system call (execve
) warrants its own manual page entry in Section 2. Any of these can be directly invoked by the programmer. For ease of comparison, the library functions and the system call are discussed as a group. The phrase exec
call will reference this group.
It is important to remember that when a process issues any exec
call, if the call is successful, the existing process is overlaid with a new set of program code. The text, data (initialized and uninitialized), and stack segment of the process are replaced and only the u
(user) area of the process remains the same. The new program code (if a C/C++ binary) begins its execution at the function main
. Since the system is now executing a different set of code for the same process, some things, by necessity, must change:
Signals that were specified as being caught by the process (i.e., associated with a signal-catching routine) are reset to their default action. This is necessary, as the addresses for the signal-catching routines are no longer valid.
In a similar vein, if the process was profiling (determining how much time is spent in individual routines), the profiling will be turned off in the overlaid process.
If the new program has its SUID
bit set, the effective EUID
and EGID
are set accordingly.
The program to be executed can be a script. In this case, the script should have its execute bit set and start with the line #!
interpreter [arg(s)], where interpreter is a valid executable (but not another script). If successful, the exec
calls do not return, as the initial calling image is lost when overlaid with a new image.
Before we delve into these calls, we should take a quick look at what normally transpires when a valid command is issued at the system (shell) level, as this process will reflect the functionality available in a program. If the command issued is
linux$ cat file.txt > file2.txt
the shell parses the command line and divides it into valid tokens (e.g., cat
, file.txt
, etc.). The shell (via a call to fork
) then generates a child process. After the fork
, the shell closes standard output and opens the file file2.txt
, mapping it to standard output in the child process. Next, by calling execve, the shell overlays the current program code with the program code for the command (in this case, the code for cat
). When the command is finished, the shell redisplays its prompt. Figure 3.2 shows the process creation and command execution sequence.
While the command is executing, the shell, by default, waits in the background. As we will see, there is a wait
system call that allows the shell or any other process to wait. Should the user place an &
at the end of the command (to indicate to the shell that the command be placed in background), the shell will not wait and will return immediately with its prompt. When the command is finished, it may perform a call to exit
or return
when in the function main
. The integer value passed to these calls is made available to the parent process via an argument to the wait
system call. When on the command line, the returned value is stored in the system variable named status
. If in the Bourne or BASH shell you issue the command
linux$ echo $?
the system will display the value returned by the last command executed. As the mapping of standard output to the file file2.txt
was done in the child process and not in the shell, the I/O redirection has no further impact on ensuing command sequences.
We should note that it is possible for a user at the command line to issue an exec
call. The syntax would be
linux$ exec command [arguments]
However, most users would not do this. The current process (the shell) would be overlaid with the program code for the command. Once the command was finished, the user would be logged out, as the original shell process would no longer exist!
In a programming environment, the exec
calls can be used to execute another program. The prototypes for the exec
calls are listed in Table 3.1.
Table 3.1. The exec
Call Prototypes.
#include <unistd.h> extern char **environ; int execl (const char *path, const char *arg, ...); int execv (const char *path, char *const argv[]); int execle(const char *path, const char *arg , ... , char * const envp[]); int execve(const char *path, char *const argv[], char * const envp[]); int execlp(const char *file, const char *arg, ...); int execvp(const char *file, char *const argv[]); |
The naming convention for these system calls reflects their functionality. Each call starts with the letters exec
. The next letter in the call name indicates if the call takes its arguments in a list format (i.e., literally specified as a series of arguments) or as a pointer to an array of arguments (analogous to the argv
structure discussed earlier). The presence of the letter l
indicates a list arrangement (a variable argument list—see the manual page on stdarg
for details); v
indicates the array or vector arrangement. The next letter of the call name (if present) is either an e
or a p
. The presence of an e
indicates the programmers will construct (in the array/vector format) and pass their own environment variable list. The passed environment variable list will become the third argument to the function main
(i.e., envp
). As noted in the section on environment variables, envp
is of limited practical value. When the programmer is responsible for the environment, the current environment variable list is not passed. The presence of a p
indicates the current environment PATH
variable should be used when searching for a file whose name does not contain a slash.[2] In the four calls, where the PATH string is not used (execl
, execv
, execle
and execve
), the path to the program to be executed must be fully specified.
The functionality of the exec
system calls is best summarized by Table 3.2.
Table 3.2. exec
Call Functionality.
Library Call Name | Argument Format | Pass Current Set of Environment Variables? | Search of |
---|---|---|---|
| list | yes | no |
| array | yes | no |
| list | no | no |
| array | no | no |
| list | yes | yes |
| array | yes | yes |
Of the six variations, execlp
and execvp
calls are used most frequently (as automatic environment passing and path searching are usually desirable) and will be explained in detail.
The execlp
library function (Table 3.3) is used when the number of arguments to be passed to the program to be executed is known in advance.
When using execlp
, the initial argument, file
, is a pointer to the file that contains the program code to be executed. If this file reference begins with a /, it is assumed that the reference is an absolute path to the file. In this circumstance, it would appear that the p
specification (execlp
) is superfluous; however, the PATH
string is still used if other arguments are file names or if the code to be executed contains file references. If no / is found, each of the directories specified in the PATH
variable will be, in turn, preappended to the file name specified, and the first valid program reference found will be the one executed. It is a good practice to fully specify the program to be executed in all situations to prevent a program with the same name, found in a prior PATH
string directory, from being inadvertently executed. For the execlp
call to be successful, the file referenced must be found and be marked as executable. If the call fails, it returns a −1 and sets errno
to indicate the error. As the overlaying of one process image with another is very complex, the possibilities for failure are numerous (as shown in Table 3.4).
Table 3.3. Summary of the execlp
Library Function.
Include File(s) |
<unistd.h> extern char **environ; | Manual Section | 3 | |
Summary |
| |||
Return | Success | Failure | Sets | |
Does not return | −1 | Yes |
Table 3.4. exec
Error Messages.
# | Constant |
| Explanation |
---|---|---|---|
1 | EPERM | Operation not permitted |
|
2 | ENOENT | No such file or directory | One or more parts of path to new process file does not exist (or is NULL). |
4 | EINTR | Interrupted system call | Signal was caught during the system call. |
5 | EIO | Input/output error | |
7 | E2BIG | Argument list too long | New process argument list plus exported shell variables exceed the system limits. |
8 | ENOEXEC | Exec format error | New process file is not in a recognized format. |
11 | EAGAIN | Resource temporarily unavailable | Total system memory while reading raw I/O is temporarily insufficient. |
12 | ENOMEM | Cannot allocate memory | New process memory requirements exceed system limits. |
13 | EACCES | Permission denied |
|
14 | EFAULT | Bad address |
|
20 | ENOTDIR | Not a directory | Part of the specified |
21 | EISDIR | Is a directory | An ELF interpreter was a directory. |
22 | EINVAL | Invalid argument | An ELF executable had more than one interpreter. |
24 | EMFILE | Too many open files | Process has exceeded the maximum number of files open. |
26 | ETXTBSY | Text file busy | More than one process has the executable open for writing. |
36 | ENAMETOOLONG | File name too long | The |
40 | ELOOP | Too many levels of symbolic links | The |
67 | ENOLINK | Link has been severed | The |
72 | EMULTIHOP | Multihop attempted | The |
80 | ELIBBAD | Accessing a corrupted shared library | An ELF interpreter was not in a recognized format. |
The ellipses in the execlp
function prototype can be thought of as argument 0 (arg0
) through argument n (argn
). These arguments are pointers to the null-terminated strings that would be normally passed by the system to the program if it were invoked on the command line. That is, argument 0, by convention, should be the name of the program that is executing. This is usually the same as the value in file
, although the program referenced by file
may include an absolute path, while the value in argument 0 most often would not. Argument 1 would be the first parameter to be passed to the program (which, using argv
notation, would be argv[1]
), argument 2 would be the second, and so on. The last argument to the execlp
library call must be a NULL that is, for portability reasons, cast to a character pointer. Program 3.3, which invokes the cat
utility program, demonstrates the use of the execlp
library call.
Example 3.3. Using the execlp
system call.
File : p3.3.cxx | /* | Running the cat utility via an exec system call | */ | #include <iostream> + #include <cstdio> | #include <unistd.h> | using namespace std; | int | main(int argc, char *argv[ ]){ 10 if (argc > 1) { | execlp("/bin/cat", "cat", argv[1], (char *) NULL); | perror("exec failure "); | return 1; | } + cerr << "Usage: " << *argv << " text_file" << endl; | return 2; | }
When passed a text file name on the command line, this program displays the contents of the file to the screen. The program accomplishes this by overlaying its own process image with the program code for the cat
utility program. The program passes the cat
utility program the name (referenced by argv[1]
) of the file to display. If the execlp
system call fails, the call to perror
is made and the program exits and returns the value 1 to the system. If the call is successful, the perror
and return
statements are never reached, as they are replaced with the program code for the cat
utility.
A sample run of the program is shown in Figure 3.3.
Example 3.3. Output of Program 3.3.
linux$ p3.3 test.txt This is a sample text file for the program to display!
If the number of arguments for the program to be executed is dynamic, then the execvp
call can be used (Table 3.5). As with the execlp
call, the initial argument to execvp
is a pointer to the file that contains the program code to be executed. However, unlike execlp
, there is only one additional argument that execvp
requires. This second argument, defined as
char *const argv[ ]
specifies that a reference to an array of pointers to character strings should be passed. The format of this array parallels that of argv
and, in many cases, is argv
. If the reference is not the argv
values for the current program, the programmer is responsible for constructing and initializing a new argv
-like array. If this second approach is taken, the last element of the new argv
array should contain a NULL address value. If execvp
fails, it returns a value of −1 and sets the value in errno
to indicate the source of the error (see Table 3.5).
Table 3.5. Summary of the execvp
System Call.
Include File(s) |
<unistd.h> <extern char **environ; | Manual Section | 3 | |
Summary |
| |||
Return | Success | Failure | Sets | |
Does not return | −1 | Yes |
Program 3.4 makes use of the argv
values for the current program.
Example 3.4. Using execvp
with argv
values.
File : p3.4.cxx | /* | Using execvp to execute the contents of argv | */ | #include <iostream> + #include <cstdio> | #include <unistd.h> | using namespace std; | int | main(int argc, char *argv[ ]) { 10 if ( argc > 1 ) { | execvp(argv[1], &argv[1]); | perror("exec failure"); | return 1; | } + cerr << "Usage: " << *argv << " exe [arg(s)]" << endl; | return 2; | }
The program will execute, via execvp
, the program passed to it on the command line. The first argument to execvp
, argv[1]
, is the reference to the program to execute.
The second argument, &argv[1]
, is the reference to the remainder of the command-line argv
array. Notice that both of these references began with the second element of argv
(that is, argv[1]
), as argv[0]
is the name of the current program (e.g., p3.4
). The output in Figure 3.4 shows that the program does work as expected.
Example 3.4. Output of Program 3.4 when passed the cat
command.
linux$ p3.4 cat test.txt This is a sample text file for a program to display!
If we place additional information on the command line when running Program 3.4, we find the program will pass the information on, as demonstrated in Figure 3.5.
Example 3.5. Output of Program 3.4 when passed the cat
command with the -n
option.
linux$ p3.4 cat -n test.txt 1 This is a sample text 2 file for a program to 3 display!
If command-line argv
values of the current program are not used with execvp
, then the programmer must construct a new argv
to be passed. An example of how this can be done is shown in Program 3.5.
Example 3.5. Using execvp
with a programmer-generated argument list.
File : p3.5.cxx | /* | Generating our own argv type list for execvp | */ | #include <iostream> + #include <cstdio> | #include <unistd.h> | using namespace std; | int | main( ){ 10 char *new_argv[ ] = {"cat", | "test.txt", | (char *) 0 | }; | execvp("/bin/cat", new_argv ); + perror("exec failure "); | return 1; | }
When compiled and run as p3.5
, the output of this program will be the same as the output from the first run of Program 3.4.
In most programs, the fork
and exec
calls are used in conjunction with one another (in some operating systems, the fork
and exec
calls are packaged as a single spawn
system call). The parent process generates a child process, which it then overlays by a call to exec
, as in Program 3.6.
Example 3.6. Using fork
with execlp
.
File : p3.6.cxx | /* | Overlaying a child process via an exec | */ | #include <iostream> + #include <sstream> | #include <cstdio> | #include <unistd.h> | using namespace std; | int 10 main( ){ | char *mesg[ ] = {"Fie", "Foh", "Fum"}; | int display_msg(char *); | for (int i=0; i < 3; ++i) | display_msg(mesg[i]); + return 0; | } | int | display_msg(char *m){ | ostringstream oss(ostringstream::out); 20 switch (fork( )) { | case 0: | sleep(1); | execlp("/bin/echo", "echo", m, (char *) NULL); | oss << m << " exec failure"; // build error msg string + perror(oss.str().c_str()); | return 1; | case -1: | perror("Fork failure"); | return 2; 30 default: | return 0; | } | }
Program 3.6 displays three messages (based on the contents of the array mesg
). This action is accomplished by calling the display_msg
function three times. Once in the display_msg
function, the program fork
s a child process and then overlays the child process code with the program code for the echo
command. The output of the program is shown in Figure 3.6.
Due to scheduling, the order of the messages may change when run multiple times.
It is interesting to observe what happens if the execlp
call in display_msg
fails (line 23). If we purposely sabotage the execlp
system call by changing it to
execlp("/bin/no_echo", "echo", m , (char *) NULL );
and assuming there is not an executable file called no_echo
to be found in /bin
, the output[3] of the program becomes that shown in Figure 3.7.
Example 3.7. Output of Program 3.6 when execlp
fails.
linux$ p3.6 Foh exec failure: No such file or directory Fie exec failure: No such file or directory Fum exec failure: No such file or directory Fum exec failure: No such file or directory Foh exec failure: No such file or directory Fum exec failure: No such file or directory Fum exec failure: No such file or directory
Surprisingly, when the execlp
call fails, we end up with a total of eight processes—the initial process and its seven children. Most likely this was not the intent of the original programmer. One way to correct this is within the display_msg
function: In the case 0:
branch of the switch
statement, replace the return
statement in line 26 with a call to exit
.
Combining what we have learned so far, we can produce, in relatively few lines of code, a shell program that restricts the user to a few basic commands (in this example, ls
, ps
, and df
). The code for our shell program[4] is shown in Program 3.7.
This program could be considered a very stripped-down version of a restricted[5] shell. The main thrust of the program is pedagogical, and improvements and expansions (of which there can be many) will be addressed in ensuing sections of the text and in a number of exercises.
Example 3.7. The huh
shell.
File : p3.7.cxx | /* | A _very_ limited shell program | */ | #include <iostream> + #include <cstdio> | #include <cstring> | #include <cstdlib> | #include <unistd.h> | using namespace std; 10 | const int MAX =256; | const int CMD_MAX=10; | char *valid_cmds = " ls ps df "; | int + main( ){ | char line_input[MAX], the_cmd[CMD_MAX]; | char *new_args[CMD_MAX], *cp; | int i; | while (1) { 20 cout << "cmd> "; | if (cin.getline(line_input, MAX, ' ') != NULL) { | cp = line_input; | i = 0; | if ((new_args[i] = strtok(cp, " ")) != NULL) { + strcpy(the_cmd, new_args[i]); | strcat(the_cmd, " "); | if ((strstr(valid_cmds, the_cmd)—valid_cmds) % 4 == 1) { | do { | cp = NULL; 30 new_args[++i] = strtok(cp, " "); | } while (i < CMD_MAX && new_args[i] != NULL); | new_args[i] = NULL; | switch (fork( )) { | case 0: + execvp(new_args[0], new_args); | perror("exec failure"); | exit(1); | case -1: | perror("fork failure"); 40 exit(2); | default: | // In the parent we should be waiting for | // the child to finish | ; + } | } else | cout << "huh?" << endl; | } | } 50 } | }
The commands the user is permitted to issue when running our shell are found in the global character string called valid_cmds
. In the valid_cmds
string, each two-letter command is preceded and followed by a space. By delimiting the commands in this manner, a predefined C string searching function strstr
can be used to determine if a user has entered a valid command. While this technique is simplistic, it is effective when a limited number of commands need to be checked. The program then issues a shell-like prompt, cmd>
, and uses the C++ input function getline
to store user input in a character array buffer called line_input
. The getline
function will read a line of input, including intervening whitespace that is terminated by a newline. If the getline
function fails (such as when the user just presses return), the program loops back around and reprompts the user for additional input. Upon entry of input, the program uses the C string function strtok
to obtain the first valid token from the line_input
array. The strtok
function, which will divide a referenced character string into tokens, requires a pointer to the array it is to parse and a list of delimiting characters that delimit tokens (in this case only a blank “ ” has been indicated). The strtok
function is a wonderful example of the idiosyncratic nature of some functions in C/C++. When strtok
is called successive times and passed a reference to NULL, it will continue to parse the initial input line starting each time where it left off previously. The strcat
function is used to add a trailing blank to this first token (assumed to the command), and the resulting sequence is stored in a character array called the_cmd
.
The next line of the program checks for the presence of the command in the valid_cmds
string at a modulus-4-based offset (see Figure 3.8).
If the command is found, a do-while
loop is used to obtain the remaining tokens (up to the limit CMD_MAX
). These tokens are stored in successive elements of the previously declared new_args
array. Upon exiting the loop, we assure that the last element of the new_args
array contains the requisite NULL value. A switch
statement, in concert with fork
and execvp
system calls, is used to execute the command.
Eventually all things must come to an end. Now that we have generated processes, we should take a closer look at how to end a process. Under its own power (assuming the process does not receive a terminating signal and the system has not crashed) a process normally terminates in one of three ways.[6] In order of preference, these are
It issues (at any point in its code) a call to either exit
or _exit
.
It issues a return
in the function main
.
It falls off the end of the function main
ending implicitly.
Programmers routinely make use of the library function exit
to terminate programs. This function, which does not return a value, is defined as shown in Table 3.6.
Table 3.6. Summary of the exit
Library Function.
Include File(s) |
| Manual Section | 3 | |
Summary |
| |||
Return | Success | Failure | Sets | |
Does not return | No return |
In earlier versions of C the inclusion of a specific header file was not required when using exit
. More recent versions of C (and C++) require the inclusion of the file <stdlib.h>
(or <cstdlib>
if going the full ANSI-C++ route) that contains the exit
function prototype. The exit
function accepts a single parameter, an integer status
value that will be returned to the parent process.[7] By convention, a 0 value is returned if the program has terminated normally; other wise, a nonzero value is returned.[8] For those who wish to standardize the value returned by exit
when terminating, the header file <stdlib.h>
contains two defined constants, EXIT_SUCCESS
and EXIT_FAILURE
, which can be used to indicate program success and failure respectively. If we somehow are able to slip by the compiler a call to exit
without passing an exit status value (i.e., exit( )
;
) or issue a return;
in main
without specifying a value, then what is returned to the parent process is technically undefined.
Upon invocation, the exit
function performs several actions. Figure 3.9 shows the relationship of the actions taken.
First, exit
will call, in reverse order, all functions that have been registered using the atexit
library function. The atexit
function is relatively new. Some older BSD-based versions of C (as well as some version of GNU) supported a library function called on_exit
that offered a similar functionality. As future support for on_exit
looks to be a bit sketchy; it might be best to stay clear of it. The atexit
function should provide similar functionality.
A brief description of the atexit
function is in order. The definition of atexit
, shown in Table 3.7, indicates that functions to be called (when the process terminates normally)[9] are registered by passing the atexit
function the address of the function. The registered functions should not have any parameters. If atexit
is successful in registering the function, atexit
returns a 0; otherwise, it returns a −1 but will not set errno
.[10]
Program 3.8 demonstrates the use of atexit
.
When run, the output of the program shows that the registered functions are called in inverse order (Figure 3.10).
In older versions of C, once all atexit
functions were called, the standard I/O library function _cleanup
would be called. Newer versions of GNU C/C++ do not support the _cleanup
function. Now when all atexit
functions have been processed, exit
calls the system call _exit
(passing on to it the value of status). Programmers may call _exit
directly if they wish to circumvent the invocation of atexit
registered functions and the flushing of I/O buffers. See Table 3.8.
Table 3.7. Summary of the atexit
Library Function.
Include File(s) |
| Manual Section | 3 | |
Summary |
| |||
Return | Success | Failure | Sets | |
0 | −1 | No |
Example 3.8. Using the atexit
library function.
File : p3.8.cxx | #include <iostream> | #include <cstdlib> | using namespace std; | int + main( ){ | void f1( ), f2( ), f3( ); | atexit(f1); | atexit(f2); | atexit(f3); 10 cout << "Getting ready to exit" << endl; | exit(0); | } | void | f1( ){ + cout << "Doing F1" << endl; | } | void | f2( ){ | cout << "Doing F2" << endl; 20 } | void | f3( ){ | cout << "Doing F3" << endl; | }
Table 3.8. Summary of the _exit
System Call.
Include File(s) | <uni | Manual Section | 2 | |
Summary |
| |||
Return | Success | Failure | Sets | |
Does not return | Does not return |
The _exit
system call, like its relative, exit
, does not return. This call also accepts an integer status value, which will be made available to the parent process. When terminating a process, the system performs a number of housekeeping operations:
All open file descriptors are closed.
The parent of the process is notified (via a SIGCHLD signal) that the process is terminating.
Status information is returned to the parent process (if it is waiting for it). If the parent process is not waiting, the system stores the status information until a wait
by the parent process is affected.
All child processes of the terminating process have their parent process ID (PPID) set to 1—they are inherited by init
.
If the process was a group leader, process group members will be sent SIGHUP/ SIGCONT signals.
Shared memory segments and semaphore references are readjusted.
If the process was running accounting, the accounting record is written out to the accounting file.
They also serve who only stand and wait. | ||
--John Milton 1608–1674 On His Blindness [1652] |
More often than not, a parent process needs to synchronize its actions by waiting until a child process has either stopped or terminated its actions. The wait
system call allows the parent process to suspend its activity until one of these actions has occurred (Table 3.9).
Table 3.9. Summary of the wait
System Call.
Include File(s) |
<sys/types.h> <sys/wait.h> | Manual Section | 2 | |
Summary |
| |||
Return | Success | Failure | Sets | |
Child process ID or 0 | −1 | Yes |
The activities of wait
are summarized in Figure 3.11.
The wait
system call accepts a single argument, which is a pointer to an integer, and returns a value defined as type pid_t
. Data type pid_t
is found in the header file <sys/types.h>
and is most commonly a long int.
If the calling process does not have any child processes associated with it, wait
will return immediately with a value of −1 and errno
will be set to ECHILD (10)
. However, if any child processes are still active, the calling process will block (suspend its activity) until a child process terminates. When a waited-for child process terminates, the status information for the child and its process ID (PID) are returned to the parent. The status information is stored as an integer value at the location referenced by the pointer status
. The low-order 16 bits of the location contain the actual status information, and the high-order bits (assuming a 32-bit machine) are set to zero. The low-order bit information can be further subdivided into a low- and high-order byte. This information is interpreted in one of two ways:
If the child process terminated normally, the low-order byte will be 0 and the high-order byte will contain the exit code (0–255):
byte 3 | byte 2 | byte 1 | byte 0 |
exit code | 0 |
If the child process terminated due to an uncaught signal, the low-order byte will contain the signal number and the high-order byte will be 0:
byte 3 | byte 2 | byte 1 | byte 0 |
0 | signal # |
In this second situation, if a core file has been produced, the leftmost bit of byte 0 will be a 1. If a NULL argument is specified for wait
, the child status information is not returned to the parent process, the parent is only notified of the child's termination.
Here are two programs, a parent (Program 3.9) and child (Program 3.10), that demonstrate the use of wait
.
Example 3.9. The parent process.
File : p3.9.cxx | /* | A parent process that waits for a child to finish | */ | #include <iostream> + #include <cstdlib> | #include <iomanip> | #include <unistd.h> | #include <sys/types.h> | #include <sys/wait.h> 10 using namespace std; | int | main(int argc, char *argv[] ){ | pid_t pid, w; | int status; + if ( argc < 4 ) { | cerr << "Usage " << *argv << " value_1 value_2 value_3 "; | return 1; | } | for (int i = 1; i < 4; ++i) // generate 3 child processes 20 if ((pid = fork( )) == 0) | execl("./child", "child", argv[i], (char *) 0); | else // assuming no failures here | cout << "Forked child " << pid << endl; | /* + Wait for the children | */ | while ((w=wait(&status)) && w != -1) | cout << "Wait on PID: " << dec << w << " returns status of " | << setw(4) << setfill('0') << hex 30 << setiosflags(ios::uppercase) << status << endl; | return 0; | }
The parent program forks three child processes. Each child process is overlaid with the executable code for the child (found in Program 3.10). The parent process passes to each child, from the parent's command line, a numeric value. As each child process is produced, the parent process displays the child process ID. After all three processes have been generated; the parent process initiates a loop to wait for the child processes to finish their execution. As each child process terminates, the value returned to the parent process is displayed.
Example 3.10. The child process.
File : p3.10.cxx | /* | The child process | */ | #define _GNU_SOURCE + #include <iostream> | #include <cstdlib> | #include <iomanip> | #include <sys/types.h> | #include <unistd.h> 10 #include <signal.h> | using namespace std; | int | main(int argc, char *argv[ ]){ | pid_t pid = getpid( ); + int ret_value; | srand((unsigned) pid); | ret_value = int(rand( ) % 256); // generate a return value | sleep(rand( ) % 3); // sleep a bit | if (atoi(*(argv + 1)) % 2) { // assuming argv[1] exists! 20 cout << "Child " << pid << " is terminating with signal 0009" << endl; | kill(pid, 9); // commit hara-kiri | } else { | cout << "Child " << pid << " is terminating with exit(" | << setw(4) << setfill('0') << setiosflags(ios::uppercase) + << hex << ret_value << ")" << endl; | exit(ret_value); | } | }
In the child program, the child process obtains its own PID using the getpid
call. The PID value is used as a seed value to initialize the srand
function. A call to rand
is used to generate a unique value to be returned when the process exits. The child process then sleeps a random number of seconds (0–3). After sleeping, if the argument passed to the child process on the command line is odd (i.e., not evenly divisible by 2), the child process kills itself by sending a signal 9 (SIGKILL) to its own PID. If the argument on the command line is even, the child process exits normally, returning the previously calculated return value. In both cases, the child process displays a message indicating what it will do before it actually executes the statements.
The source programs are compiled and the executables named parent and child respectively. They are run by calling the parent program. Two sample output sequences are shown in Figure 3.12.
Example 3.12. Two runs of Programs 3.9 and 3.10.
linux$ parent 2 1 2 <-- 1 Forked child 8975 Forked child 8976 Child 8976 is terminating with signal 0009 Forked child 8977 Wait on PID: 8976 returns status of 0009 Child 8977 is terminating with exit(008F) Wait on PID: 8977 returns status of 8F00 Child 8975 is terminating with exit(0062) Wait on PID: 8975 returns status of 6200 linux$ parent 2 2 1 <-- 2 Forked child 8980 Forked child 8981 Forked child 8982 Child 8982 is terminating with signal 0009 Wait on PID: 8982 returns status of 0009 Child 8980 is terminating with exit(00B0) Wait on PID: 8980 returns status of B000 Child 8981 is terminating with exit(00D3) Wait on PID: 8981 returns status of D300
There are several things of interest to note in this output. In the first output sequence, one child processes (PID 8976) has terminated before the parent has finished its process generation. Processes that have terminated but have not been wait
ed upon by their parent process are called zombie processes. Zombie processes occupy a slot in the process table, consume no other system resources, and will be marked with the letter Z when a process status command is issued (e.g., ps -alx
or ps -el
). A zombie process cannot be killed[11] even with the standard Teflon bullet (e.g., at a system level: kill -9
process_id_number
). Zombies are put to rest when their parent process performs a wait
to obtain their process status information. When this occurs, any remaining system resources allocated for the process are recovered by the kernel. Should the child process become an orphan before its parent issues the wait
, the process will be inherited by init
, which, by design, will issue a wait
for the process. On some very rare occasions, even this will not cause the zombie process to “die.” In these cases, a system reboot may be needed to clear the process table of the entry.
Both sets of output clearly show that when the child process terminates normally, the exit value returned by the child is stored in the second byte of the integer value referenced by argument to the wait
call in the parent process. Likewise, if the child terminates due to an uncaught signal, the signal value is stored in the first byte of the same referenced location. It is also apparent that wait
will return with the information for the first child process that terminates, which may or may not be the first child process generated.
It is easy to see that the interpretation of the status information can be cumbersome, to say the least. At one time, programmers wrote their own macros to interrogate the contents of status. Now most use one of the predefined status macros. These macros are shown in Table 3.10.
Table 3.10. The wstat
Macros.
Macro | Description |
---|---|
| Returns a true if the child process exited normally. |
| Returns the |
| Returns a true if the child exited due to uncaught signal. |
| Returns the signal that terminated the child. Should be called only if |
| Returns a true if the child process is stopped. |
| Returns the signal that stopped the child. Should be called only if WIFSTOPPED(status)has returned a true. |
The argument to each of these macros is the integer status value (not the pointer to the value) that is returned to the wait
call. The macros are most often used in pairs. The WIF
macros are used as a test for a given condition. If the condition is true, the second macro of the pair is used to return the specified value. As shown below, these macros could be incorporated in the wait loop in the parent Program 3.9 to obtain the child status information:
... while ((w = wait(&status)) && w != -1) if (WIFEXITED(status)) // test with macro cout << "Wait on PID: " << dec << w << " returns a value of " << hex << WEXITSTATUS(status) << endl; // obtain value else if (WIFSIGNALED(status)) // test with macro cout << "Wait on PID: " << dec << w << " returns a signal of " << hex << WTERMSIG(status) << endl; // obtain value ...
While the wait
system call is helpful, it does have some limitations. It will always return the status of the first child process that terminates or stops. Thus, if the status information returned by wait
is not from the child process we want, the information may need to be stored on a temporary basis for possible future reference and additional calls to wait
made. Another limitation of wait
is that it will always block if status information is not available. Fortunately, another system call, waitpid
, which is more flexible (and thus more complex), addresses these shortcomings. In most invocations, the waitpid
call will block the calling process until one of the specified child processes changes state. The waitpid
system call summary is shown in Table 3.11.
Table 3.11. Summary of the waitpid
System Call.
Include File(s) |
<sys/types.h> <sys/wait.h> | Manual Section | 2 | ||
Summary |
| ||||
Return | Success | Failure | Sets | ||
Child PID or 0 | −1 | Yes |
The first argument of the waitpid
system call, pid
, is used to stipulate the set of child process identification numbers that should be waited for (Table 3.12).
Table 3.12. Interpretation of pid
Values by waitpid
.
| Wait for |
---|---|
| Any child process whose process group ID equals the absolute value of |
| Any child process—in a manner similar to |
| Any child process whose process group ID equals the caller's process group ID. |
| The child process with this process ID. |
The second argument, *status
, as with the wait
call, references an integer status location where the status information of the child process will be stored if the waitpid
call is successful. This location can be examined directly or with the previously presented wstat
macros.
The third argument, options
, may be 0 (don't care), or it can be formed by a bitwise OR
of one or more of the flags listed in Table 3.13 (these flags are usually defined in the <sys/wait.h>
header file). The flags are applicable to the specified child process set discussed previously.
Table 3.13. Flag Values for waitpid
.
FLAG Value | Specifies |
---|---|
WNOHANG | Return immediately if no child has exited—do not block if the status cannot be obtained; return a value of 0, not the |
WUNTRACED | Return immediately if child is blocked. |
If the value given for pid
is -1 and the option flag is set to 0, the waitpid
and wait
system call act in a similar fashion. If waitpid
fails, it returns a value of –1 and sets errno
to indicate the source of the error (Table 3.14).
Table 3.14. waitpid
Error Messages.
# | Constant |
| Explanation |
---|---|---|---|
4 | EINTR | Interrupted system call | Signal was caught during the system call. |
10 | ECHILD | No child process | Process specified by |
22 | EINVAL | Invalid argument | Invalid value for |
85 | ERESTART | Interrupted system call should be restarted | WNOHANG not specified, and unblocked signal or SIGCHILD was caught. |
We can modify a few lines in our current version of the parent process (Program 3.9) to save the generated child PIDs in an array. This information can be used with the waitpid
system call to coerce the parent process into displaying status information from child processes in the order of child process generation instead of their termination order. Program 3.11 shows how this can be done.
Example 3.11. A parent program using waitpid
.
File : p3.11.cxx | #include <iostream> | #include <cstdlib> | #include <iomanip> | #include <unistd.h> + #include <sys/types.h> | #include <sys/wait.h> | using namespace std; | int | main(int argc, char *argv[] ){ 10 pid_t pid[3], w; | int status; | if ( argc < 4 ) { | cerr << "Usage " << *argv << " value_1 value_2 value_3 "; | return 1; + } | for (int i=1; i < 4; ++i) // generate 3 child processes | if ((pid[i-1] = fork( )) == 0) | execl("./child", "child", argv[i], (char *) 0); | else // assuming no failures here 20 cout << "Forked child " << pid[i-1] << endl; | /* | Wait for the children | */ | for (int i=0;(w=waitpid(pid[i], &status,0)) && w != -1; ++i){ + cout << "Wait on PID " << dec << w << " returns "; | if (WIFEXITED(status)) // test with macro | cout << " a value of " << setw(4) << setfill('0') << hex | << setiosflags(ios::uppercase) << WEXITSTATUS(status) << endl; | else if (WIFSIGNALED(status)) // test with macro 30 cout << " a signal of " << setw(4) << setfill('0') << hex | << setiosflags(ios::uppercase) << WTERMSIG(status) << endl; | else | cout << " unexpectedly!" << endl; | } + return 0; | }
A run of this program (using the same child process—Program 3.10) confirms that the status information returned to the parent is indeed ordered based on the sequence of child processes generation, not the order in which the processes terminated. Also, note that the status macros are used to evaluate the return from waitpid
system call (Figure 3.13).
Example 3.13. Output of Program 3.11.
linux$ p3.11 2 2 1 Forked child 9772 Forked child 9773 <-- 1 Child 9773 is terminating with exit(008B) <-- 2 Forked child 9774 Child 9772 is terminating with exit(00CD) Wait on PID 9772 returns a value of 00CD <-- 3 Wait on PID 9773 returns a value of 008B Child 9774 is terminating with signal 0009 Wait on PID 9774 returns a signal of 0009
On some occasions, the information returned from wait
or waitpid
may be insufficient. Additional information on resource usage by a child process may be sought. There are two BSD compatibility library functions, wait3
and wait4
,[12] that can be used to provide this information (Table 3.15).
Table 3.15. Summary of the wait3/wait4
Library Functions.
Include File(s) |
#define _USE_BSD #include <sys/types.h> #include <sys/resource.h> #include <sys/wait.h> | Manual Section | 3 | ||
Summary |
pid_t wait3(int *status, int options, struct rusage *rusage); pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage); | ||||
Return | Success | Failure | Sets | ||
Child PID or 0 | −1 | Yes |
The wait3
and wait4
functions parallel the wait
and waitpid
functions respectively. The wait3
function waits for the first child process to terminate or stop. The wait4
function waits for the specified PID (pid
). In addition, should the pid
value passed to the wait4
function be set to 0, wait4
will wait on the first child process in a manner similar to wait3
. Both functions accept option flags to indicate whether or not they should block and/or report on stopped child processes. These option flags are shown in Table 3.16.
Table 3.16. Option Flag Values for wait3
/wait4
.
FLAG Value | Specifies |
---|---|
WNOHANG | Return immediately if no child has exited—do not block if the status cannot be obtained; return a value of 0 not the |
WUNTRACED | Return immediately if child is blocked. |
Both functions contain an argument that is a reference to a rusage
structure. This structure is defined in the header file <sys/resource.h>
.[13]
struct rusage { struct timeval ru_utime; /* user time used */ struct timeval ru_stime; /* system time used */ long ru_maxrss; /* maximum resident set size */ long ru_ixrss; /* integral shared memory size */ long ru_idrss; /* integral unshared data size */ long ru_isrss; /* integral unshared stack size */ long ru_minflt; /* page reclaims */ long ru_majflt; /* page faults */ long ru_nswap; /* swaps */ long ru_inblock; /* block input operations */ long ru_oublock; /* block output operations */ long ru_msgsnd; /* messages sent */ long ru_msgrcv; /* messages received */ long ru_nsignals; /* signals received */ long ru_nvcsw; /* voluntary context switches */ long ru_nivcsw; /* involuntary context switches */ };
If the rusage
argument is non-null, the system populates the rusage
structure with the current information from the specified child process. See the getrusage
system call in Section 2 of the manual pages for additional information. The status macros (see previous section on wait
and waitpid
) can be used with the status information returned by wait3
and wait4
. See Table 3.17.
Table 3.17. wait3
/wait4
Error Messages.
# | Constant |
| Explanation |
---|---|---|---|
4 | EINTR | Interrupted system call | Signal was caught during the system call. |
10 | ECHILD | No child process | Process specified by |
22 | EINVAL | Invalid argument | Invalid value for |
85 | ERESTART | Interrupted system call should be restarted | WNOHANG not specified, and unblocked signal or SIGCHILD was caught. |
Processes are generated by the fork
system call. The process that issues the fork
system call is known as the parent, and the new process as the child. Child processes may have their executable code overlaid with other executable code via an exec
system call. When a process finishes executing its code, performs a return in the function main
, or makes an exit
system call, the process terminates. Parent processes may wait
for their child processes to terminate. Terminating child processes return status information that can be examined by the parent process.
_exit
system call
atexit
system call
daemon
exec
execl
library function
execle
function call
execlp
library function
execv
library function
execve
system call
execvp
library function
exit code
exit
library function
flush
rand
library function
restricted shell
rusage
structure
srand
library function
status information
strstr
library function
strtok
library function
wait
system call
wait3
library function
wait4
library function
waitpid
system call
WEXITSTATUS
macro
WIFEXITED
macro
WIFSIGNALED
macro
WIFSTOPPED
macro
wstat
macros
WSTOPSIG
macro
WTERMSIG
macro
zombie
[1] In some versions of UNIX, such as Solaris, all the exec
calls are system calls and are grouped together as library functions and discussed in one section of the manual. Linux has a more historic approach to things.
[2] If the executable file is a script, the Bourne shell (/bin/sh
) is invoked to execute the script. The shell is then passed the specified argument information.
[3] The program uses a common programming trick to create a message string on-the-fly to pass to the perror
routine.
[4] For reasons that become obvious when the program is run, this is nicknamed the huh
shell.
[5] Many UNIX environments come with a predefined restricted shell (which is different from the remote shell /bin/rsh
). A restricted shell is sometimes specified as a login shell for users (such as ftp) that require a more controlled environment. Linux does not come with a specific restricted shell for users, but some of the standard shells (such as bash
and ksh
) can be passed a command-line option (–r
) that will run the shell in restricted mode. Linux does come with a restricted shell for sendmail
(smrsh
).
[6] Of course, the library function abort
can also be used to end a process, but its call will result in an abnormal termination of the process.
[7] I know, I know—what if the parent is no longer around? Remember that init
inherits processes whose parents are gone. The handling of status values is discussed further in Section 3.6.
[8] Only the low-order eight bits are returned, thus values range from 0 to 255. (Hmm, I wonder ... would exit(-1)
actually return a 255?)
[9] A normal termination is considered a call to exit
or a return in main
. On our system, atexit
registered functions will be called even if the program ends implicitly (without a return
in main
).
[10] This is one of the rare cases where no explanation of errno
values is provided by system designers.
[11] This miraculous ability is the source of the name zombie.
[12] It is not clear if these functions will be supported in subsequent versions of the GNU compiler, and they may limit the portability of programs that incorporate them. As these are BSD-based functions, _USE_BSD
must be defined in the program code or defined on the command line when the source code is compiled.
[13] On some systems, you may need the header file <sys/rusage.h>
instead of <sys/resource.h>
, and you may need to explicitly link in the BSD library that contains the object code for the wait3/wait4
functions.
3.134.103.74