Signals |
^Z
,
^C
, or
kill
are typed)
or from processes (kill(2)
).
Signals have almost no info content. You can't usually
find out who sent the signal, so don't use them as a messaging
system.
Signal handling has evolved on UNIX.
Some UNIX's
(including SVR4) let you block them, hold them, find out which
PID and UID sent them, or determine why they were sent.
Unreliable signal handling was the only kind
available on older systems and some functions such as
signal(3C)
may provide
only unreliable signals. Signals could not be queued, blocked, or if
blockable then another signal could slip through. When the only
purpose for handling a signal is to clean up before terminating
this is not quite so bad. Many textbooks discuss only
unreliable signals and have flawed code.
Types of signals |
signal(5)
or
kill -l
for a list.
Interesting ones for us are:
SIGHUP
(hangup),
SIGINT
(^C
),
SIGQUIT
(^
),
SIGFPE
(floating point exceptions such as division by 0),
SIGKILL
(desperate kill: uncatchable or ignorable),
SIGTERM
(graceful kill: generate by the shell's kill
command),
SIGTSTP
(^Z
),
SIGALRM
(timer alarm),
SIGWINCH
(window change).
Signal delivery and actions |
There are three actions that a process can take on a signal.
The signal can be default processed (SIG_DFL
),
ignored (SIG_IGN
) or caught.
The default action (SIG_DFL
) on receiving most signals is termination
of the process.
Signals could instead be ignored (SIG_IGN
), but this is
reasonable for only certain signals and even then not always.
A signal is caught if a signal handler function has been installed.
The action in this case is to call the signal handler. This
is fraught with problems, mostly because signals can arrive
at awkward times and race conditions may occur.
What should a signal handler do? |
longjmp(3C)
or
better siglongjmp(3C)
to jump
back to a previous execution point.
siglongjmp
is essentially
a non-local go to; it unwinds the stack.
At some point in the program call sigsetjmp
,
which marks the current point of execution and stores
the signal mask. When siglongjmp
is called,
processing returns to the sigsetjmp
point.
There are several restrictions,
including that you cannot siglongjmp
to a sigsetjmp
point
once the function containing the sigsetjmp
has returned, so change
the signal handler before returning from the function.
All register and non-volatile auto variables at the
sigsetjmp
point have undefined values
when siglongjmp
is taken.
Suppose a signal arrives in the middle of a malloc
.
This is very awkward because
chaos will result if the signal handler also calls malloc
.
So it is unsafe to use malloc
in a handler. The same applies to
any other library functions that uses static data structures or that calls
malloc
. This includes
all stdio functions and many other library functions.
A further problem is that if processing is to be resumed, then
the malloc
must be resumed,
so siglongjmp
should not be used
in the handler for this signal. Exiting or setting a flag is
the only reasonable solution.
If the handler returns, processing resumes at the point of interruption.
Depending on the signal this can be undesirable. For example, a return
in connection with SIGFPE
will lead to
an immediate second SIGFPE
unless you patch up the problem. Patching is very difficult but even if
possible, will require some dirty machine dependent method. Rather than
returning from the handler,
a siglongjmp
or
exit
is more reasonable in this case.
Generating Signals |
trap
for
altering how to handling them. Consider using it instead of writing C code.
^Z
is used in interactive shells to suspend a process
for possible later restart.
A user generated ^Z
is turned into a SIGTSTP
signal by the terminal driver.
Similarly for other keyboard generated signals.
The shell kill
raises a signal by making the system call
kill(2)
.
A process can generate signals in several ways. Of course it can
unintentional generate them by attempting illegal hardware operations,
such as by dividing by zero, or by attempting to execute malformed
machine instructions. A process can generate signals intentional
with:
kill(2)
,
raise(3C)
(sends the signal to itself),
alarm(2)
(which starts a timer).
Related to these is
pause(2)
or better yet
sigsuspend(2)
which suspends a process until the next signal is delivered.
Note: Making a process ``sleep'' on a multitasking system by
using a time consuming loop is very poor manners. Instead use an
alarm
and
sigsuspend
or
sleep(1)
or
sleep(3C)
which in turn use
alarm
and
pause
.
Signal.h |
#include <signal.h>
which defines
SIG_DFL
,
SIG_IGN
,
the signals SIGHUP
, etc., and
declares the various signal functions.
The basic but unreliable signal function is signal(3C)
.
Suppose we define:
typedef void (*HANDLER) (int);
signal( )
is a function that replaces the old handler
with a new handler:
old_handler = HANDLER signal(int signo, HANDLER new_handler);
SIG_DFL
or
SIG_IGN
.
signal
returns the old handler, for possible reuse,
or SIG_ERR
if there was an error.
One of the problems with signal
is that it is not possible
to determine what the signal handler is without replacing it.
Typically it is used as follows:
if (signal(signo,SIG_IGN) != SIG_IGN) oldhandler = signal(signo, handler);
handler
is a function defined elsewhere.
The test for SIG_IGN
is typical: if an application was initially ignoring a signal then usually
it should continue to be ignored. This is especially true of SIGTERM
and SIGINT
signals, because the shell will set a background job to
ignore these so that ^C
will only kill the foreground job.
If you un-ignore them, then when the process is backgrounded it will
catch signals that are intended only for the foreground job.
Signals and Exec |
exec
, a process
does not have access to previous code, so it cannot
inherit the signal handlers. For this reason after an exec
,
all signals are reset to SIG_DFL
,
except SIG_IGN
ones remain
SIG_IGN
.
Of course, the new process can define its own signal handlers.
Signals are especially hard to handle in system calls.
Calls that involve waits such as
open(2)
,
read(2)
,
and write(2)
on slow devices (terminal and pipes but not disks)
that could block for long periods,
if interrupted will return with the error
EINTR
and are not restarted
by default (but modern UNIX's can transparently restart them).
Your program needs to restart them in this case.
A not completely satisfactory alternative is to block signals while doing
these, however, your program becomes temporarily uninterruptable!
This affects stdio functions like getchar( )
since EOF is returned
even though a ``real'' EOF has not yet been seen;
set a flag in the handler
and then test this flag to see if the EOF was caused by a signal.
For a related example, consider:
system("ls > files_in_dir");
/* commands to process files_in_dir follow */
system( )
is not an ordinary function.
It involves running another
program as a child process and a wait is involved.
A ^C
while the
system( )
is running will error out
system( )
and processing will resume with the lines
below if ^C
is caught.
In this case files_in_dir
is probably corrupt or
incomplete, so the system( )
should be done over.
Sigaction |
signal(3C)
is the
reliable sigaction(2)
.
The syntax and steps required are more involved.
As a first step,
sigset_t sset
declares a
signal set (mask)
called sset
. Use
sigemptyset(&sset)
or
sigfillset(&sset)
to exclude all or include all signals in the set.
Then include or exclude each desired signal, signo
, one
at a time with
sigaddset(&sset, signo)
or
sigdelset(&sset, signo)
.
To test if signo
is
in a set use: sigismember(&sset, signo)
.
The signal mask for a process is altered with
sigprocmask(how, &sset, &oldsset)
which can be used to
change which signals are blocked.
how
determines
the interpretation of sset
.
A SIG_BLOCK
or SIG_UNBLOCK
indicates that sset
specifies additional signals to
block or unblock. If how
is SIG_SETMASK
then
sset
is the new mask.
If &oldsset
is not NULL
then the old mask is returned through it. If &sset
is NULL
then how
is not used and no change is made to the current mask.
sigpending(&sset)
tells which signals are pending.
To test if SIGINT
is pending:
sigpending(&sset);
if(sigismember(&sset,SIGINT)) printf("SIGINT pending");
In the next example, ^C
is disabled in a critical region
and ^C
is tested to see if it is pending.
#include <signal.h>
void fatal(void) { /* signal error */ exit(21); }
int main() {
sigset_t sset,oldsset,pendingset;
sigemptyset(&sset); /* better: sigprocmask(SIG_BLOCK,NULL,&sset); */
sigaddset(&sset,SIGINT);
if (-1 == sigprocmask(SIG_BLOCK,&sset,&oldsset)) fatal();
/* signal now blocked */
printf("Entering pretend critical region\n");
sleep(5);
sigpending(&pendingset); /* test if signal is pending */
if (sigismember(&pendingset,SIGINT)) printf("SIGINT pending\n");
printf("Leaving critical region\n");
/* restore previous set */
if (-1 == sigprocmask(SIG_SETMASK,&oldsset,NULL)) fatal();
/* WRONG: sigprocmask(SIG_UNBLOCK,&sset,NULL) */
/* [Should not unblock SIGINT if it was initially blocked] */
return printf("Bye - you never pressed ^C\n") , 0;
}
sigaction(2)
uses a
three member structure sigaction
.
struct sigaction act, oldact;
sigaction(signo, &act, &oldact);
act.sa_handler
is the name of the signal handler,
SIG_DFL
or SIG_IGN
,
act.sa_mask
is a set of signals (in addition to previously
blocked ones and
signo
) to block while in the signal handler
and act.sa_flags
are flags.
Flag SA_RESTART
indicates that
interrupted system calls should be automatically restarted.
For SIGCHILD
,
flags SA_NOCLDWAIT
means this process
will generate no zombies, while
SA_NOCLDSTOP
means
do not generate SIGCHILD
if child is stopped
by a job control signal.
If &oldact
is not NULL then the old sigaction is stored there.
If &act
is not NULL then
act
specifies the new action.
Finally, sigsuspend(sset)
makes
sset
the signal mask and
then sleeps the process until a signal arrives.
It is like a sigprocmask
followed by a pause
but avoids the race condition.
For more details and examples see the excellent book Advanced Programming in the UNIX Environment by W. Richard Stevens, Addison Wesley.
Last update: 2001 April 28