EmbLogic's Blog

Why is Sigaction() a better option than Signal()

First of all lets see what a signal is. A signal is a software interrupt sent by the kernel to a foreground process to report an exceptional situation or report a case where the process is trying to access an invalid memory location ( e.g. a segmentation fault is reported after SIGSEGV signal is generated when we try to access an invalid location) or any other asynchronous event.

There are a total of 64 signals defined in Linux. These have been divided into various sections :

  1. Program error signals
  2. Termination signals
  3. Alarm signals
  4. Asynchronous I/O signals
  5. Job control signals
  6. Operation error signals
  7. Miscellaneous signals

All these are defined in header file signal.h.

When generated these signals would execute there default behavior. But we can change this by redefining what the signal should do when its generated. This new definition is called a handler.

NOTE : SIGKILL and SIGSTOP can’t be caught or handled.

The signal system call – int signal(int signum, sighandler_t handler) is used to register a signal handler which would be invoked when the signum signal is generated.

Using signal() system call has some major drawbacks, of which the most significant is that its behavior varies across different versions of UNIX and Linux. Others include:

  • Undefined behavior if the signal handler is already running because signals are not blocked while the current handler is executing.
  • We can’t pass arguments to the handler.
  • No information about the origins of the signal.
  • The signal function generally resets back to its default behavior, which is more often then not termination of the current process. Now, suppose a signal is generated and between the time the signal is generated and the handler  re-installs its definition, another instance of the signal occurs. In this case the default behavior which is generally termination of process, would happen.

So, I think its safe to say that signal() is a fairly primitive and unsafe way to handle incoming signals.

Contrary to signal(), sigaction() provides a vast variety of options while handling signals but, with added complexity.

This is the prototype for sigaction() system call – int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

The 1st argument takes the signal which is to be handled, the 3rd argument is used to define the previous behavior of the signal. The 2nd argument is a pointer to sigaction which is a kernel data structure its defined as:

struct sigaction {
void     (*sa_handler)(int);
void     (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t   sa_mask;
int        sa_flags;
void     (*sa_restorer)(void);
};
– sa_handler specifies the action to be associated with signum. This function receives  the  signal no. as its only argument.

– Parameters in sa_sigaction i.e void* is used to send arguments to the handler and siginfo_t is a structure which gives information about the whereabouts of the signal and other critical information. Its defined as:

siginfo_t {
int      si_signo;    /* Signal number */
int      si_errno;    /* An errno value */
int      si_code;     /* Signal code */
int      si_trapno;   /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t    si_pid;      /* Sending process ID */
uid_t    si_uid;      /* Real user ID of sending process */
int      si_status;   /* Exit value or signal */
clock_t  si_utime;    /* User time consumed */
clock_t  si_stime;    /* System time consumed */
sigval_t si_value;    /* Signal value */
int      si_int;      /* POSIX.1b signal */
void    *si_ptr;      /* POSIX.1b signal */
int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
int      si_timerid;  /* Timer ID; POSIX.1b timers */
void    *si_addr;     /* Memory location which caused fault */
long     si_band;     /* Band event (was int in
glibc 2.3.2 and earlier) */
int      si_fd;       /* File descriptor */
short    si_addr_lsb; /* Least significant bit of address
(since kernel 2.6.32) */
}

– sigset_t sa_mask is a set of signals to be blocked during execution of the signal handler.   This is called signal masking. The blocked signals are sent to the sigpending queue, sigpending is a kernel structure defined in signal.h which is used to queue all the signals raised in the kernel space and are blocked in the user space.

– sa_flags specify a set of flags which modify the behavior of the signal. For e.g. if SA_SIGINFO then sa_sigaction specifies the signal handling function for signum not sa_handler. This is used when we want to define our own handler. List of all the flags is :

  • SA_NOCLDSTOP
  • SA_NOCLDWAIT
  • SA_NODEFER
  • SA_ONSTACK
  • SA_RESETHAND
  • SA_RESTART
  • SA_SIGINFO

– sa_restorer should not be by the user, its used internally by the Linux kernel while handling a pending unblocked signal.

Its quiet obvious that sigaction() allows more precise and safe handling of signals along with portability and takes care of the anomalies or loop holes created while using signal() system call.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>