X

Solaris serviceability and nifty tools

signal() versus sigaction() on Solaris

This entry is mostly for newcomers to Solaris/OpenSolaris from UNIX-like systems. When I had been taught
about signal() and sigaction() my understanding was that sigaction() is just a
superset of signal() and also POSIX conformant but otherwise they accomplish the same thing.
This is indeed the case for some of UNIX-like operating systems. In Solaris, as I only recently discovered
(to my dismay :)), it's different.

Consider the following code (please ignore the fact it's not strictly checking return values and
that the signal handler is not safe):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
void sig_handler(int s) {

printf("Got signal! Sleeping.\\n");

sleep(10);
printf("returning from signal handler\\n");
}
int main(void) {
struct sigaction s_action;

printf("Setting signal handler: ");
#ifdef POSIX_SIGNALS

printf("sigaction\\n");

(void) sigemptyset(&s_action.sa_mask);

s_action.sa_handler = sig_handler;
s_action.sa_flags = 0;

(void) sigaction(SIGHUP, &s_action, (struct sigaction \*) NULL);
#else

printf("signal\\n");

signal(SIGHUP, sig_handler);
#endif

printf("Waiting for signal\\n");

while(1)

pause();

return (0);
}

Now try to compile and run with and without the -DPOSIX_SIGNALS and send 2 SIGHUP signals to the process
within the 10 seconds window (so the second signal is received while the signal handler is still running).
With sigaction(), the signal will be caught by the handler in both of the cases.
With signal() however, the second signal will cause the process to exit. This is because kernel
will reset the signal handler to default upon receiving the signal for the first time. This is described in the signal(3C)
man page in a somewhat hidden sentence inside the second paragraph (it really pays out to read man pages slowly and
with attention to detail):

     If signal()  is
used, disp is the address of a signal handler, and sig is
not SIGILL, SIGTRAP, or SIGPWR, the system first sets the
signal's disposition to SIG_DFL before executing the signal
handler.

The sigaction(2) man page has this section:

     SA_RESETHAND    If set and the signal is caught, the  dispo-
sition of the signal is reset to SIG_DFL and
the signal will not be blocked on entry to
the signal handler (SIGILL, SIGTRAP, and
SIGPWR cannot be automatically reset when
delivered; the system silently enforces this
restriction).

sigaction() does not set the flag by default which results in the different behavior.
I found out that this behavior has been present since Solaris 2.0 or so.

In fact, signal() routine from libc is implemented via sigaction(). From
$SRC/lib/libc/port/sys/signal.c:

     58 /\*
59 \* SVr3.x signal compatibility routines. They are now
60 \* implemented as library routines instead of system
61 \* calls.
62 \*/
63
64 void(\*
65 signal(int sig, void(\*func)(int)))(int)
66 {
67

struct sigaction nact;
68

struct sigaction oact;
69
70

CHECK_SIG(sig, SIG_ERR);
71
72

nact.sa_handler = func;
73

nact.sa_flags = SA_RESETHAND|SA_NODEFER;
74

(void) sigemptyset(&nact.sa_mask);
75
76

/\*
77

\* Pay special attention if sig is SIGCHLD and
78

\* the disposition is SIG_IGN, per sysV signal man page.
79

\*/
80

if (sig == SIGCHLD) {
81

nact.sa_flags |= SA_NOCLDSTOP;
82

if (func == SIG_IGN)
83

nact.sa_flags |= SA_NOCLDWAIT;
84

}
85
86

if (STOPDEFAULT(sig))
87

nact.sa_flags |= SA_RESTART;
88
89

if (sigaction(sig, &nact, &oact) < 0)
90

return (SIG_ERR);
91
92

return (oact.sa_handler);
93 }

I am pretty sure that the SA_RESETHAND flag is set in signal() in order to preserve backwards compatibility.

This means that to solve this problem with signal(), one should set the signal handler again in the signal handler itself.
However, this is not a complete solution since there is still a window where the signal can be delivered and
the handler is set to SIG_DFL - the default handler which is exit in case of SIGHUP as
the signal.h(3HEAD) man page explains in really useful table:

          Name        Value   Default                    Event
SIGHUP 1 Exit Hangup (see termio(7I))
...

Now let's look at FreeBSD. Its SIGNAL(3) man page contains this separate paragraph:

     The handled signal is unblocked when the function returns and the process
continues from where it left off when the signal occurred. Unlike previ-
ous signal facilities, the handler func() remains installed after a sig-
nal has been delivered.

The second sentence is actually printed in bold letters. I also tried on Linux and NetBSD and
the behavior is the same as in FreeBSD.

So, to conclude all of the above: using signal() is really not portable.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.