Calling Functions from a Signal Handler

The signal is an asynchronous event. Consequently, a signal such as SIGINT can arrive while your program is in the middle of executing a call to malloc(3), sprintf(3), or your wn code. This creates some program integrity issues that you will need to plan for.

If malloc(3) is being executed, linked lists of free memory areas may be only partially updated when the signal arrives. Thus, when the signal handler is executing, the memory heap is in an unstable state. If the signal handler were itself to call upon malloc(3), it is likely that data corruption or a program fault would follow. The function malloc(3) cannot tolerate this sequence of events, because it is not designed to be re-entrant code.

One characteristic of re-entrant code is that it does not save any state information within itself in static or global areas. Instead, the caller in an argument list provides all data items. Contrast this to the function malloc(3), which relies on a global heap, with global state data.

The asynchronous nature of signals is such that you must call only re-entrant functions from within your signal handler. Otherwise, you may end up spending many hours removing the occasional bug that shows up.

The following are the POSIX.1 standard re-entrant functions. The entries marked with an asterisk are not listed in the POSIX.1 standard, but were listed as re-entrant by the AT&T SVID standard. Check these with your local documentation before they are used in a signal handler.

_exit fork read tcdrain
abort* fstat rename tcflow
access getegid rmdir tcflush
alarm geteuid setgid tcgetattr
cfgetispeed getgid setpgid tcgetpgrp
cfgetospeed getgroups setsid tcsendbreak
cfsetispeed getpgrp setuid tcsetattr
cfsetospeed getpid sigaction tcsetpgrp
chdir getppid segaddset time
chmod getuid segdelset times
chown kill sigemptyset umask
chroot* link sigfillset uname
close longjmp sigismember unlink
creat lseek signal* ustat*
dup mkdir sigpending utime
dup2 mkfifo sigprocmask wait
execle open sigsuspend waitpid
execve pathconf sleep write
exit* pause stat  
fcntl pipe sysconf  

Avoiding Re-entrant Code Issues

The reliable signal interface permits you to control when certain signals are raised. This can be used to your advantage when a signal handler must call functions that are not re-entrant. This method is applied in the following steps:

  1. Block the signal of interest using sigprocmask(2).

  2. At certain points within your application, test if the signal is pending using sigpending(2).

  3. Call sigsuspend(2) at a safe point to allow the signal to be raised.

By calling sigsuspend(2) at a controlled point in your application, you eliminate the fact that functions such as malloc(3) were executing at the time of the signal. This procedure ensures that it is safe to call upon functions that are not re-entrant.

Re-entrancy Issues with errno in a Signal Handler

Technically, many of the functions listed previously are not purely re-entrant. Many have the capability to modify the value of the global external variable errno. Consequently, you must be careful to preserve errno within a signal handler.

Warning

Many re-entrant functions are capable of modifying the external variable errno. To maintain pure re-entrancy, be sure to save and restore errno in the signal handler.

A failure to observe this rule can lead to some obscure and difficult-to-diagnose bugs.


The signal-catching function code found in Listing 15.2 is repeated here, as follows:

void
handler(int signo) {

    ++count;                    /* Increment count */
    write(1,"Got SIGINT
",11); /* Write message */
}

This function is not purely re-entrant, because the errno value could be disturbed by the call to write(2). This is easily corrected by inserting a save and restore statement:

void
handler(int signo) {
    int e = errno;              /* Save errno */

    ++count;                    /* Increment count */
    write(1,"Got SIGINT
",11); /* Write message */
    errno = e;                  /* Restore errno */
}

Saving and restoring errno prevents the application from seeing a changed errno value when the signal handler returns. This type of problem can be extremely difficult to debug, because it will often depend upon timing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.22.135