Signals are used to notify a process about some event. Signals are asynchornous events. The event condition may be for ex: packet received on a network interface, packet sent on a network interface, watchdog timer expiry, floating point exception, invalid memory address read / write (segfault, alignment fault) etc. The linux provides 64 different signals range from SIGHUP
to SIGRTMAX
. The normal signals range from SIGHUP
to SIGSYS
and the real time signals start from SIGRTMIN
to SIGRTMAX
.
The command kill -l
on the bash
would give us the following.
SIGHUP | SIGINT | SIGQUIT | SIGILL | SIGTRAP |
SIGABRT | SIGBUS | SIGFPE | SIGKILL | SIGUSR1 |
SIGSEGV | SIGUSR2 | SIGPIPE | SIGALRM | SIGTERM |
SIGSTKFLT | SIGCHLD | SIGCONT | SIGSTOP | SIGTSTP |
SIGTTIN | SIGTTOU | SIGURG | SIGXCPU | SIGXFSZ |
SIGVTARLM | SIGPROF | SIGWINCH | SIGIO | SIGPWR |
SIGSYS | SIGRTMIN | SIGRTMIN + 1 | SIGRTMIN + 2 | SIGRTMIN + 3 |
SIGRTMIN + 4 | SIGRTMIN + 5 | SIGRMIN + 6 | SIGRTMIN + 7 | SIGRTMIN + 8 |
SIGRTMIN + 9 | SIGRTMIN + 10 | SIGRTMIN + 11 | SIGRTMIN + 12 | SIGRTMIN + 13 |
SIGRTMIN + 14 | SIGRTMIN + 15 | SIGRTMAX - 14 | SIGRTMAX - 13 | SIGRTMAX - 12 |
SIGRTMAX - 11 | SIGRTMAX - 10 | SIGRTMAX - 9 | SIGRTMAX - 8 | SIGRTMAX - 6 |
SIGRTMAX - 6 | SIGRTMAX - 5 | SIGRTMAX - 4 | SIGRTMAX - 3 | SIGRTMAX - 2 |
SIGRTMAX - 1 | SIGRTMAX |
The SIGINT
and SIGQUIT
are familiar to us as from the keyboard as we usually perform the ctrl + c
(SIGINT
) or ctrl + \
(SIGQUIT
) combination to stop a program.
Signals are also delivered to a process (running or stopped or waiting) with the help of kill
command. The manual page (man kill
) of kill command says that the default and easier version of kill command is the kill pid
. Where pid
is the process ID that is found via the ps
command. The default signal is SIGTERM (15)
. Alternatively a signal number is specified to the kill
command such as kill -2 1291
making a delivery of SIGINT(2)
signal to the process ID 1291.
The linux also provide a mechanism for sysadmins to control the processes via two powerful and unmaskable signals SIGKILL
and SIGSTOP
. They are fatal and the program terminates as soon as it receives them. This allows admins to kill the offending or bad processes from hogging the resources.
The linux also provide us a set of system calls API to use the signals that are delivered to the process. Some of the most important API are sigaction
and signal
. Lately signalfd
is introduced that delivers the signals in a synchronous fashion to safely control the signals that occur at unknown or surprisingly.
handling of the signals:
- ignore the signal - SIG_IGN (ignore the signal by specifying this)
- perform the handler function execution - create a callback handler that gets called when signal occurs
- default action - is default for each process when created - kernel creates a default handler for each process
Signals are asynchornous and must be handled. So the system call interface provides an API to handle the signals. Below described are some of the system calls.
signal
is a system call, that allows the program to handle the signal when it occurs.
prototype:
sighandler_t signal(int signum, sighandler_t handler);
A very good stackoverflow question here tells us why sigaction
is better than signal
. The key differences are:
-
the
signal()
does not necessarily block other signals from arriving while the current signal is being executed. Thus when more than one signal occur at the same time, it becomes more problematic to understand and perform actions. If it is on the same data, this might even get more complex. Thesigaction()
blocks the other signals while the handler is being executed. -
As soon as the
signal()
handler is executed, thesignal()
sets the default handler toSIG_DFL
which may beSIGINT / SIGTERM / SIGQUIT
and the handler must reset the function back to itself so that when the signal occur again, the signal can be handled back. However, with the signal() allowing the handler to be called even though the handler is already executing, this will implicate a serious problem where in which the small window between the register and default would let the program go intoSIG_DFL
.
signalfd is a new system call introduced by the linux kernel. The signalfd system call returns a file descriptor associated with the signal. This file descriptor is then used to wait on the select, poll or epoll system calls. Unlike the signal
or sigaction
the signals are queued in the socket buffer and can be read on the socket.
The prototype of the signalfd
is as follows.
int signalfd(int fd, const sigset_t *mask, int flags);
To use the signalfd
one must include <sys/signalfd.h>
.
If the fd argument is -1, the signalfd
returns a new file descriptor. If fd argument is not -1, then the fd that is returned from previous calls to the signalfd
must be given as an argument.
The mask argument is similar to the one that we pass to the sigprocmask
system call. This allows the signalfd
to create an fd
out for the mask. As the mask is created, the signals in the mask should be blocked with the sigprocmask
. This allows the correct functionality of the signalfd
.
The flags
argument is usually set to 0. It is much similar to the O_NONBLOCK flag options of other system calls.
Include <sys/signalfd.h>
to use the signalfd
system call.
Below is an example of the signalfd
system call. Download here
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <string.h>
int main(int argc, char **argv)
{
int sfd;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGQUIT);
sigaddset(&mask, SIGINT);
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
printf("afiled to sigprocmask\n");
return -1;
}
sfd = signalfd(-1, &mask, 0);
if (sfd < 0) {
printf("failed to get signalfd\n");
return -1;
}
while (1) {
struct signalfd_siginfo sf;
int ret;
memset(&sf, 0, sizeof(sf));
ret = read(sfd, &sf, sizeof(sf));
if (ret != sizeof(sf)) {
printf("invalid length of siginfo %d received\n", ret);
return -1;
}
printf("pid %d signal value: %d signal code: %d\n",
sf.ssi_pid,
sf.ssi_signo,
sf.ssi_code);
if (sf.ssi_signo == SIGQUIT) {
printf("received termination signal\n");
} else if (sf.ssi_signo == SIGINT) {
printf("received interrupt\n");
} else {
printf("invalid signal %d\n", sf.ssi_signo);
}
}
close(sfd);
return 0;
}
The signals are first masked by the sigaddset
system call and then blocked with sigprocmask
. The mask is then given to the signalfd
system call. The signals are then queued to the socket fd returned. This can be waited upon the read
or select
system call.
The kernel returns a variable of the form signalfd_siginfo
upon read.The structure then contains the signal that is occured. Here's some contents in the signalfd_siginfo
.
struct signalfd_siginfo {
uint32_t ssi_signo;
...
uint32_t ssi_pid;
....
};
The ssi_signo
contains the signal number that is occured. The ssi_pid
is the process that sent this signal.
Example: signalfd basic example
sigaddset
adds the signal to a set. The prototype is as follows.
int sigaddset(sigset_t *set, int signo);
The signo
gets added to the set
. Multiple calls of the sigaddset
on the same set
would add the signals to the set. The function is mostly used in generating a signal mask for the sigprocmask
. See more about sigprocmask
in the below sections.
usually, the sigaddset
is called this way:
sigset_t set;
sigaddset(&set, SIGINT); // ignore SIGINT
ignores the signal SIGINT
.
To setup a group of signals, the sigaddset
can be allowed to call in a loop. For example,
int i = 0;
int signal_list[] = {SIGINT, SIGQUIT, SIGALRM};
sigset_t set;
// setup signal mask for the signals in signal_list
while (i < sizeof(signal_list) / sizeof(signal_list[0])) {
sigaddset(&set, signal_list[i]);
}
sigfillset
initializes the set
to full, including all the signals. The prototype is as follows.
int sigfillset(sigset_t *set);
sigemptyset
clears the signal mask. The initialization of the set of type sigset_t
is usually done with the sigemptyset
. Before any calls to sigaddset
the set of type sigset_t
must be cleared with sigemptyset
.
The above examples calls of sigaddset
must use sigemptyset
. So the fixed code examples look like the below,
sigset_t set;
sigemptyset(&set); // clear the signal set
sigaddset(&set, SIGINT); // ignore SIGINT
int i = 0;
int signal_list[] = {SIGINT, SIGQUIT, SIGALRM};
sigset_t set;
sigemptyset(&set); // clear the signal set
// setup signal mask for the signals in signal_list
while (i < sizeof(signal_list) / sizeof(signal_list[0])) {
sigaddset(&set, signal_list[i]);
}
sigismember
validates if the given signal is with in the set. Prototype is as follows,
int sigismember(sigset_t *set, int no);
usually, the calling example looks like the following way,
sigismember(&set, SIGALRM);
below is one of the examples of using both sigfillset
and sigismember
.
/**
* sigismember and sigfillset example
*
* Author: Devendra Naga ([email protected])
*
* LICENSE MIT
*/
#include <stdio.h>
#include <signal.h>
int main()
{
sigset_t set;
sigfillset(&set);
if (sigismember(&set, SIGALRM)) {
printf("sigalrm is a member of the set\n");
} else {
printf("sigalrm is not a member of the set\n");
}
}
sigdelset
deletes the signal from the given set. The prototype is as follows,
int sigdelset(sigset_t *set, int no);
The system call sigprocmask
, used to block certain signals.
The sigprocmask
prototype is as follows (from the man pages),
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
is defined by one of the following, SIG_BLOCK
and SIG_UNBLOCK
.
Below is an example use of sigprocmask
.
/**
* example sigprocmask
*
* Author: Devendra Naga ([email protected])
*
* LICENSE MIT
*/
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
sigset_t set;
int ret;
sigemptyset(&set);
// add SIGINT
sigaddset(&set, SIGINT);
ret = sigprocmask(SIG_BLOCK, &set, NULL);
if (ret != 0) {
perror("sigprocmask");
return -1;
}
int count = 0;
while (1) {
printf("hello .. %d\n", count ++);
sleep(1);
if (count == 5) {
break;
}
}
ret = sigprocmask(SIG_UNBLOCK, &set, NULL);
if (ret != 0) {
perror("sigprocmask");
return -1;
}
}
In the above example, the signal set of type sigset_t
is created with sigemptyset
followed by the sigaddset
. The signal SIGINT
being setup in the signal set.
The sigprocmask
system call is made with SIG_BLOCK
that effectively blocks the signal SIGINT
till the loop below executes. The loop is created only for testing purposes to see if the SIGINT
is actually blocked by holding down ctrl +c
combination on the keyboard. Till the count of 5, the signal is blocked and a call to the sigprocmask
with SIG_UNBLOCK
is made to unblock the signal.