10.6. Reentrant Functions
When a signal that is being caught is handled by a process, the normal sequence of instructions being executed by the process is temporarily interrupted by the signal handler. The process then continues executing, but the instructions in the signal handler are now executed. If the signal handler returns (instead of calling exit or longjmp, for example), then the normal sequence of instructions that the process was executing when the signal was caught continues executing. (This is similar to what happens when a hardware interrupt occurs.) But in the signal handler, we can't tell where the process was executing when the signal was caught. What if the process was in the middle of allocating additional memory on its heap using malloc, and we call malloc from the signal handler? Or, what if the process was in the middle of a call to a function, such as getpwnam (Section 6.2), that stores its result in a static location, and we call the same function from the signal handler? In the malloc example, havoc can result for the process, since malloc usually maintains a linked list of all its allocated areas, and it may have been in the middle of changing this list. In the case of getpwnam, the information returned to the normal caller can get overwritten with the information returned to the signal handler.
The Single UNIX Specification specifies the functions that are guaranteed to be reentrant. Figure 10.4 lists these reentrant functions.
Figure 10.4. Reentrant functions that may be called from a signal handler
accept
fchmod
lseek
sendto
stat
access
fchown
lstat
setgid
symlink
aio_error
fcntl
mkdir
setpgid
sysconf
aio_return
fdatasync
mkfifo
setsid
tcdrain
aio_suspend
fork
open
setsockopt
tcflow
alarm
fpathconf
pathconf
setuid
tcflush
bind
fstat
pause
shutdown
tcgetattr
cfgetispeed
fsync
pipe
sigaction
tcgetpgrp
cfgetospeed
ftruncate
poll
sigaddset
tcsendbreak
cfsetispeed
getegid
posix_trace_event
sigdelset
tcsetattr
cfsetospeed
geteuid
pselect
sigemptyset
tcsetpgrp
chdir
getgid
raise
sigfillset
time
chmod
getgroups
read
sigismember
timer_getoverrun
chown
getpeername
readlink
signal
timer_gettime
clock_gettime
getpgrp
recv
sigpause
timer_settime
close
getpid
recvfrom
sigpending
times
connect
getppid
recvmsg
sigprocmask
umask
creat
getsockname
rename
sigqueue
uname
dup
getsockopt
rmdir
sigset
unlink
dup2
getuid
select
sigsuspend
utime
execle
kill
sem_post
sleep
wait
execve
link
send
socket
waitpid
_Exit & _exit
listen
sendmsg
socketpair
write
Most functions that are not in Figure 10.4 are missing because (a) they are known to use static data structures, (b) they call malloc or free, or (c) they are part of the standard I/O library. Most implementations of the standard I/O library use global data structures in a nonreentrant way. Note that even though we call printf from signal handlers in some of our examples, it is not guaranteed to produce the expected results, since the signal hander can interrupt a call to printf from our main program.
Be aware that even if we call a function listed in Figure 10.4 from a signal handler, there is only one errno variable per thread (recall the discussion of errno and threads in Section 1.7), and we might modify its value. Consider a signal handler that is invoked right after main has set errno. If the signal handler calls read, for example, this call can change the value of errno, wiping out the value that was just stored in main. Therefore, as a general rule, when calling the functions listed in Figure 10.4 from a signal handler, we should save and restore errno. (Be aware that a commonly caught signal is SIGCHLD, and its signal handler usually calls one of the wait functions. All the wait functions can change errno.)
Note that longjmp (Section 7.10) and siglongjmp (Section 10.15) are missing from Figure 10.4, because the signal may have occurred while the main routine was updating a data structure in a nonreentrant way. This data structure could be left half updated if we call siglongjmp instead of returning from the signal handler. If it is going to do such things as update global data structures, as we describe here, while catching signals that cause sigsetjmp to be executed, an application needs to block the signals while updating the data structures.
Example
Figure 10.5 shows a program that calls the nonreentrant function getpwnam from a signal handler that is called every second. We describe the alarm function in Section 10.10. We use it here to generate a SIGALRM signal every second.
When this program was run, the results were random. Usually, the program would be terminated by a SIGSEGV signal when the signal handler returned after several iterations. An examination of the core file showed that the main function had called getpwnam, but that some internal pointers had been corrupted when the signal handler called the same function. Occasionally, the program would run for several seconds before crashing with a SIGSEGV error. When the main function did run correctly after the signal had been caught, the return value was sometimes corrupted and sometimes fine. Once (on Mac OS X), messages were printed from the malloc library routine warning about freeing pointers not allocated through malloc.
As shown by this example, if we call a nonreentrant function from a signal handler, the results are unpredictable.
Figure 10.5. Call a nonreentrant function from a signal handler #include "apue.h"
#include
static void
my_alarm(int signo)
{
struct passwd *rootptr;
printf("in signal handler/n");
if ((rootptr = getpwnam("root")) == NULL)
err_sys("getpwnam(root) error");
alarm(1);
}
int
main(void)
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for ( ; ; ) {
if ((ptr = getpwnam("sar")) == NULL)
err_sys("getpwnam error");
if (strcmp(ptr->pw_name, "sar") != 0)
printf("return value corrupted!, pw_name = %s/n",
ptr->pw_name);
}
}