Handler functions usually don't do very much. The best practice is to
write a handler that does nothing but set an external variable that the
program checks regularly, and leave all serious work to the program.
This is best because the handler can be called asynchronously, at
unpredictable times—perhaps in the middle of a primitive function, or
even between the beginning and the end of a C operator that requires
multiple instructions. The data structures being manipulated might
therefore be in an inconsistent state when the handler function is
invoked. Even copying one int variable into another can take two
instructions on most machines.
This means you have to be very careful about what you do in a signal
handler.
If your handler needs to access any global variables from your program,
declare those variables volatile. This tells the compiler that
the value of the variable might change asynchronously, and inhibits
certain optimizations that would be invalidated by such modifications.
If you call a function in the handler, make sure it is reentrant
with respect to signals, or else make sure that the signal cannot
interrupt a call to a related function.
A function can be non-reentrant if it uses memory that is not on the
stack.
If a function uses a static variable or a global variable, or a
dynamically-allocated object that it finds for itself, then it is
non-reentrant and any two calls to the function can interfere.
For example, suppose that the signal handler uses gethostbyname.
This function returns its value in a static object, reusing the same
object each time. If the signal happens to arrive during a call to
gethostbyname, or even after one (while the program is still
using the value), it will clobber the value that the program asked for.
However, if the program does not use gethostbyname or any other
function that returns information in the same object, or if it always
blocks signals around each use, then you are safe.
There are a large number of library functions that return values in a
fixed object, always reusing the same object in this fashion, and all of
them cause the same problem. Function descriptions in this manual
always mention this behavior.
If a function uses and modifies an object that you supply, then it is
potentially non-reentrant; two calls can interfere if they use the same
object.
This case arises when you do I/O using streams. Suppose that the
signal handler prints a message with fprintf. Suppose that the
program was in the middle of an fprintf call using the same
stream when the signal was delivered. Both the signal handler's message
and the program's data could be corrupted, because both calls operate on
the same data structure—the stream itself.
However, if you know that the stream that the handler uses cannot
possibly be used by the program at a time when signals can arrive, then
you are safe. It is no problem if the program uses some other stream.
On most systems, malloc and free are not reentrant,
because they use a static data structure which records what memory
blocks are free. As a result, no library functions that allocate or
free memory are reentrant. This includes functions that allocate space
to store a result.
The best way to avoid the need to allocate memory in a handler is to
allocate in advance space for signal handlers to use.
The best way to avoid freeing memory in a handler is to flag or record
the objects to be freed, and have the program check from time to time
whether anything is waiting to be freed. But this must be done with
care, because placing an object on a chain is not atomic, and if it is
interrupted by another signal handler that does the same thing, you
could “lose” one of the objects.
Any function that modifies errno is non-reentrant, but you can
correct for this: in the handler, save the original value of
errno and restore it before returning normally. This prevents
errors that occur within the signal handler from being confused with
errors from system calls at the point the program is interrupted to run
the handler.
This technique is generally applicable; if you want to call in a handler
a function that modifies a particular object in memory, you can make
this safe by saving and restoring that object.
Merely reading from a memory object is safe provided that you can deal
with any of the values that might appear in the object at a time when
the signal can be delivered. Keep in mind that assignment to some data
types requires more than one instruction, which means that the handler
could run “in the middle of” an assignment to the variable if its type
is not atomic. See Atomic Data Access.
Merely writing into a memory object is safe as long as a sudden change
in the value, at any time when the handler might run, will not disturb
anything.
Published under the terms of the GNU General Public License