Sometimes, an application needs to automatically perform some operations on process termination. Consider the example of an application library that, if used during the life of the process, needs to have some cleanup actions performed automatically when the process exits. Since the library doesn’t have control of when and how the process exits, and can’t mandate that the main program call a library-specific cleanup function before exiting, cleanup is not guaranteed to occur. One approach in such situations is to use an exit handler (older System V manuals used the term program termination routine).
An exit handler is a programmer-supplied function that is registered at some point during the life of the process and is then automatically called during normal process termination via exit(). Exit handlers are not called if a program calls _exit() directly or if the process is terminated abnormally by a signal.
To some extent, the fact that exit handlers are not called when a process is terminated by a signal limits their utility. The best we can do is to establish handlers for the signals that might be sent to the process, and have these handlers set a flag that causes the main program to call exit(). (Because exit() is not one of the async-signal-safe functions listed in Table 21-1, in Use of errno inside signal handlers, we generally can’t call it from a signal handler.) Even then, this doesn’t handle the case of SIGKILL
, whose default action can’t be changed. This is one more reason we should avoid using SIGKILL
to terminate a process (as noted in Signal Types and Default Actions), and instead use SIGTERM
, which is the default signal sent by the kill command.
The GNU C library provides two ways of registering exit handlers. The first method, specified in SUSv3, is to use the atexit() function.
#include <stdlib.h>
int atexit
(void (*func)(void));
Returns 0 on success, or nonzero on error
The atexit() function adds func to a list of functions that are called when the process terminates. The function func should be defined to take no arguments and return no value, thus having the following general form:
void func(void) { /* Perform some actions */ }
Note that atexit() returns a nonzero value (not necessarily -1) on error.
It is possible to register multiple exit handlers (and even the same exit handler multiple times). When the program invokes exit(), these functions are called in reverse order of registration. This ordering is logical because, typically, functions that are registered earlier are those that carry out more fundamental types of cleanups that may need to be performed after later-registered functions.
Essentially, any desired action can be performed inside an exit handler, including registering additional exit handlers, which are placed at the head of the list of exit handlers that remain to be called. However, if one of the exit handlers fails to return—either because it called _exit() or because the process was terminated by a signal (e.g., the exit handler called raise())—then the remaining exit handlers are not called. In addition, the remaining actions that would normally be performed by exit() (i.e., flushing stdio buffers) are not performed.
SUSv3 states that if an exit handler itself calls exit(), the results are undefined. On Linux, the remaining exit handlers are invoked as normal. However, on some systems, this causes all of the exit handlers to once more be invoked, which can result in an infinite recursion (until a stack overflow kills the process). Portable applications should avoid calling exit() inside an exit handler.
SUSv3 requires that an implementation allow a process to be able to register at least 32 exit handlers. Using the call sysconf(_SC_ATEXIT_MAX), a program can determine the implementation-defined upper limit on the number of exit handlers that can be registered. (However, there is no way to find out how many exit handlers have already been registered.) By chaining the registered exit handlers in a dynamically allocated linked list, glibc allows a virtually unlimited number of exit handlers to be registered. On Linux, sysconf(_SC_ATEXIT_MAX) returns 2,147,482,647 (i.e., the maximum signed 32-bit integer). In other words, something else will break (e.g., lack of memory) before we reach the limit on the number of functions that can be registered.
A child process created via fork() inherits a copy of its parent’s exit handler registrations. When a process performs an exec(), all exit handler registrations are removed. (This is necessarily so, since an exec() replaces the code of the exit handlers along with the rest of the existing program code.)
We can’t deregister an exit handler that has been registered with atexit() (or on_exit(), described below). However, we can have the exit handler check whether a global flag is set before it performs its actions, and disable the exit handler by clearing the flag.
Exit handlers registered with atexit() suffer a couple of limitations. The first is that when called, an exit handler doesn’t know what status was passed to exit(). Occasionally, knowing the status could be useful; for example, we may like to perform different actions depending on whether the process is exiting successfully or unsuccessfully. The second limitation is that we can’t specify an argument to the exit handler when it is called. Such a facility could be useful to define an exit handler that performs different actions depending on its argument, or to register a function multiple times, each time with a different argument.
To address these limitations, glibc provides a (nonstandard) alternative method of registering exit handlers: on_exit().
#define _BSD_SOURCE /* Or: #define _SVID_SOURCE */
#include <stdlib.h>
int on_exit
(void (*func)(int, void *), void *arg);
Returns 0 on success, or nonzero on error
The func argument of on_exit() is a pointer to a function of the following type:
void func(int status, void *arg) { /* Perform cleanup actions */ }
When called, func() is passed two arguments: the status argument supplied to exit(), and a copy of the arg argument supplied to on_exit() at the time the function was registered. Although defined as a pointer type, arg is open to programmer-defined interpretation. It could be used as a pointer to some structure; equally, through judicious use of casting, it could be treated as an integer or other scalar type.
Like atexit(), on_exit() returns a nonzero value (not necessarily -1) on error.
As with atexit(), multiple exit handlers can be registered with on_exit(). Functions registered using atexit() and on_exit() are placed on the same list. If both methods are used in the same program, then the exit handlers are called in reverse order of their registration using the two methods.
Although more flexible than atexit(), on_exit() should be avoided in programs intended to be portable, since it is not covered by any standards and is available on few other UNIX implementations.
Example 25-1 demonstrates the use of atexit() and on_exit() to register exit handlers. When we run this program, we see the following output:
$ ./exit_handlers
on_exit function called: status=2, arg=20
atexit function 2 called
atexit function 1 called
on_exit function called: status=2, arg=10
Example 25-1. Using exit handlers
procexec/exit_handlers.c
#define _BSD_SOURCE /* Get on_exit() declaration from <stdlib.h> */ #include <stdlib.h> #include "tlpi_hdr.h" static void atexitFunc1(void) { printf("atexit function 1 called\n"); } static void atexitFunc2(void) { printf("atexit function 2 called\n"); } static void onexitFunc(int exitStatus, void *arg) { printf("on_exit function called: status=%d, arg=%ld\n", exitStatus, (long) arg); } int main(int argc, char *argv[]) { if (on_exit(onexitFunc, (void *) 10) != 0) fatal("on_exit 1"); if (atexit(atexitFunc1) != 0) fatal("atexit 1"); if (atexit(atexitFunc2) != 0) fatal("atexit 2"); if (on_exit(onexitFunc, (void *) 20) != 0) fatal("on_exit 2"); exit(2); }procexec/exit_handlers.c