Friday, December 4, 2009

8.6 Handling Signals: Errors and Async-signal Safety



[ Team LiB ]






8.6 Handling Signals: Errors and Async-signal Safety


Be aware of three difficulties that can occur when signals interact with function calls. The first concerns whether POSIX functions that are interrupted by signals should be restarted. Another problem occurs when signal handlers call nonreentrant functions. A third problem involves the handling of errors that use errno.


What happens when a process catches a signal while it is executing a library function? The answer depends on the type of call. Terminal I/O can block the process for an undetermined length of time. There is no limit on how long it takes to get a key value from a keyboard or to read from a pipe. Function calls that perform such operations are sometimes characterized as "slow". Other operations, such as disk I/O, can block for short periods of time. Still others, such as getpid, do not block at all. Neither of these last types is considered to be "slow".


The slow POSIX calls are the ones that are interrupted by signals. They return when a signal is caught and the signal handler returns. The interrupted function returns �1 with errno set to EINTR. Look in the ERRORS section of the man page to see if a given function can be interrupted by a signal. If a function sets errno and one of the possible values is EINTR, the function can be interrupted. The program must handle this error explicitly and restart the system call if desired. It is not always possible to logically determine which functions fit into this category, so be sure to check the man page.


It was originally thought that the operating system needs to interrupt slow calls to allow-the user the option of canceling a blocked call. This traditional treatment of handling blocked functions has been found to add unneeded complexity to many programs. The POSIX committee decided that new functions (such as those in the POSIX threads extension) would never set errno to EINTR. However, the behavior of traditional functions such as read and write was not changed. Appendix B gives a restart library of wrappers that restart common interruptible functions such as read and write.


Recall that a function is async-signal safe if it can be safely called from within a signal handler. Many POSIX library functions are not async-signal safe because they use static data structures, call malloc or free, or use global data structures in a nonreentrant way. Consequently, a single process might not correctly execute concurrent calls to these functions.


Normally this is not a problem, but signals add concurrency to a program. Since signals occur asynchronously, a process may catch a signal while it is executing a library function. (For example, suppose the program interrupts a strtok call and executes another strtok in the signal handler. What happens when the first call resumes?) You must therefore be careful when calling library functions from inside signal handlers. Table 8.2 lists the functions that POSIX guarantees are safe to call from a signal handler. Notice that functions such as fprintf from the C standard I/O library are not on the list.


Signal handlers can be entered asynchronously, that is, at any time. Care must be taken so that they do not interfere with error handling in the rest of the program. Suppose a function reports an error by returning -1 and setting errno. What happens if a signal is caught before the error message is printed? If the signal handler calls a function that changes errno, an incorrect error might be reported. As a general rule, signal handlers should save and restore errno if they call functions that might change errno.



Example 8.28

The following function can be used as a signal handler. The myhandler saves the value of errno on entry and restores it on return.



void myhandler(int signo) {
int esaved;
esaved = errno;
write(STDOUT_FILENO, "Got a signal\n", 13);
errno = esaved;
}


Table 8.2. Functions that POSIX guarantees to be async-signal safe.

_Exit

getpid

sigaddset

_exit

getppid

sigdelset

accept

getsockname

sigemptyset

access

getsockopt

sigfillset

aio_error

getuid

sigismember

aio_return

kill

signal

aio_suspend

link

sigpause

alarm

listen

sigpending

bind

lseek

sigprocmask

cfgetispeed

lstat

sigqueue

cfgetospeed

mkdir

sigset

cfsetispeed

mkfifo

sigsuspend

cfsetospeed

open

sleep

chdir

pathconf

socket

chmod

pause

socketpair

chown

pipe

stat

clock_gettime

poll

symlink

close

posix_trace_event

sysconf

connect

pselect

tcdrain

creat

raise

tcflow

dup

read

tcflush

dup2

readlink

tcgetattr

execle

recv

tcgetpgrp

execve

recvfrom

tcsendbreak

fchmod

recvmsg

tcsetattr

fchown

rename

tcsetpgrp

fcntl

rmdir

time

fdatasync

select

timer_getoverrun

fork

sem_post

timer_gettime

fpathconf

send

timer_settime

fstat

sendmsg

times

fsync

sendto

umask

ftruncate

setgid

uname

getegid

setpgid

unlink

geteuid

setsid

utime

getgid

setsockopt

wait

getgroups

setuid

waitpid

getpeername

shutdown

write

getpgrp

sigaction

 


Signal handling is complicated, but here are a few useful rules.


  • When in doubt, explicitly restart library calls within a program or use the restart library of Appendix B.

  • Check each library function used in a signal handler to make sure that it is on the list of async-signal safe functions.

  • Carefully analyze the potential interactions between a signal handler that changes an external variable and other program code that accesses the variable. Block signals to prevent unwanted interactions.

  • Save and restore errno when appropriate.






    [ Team LiB ]



    No comments: