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 :
- Program error signals
- Termination signals
- Alarm signals
- Asynchronous I/O signals
- Job control signals
- Operation error signals
- 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.