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
[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.
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 |
void exit(int status); |
|||
Return |
Success |
Failure |
Sets errno |
|
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 (or 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 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.
[7] I know, I knowwhat 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?)
Upon invocation, the exit function performs several actions. Figure 3.9 shows the relationship of the actions taken.
Figure 3.9. Actions taken by library function exit .
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]
[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.
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 |
int atexit(void (*function)(void)); |
|||
Return |
Success |
Failure |
Sets errno |
|
-1 |
No |
Program 3.8 Using the atexit library function.
File : p3.8.cxx #include #include 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; }
Figure 3.10 Output of Program 3.8.
linux$ p3.8 Getting ready to exit Doing F3 Doing F2 Doing F1
Table 3.8. Summary of the _exit System Call.
Include File(s) |
std.h> |
Manual Section |
2 |
|
Summary |
void _exit(int status); |
|||
Return |
Success |
Failure |
Sets errno |
|
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:
Programs and Processes
Processing Environment
Using Processes
Primitive Communications
Pipes
Message Queues
Semaphores
Shared Memory
Remote Procedure Calls
Sockets
Threads
Appendix A. Using Linux Manual Pages
Appendix B. UNIX Error Messages
Appendix C. RPC Syntax Diagrams
Appendix D. Profiling Programs