Signals in Linux

  • Post author:
  • Post last modified:February 28, 2024
  • Reading time:27 mins read

Signals

A signal is a notification delivered to a process by the kernel. A signal indicates that an event has occurred and the process must take note of it. Signals are mostly delivered asynchronously to a process. Whatever the process was doing is suspended and the processing of the signal takes place immediately.

1.0 Signal disposition

Each signal has a current disposition in a process, which determines how the process reacts on receiving that signal. There is a default disposition for each signal, which can be changed by the process. A signal can have one of the following default dispositions:

Signal disposition
Default ActionDescription
TermTerminate the process.
IgnIgnore the signal.
CoreTerminate the process and dump core.
StopStop the process.
ContContinue the process if it is currently stopped.

2.0 Signal groups

Signals are grouped in two categories, the standard signals and POSIX real-time signals. The standard signals are the classical signals that have been there since the early days of Unix.

2.1 Standard signals

The signals in Linux are #defined in <signal.h>. The list is

Linux signals
SignalDescriptionDefault action
SIGHUPHangupTerm
SIGINTInterrupt (generated from the keyboard, by pressing CTRL-C)Term
SIGQUITQuit (generated from the keyboard, by pressing CTRL-\)Core
SIGILLIllegal instructionCore
SIGTRAPTrace/breakpoint trapCore
SIGABRTCall to abort () functionCore
SIGIOTSynonym for SIGABRTCore
SIGBUSAttempt to access to invalid memory addressCore
SIGFPEFatal arithmetic errorCore
SIGKILLKill generated synthetically, by the kill -9 pid commandTerm
SIGUSR1User signal 1, generated syntheticallyTerm
SIGSEGVInvalid virtual memory referenceCore
SIGUSR2User signal 2, generated syntheticallyTerm
SIGPIPEBroken pipe. Write to pipe when there is no process on the other end to read.Term
SIGALRMAlarm clock timer expired.Term
SIGTERMTermination signal, sent synthetically by the kill pid command.Term
SIGSTKFLTStack fault on coprocessor (unused)Term
SIGCHLDChild process stopped or terminated.Ign
SIGCONTContinue executing (from keyboard)Cont
SIGSTOPStop executing (synthetic)Stop
SIGTSTPStop executing (from keyboard)Stop
SIGTTINBackground process, trying to readStop
SIGTTOUBackground process, trying to writeStop
SIGURGUrgent condition on socket.Ign
SIGXCPUCPU time limit exceededCore
SIGXFSZFile size limit exceededCore
SIGVTALRMVirtual timer expired.Term
SIGPROFProfiling timer expiredTerm
SIGWINCHWindow resize signalTerm
SIGIOI/O is now possibleTerm
SIGPOLLPollable eventTerm
SIGPWRPower failureTerm
SIGSYSBad arguments to system callCore
SIGUNUSEDSynonym of SIGSYSCore

Signals are generated naturally whenever the associated event occurs. For example, when a process tries to access memory outside the physical memory, SIGBUS is generated. Alternatively, any signal can be generated synthetically by using one of the five system calls or library functions, kill, killpg, pthread_kill, raise or abort. The default actions terminate and abort are almost the same except that in case of abort, a core-dump file is written.

The signals, SIGKILL and SIGSTOP, cannot be caught, blocked or ignored. For these signals, the default action has to happen.

A signal can be delivered to an entire process or to a specific thread in a process. However, if a process has multiple threads and a signal is delivered to the process (and, not to a specific thread), it is not defined that which thread will receive that signal. The action for a signal (the default action, whether ignoring the signal or the signal handler) is for the entire process, even if the process has multiple threads.

2.2 POSIX real-time signals

POSIX.1b, formally IEEE Std 1003.1b-1993, and, also called POSIX.4 during development, is for Real-time Extensions for POSIX compliant Operating Systems. POSIX.1b provides specifications for Real Time Signals (RTS), which are implemented in Linux and are commonly known as POSIX-RTS. POSIX real-time signals have additional signals beyond SIGUSR1 and SIGUSR2, provide queuing of signals, and, some additional application data is also provided.

3.0 Reliable signals

In early Unix versions, a signal handler was set using the signal system call. The problem with signal system call was that when the concerned signal was received, the disposition for that signal was set to the default. So, one had to set the handler every time a signal came. There is a small time window between receiving a signal and setting the handler again and if that signal came again in that small time window, the default action would happen and the process would often get terminated. So signal were considered somewhat non-reliable. The signal system call has since been deprecated and the newer method is to set the signal handler using the sigaction system call, which does not reset the signal disposition when the concerned signal comes. Using the sigaction system call, we get reliable signals in Linux and other POSIX compliant systems.

4.0 Blocking signals

Signals are generated and, then, delivered. After generation, and, before delivery, a signal is pending. There are times when we do not want our process to be interrupted with a signal. The process may be in the middle of updating a critical data structure and a signal handler running at that time could result in making the data structure inconsistent. So, a thread can block signals. When a signal is blocked and is generated for a process, it is kept in the pending state and, once the signal is unblocked, it is delivered to the process.

4.1 Signal Mask

The signal mask for a thread is the set of its currently blocked signals. You can set the signal mask for a thread with the pthread_sigmask function. If there is only one thread in a process (the main thread), you can set the signal mask for it with the sigprocmask system call. It is important to note that the signal mask is always for a thread.

4.2 sigsetops – signal set operation functions

#include <signal.h>

int sigemptyset (sigset_t *set);

int sigfillset (sigset_t *set);

int sigaddset (sigset_t *set, int signum);

int sigdelset (sigset_t *set, int signum);

int sigismember (const sigset_t *set, int signum);

sigemptyset sets the signal set pointed by set to an empty set. sigfillset adds all the signals to the set. sigaddset adds the signal signum to the set whereas sigdelset deletes the signum from it. sigismember checks whether signum is in the set. Next, we look at the system calls for setting the signal mask.

#include <signal.h>

int pthread_sigmask (int how, const sigset_t *set, sigset_t *oldset); // linking with -lpthread is required

int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);

pthread_sigmask is for changing the signal mask for a thread. The first parameter tells how the signal mask is to be changed. The how parameter could be SIG_BLOCK, which means the new signal mask should include the signals in the set. Or, how could be SIG_UNBLOCK, which means that the new signal mask should exclude the signals in the set. The third possible value for how is SIG_SETMASK, which results in the set becoming the new signal mask, replacing the earlier one. pthread_sigmask returns 0 on success and error number on failure. Like all pthread functions, errno is not set on failure.

sigprocmask is the older call for changing the signal mask for the singleton thread of a single threaded process. The parameters are the same as that of pthread_sigmask. However, there is a difference in the return value. sigprocmask returns 0 on success and -1 on failure and errno is set on failure. Use of pthread_sigmask requires linking the program with the pthread library. So, if you are not using threads and not linking with the pthread library, sigprocmask is the call for changing the signal mask.

4.3 sigpending

#include <signal.h>

int sigpending (sigset_t *set);

sigpending provides the set of signals that are pending for delivery to the calling thread. A thread might have blocked some signals and sigpending tells which signals were raised while blocked. The calling thread passes a pointer to an empty sigset_t and the mask of pending signals is returned in the set. sigpending returns 0 on success and -1 on failure.

5.0 Changing the signal action

The current action for a signal in a process can be changed with the sigaction system call.

#include <signal.h>

struct sigaction {
    void     (*sa_handler) (int);
    void     (*sa_sigaction) (int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer) (void);
};

int sigaction (int signum, const struct sigaction *act,
               struct sigaction *oldact);

signum can be any valid signal except SIGKILL and SIGSTOP. If oldact is not NULL, the previous action is returned in oldact. If act is not NULL, the new action is installed from act. Looking at the struct sigaction, the first two parameters, sa_handler and sa_sigaction are mutually exclusive, and, only one should be used. Also, the actual implementation might be using a union of the two; so the one not being used, must not be touched. If SA_SIGINFO is specified in sa_flags, then sa_sigaction is used, otherwise, sa_handler is used.

sa_handler can have the value SIG_DFL or SIG_IGN or it may be a pointer to the signal handler function. If the value is SIG_DFL, it means that the default action for the signal is to be restored. Similarly, if the value is SIG_IGN, the signal will, henceforth, be ignored. If a function pointer is passed, that function is installed as the handler for the signum signal. The signal handler function has a prototype like,

void signal_handler_fcn (int signum);

sa_mask is the set of signals that should be blocked in the thread in which the signal handler is called. These signals are added to the signal mask of the thread executing the signal handler. The signal for which the signal handler is called is also blocked unless the SA_NODEFER flag is specified. After the signal handler exits, the original signal mask for the thread is restored. sa_flags modify the behavior of the signal. It is bit-wise OR of zero or more flags. As mentioned before, the flag SA_SIGINFO indicates that sa_sigaction field is being used in struct sigaction instead of the sa_handler field. SA_NODEFER flag means that the signal for which this handler will be invoked should not be masked during the execution of the handler. Flag SA_NOCLDSTOP is meaningful for the SIGCHLD signal and means that SIGCHLD signal should not be delivered when a child stops or starts. Similarly, SA_NOCLDWAIT flag is also for the SIGCHLD signal and means that terminating children should not be turned into zombies. Flag SA_ONSTACK instructs the signal to be delivered on the alternate signal stack if the alternate stack has been declared with the sigaltstack system call. Flag SA_RESTART implies that if the signal comes when the process is blocked in a system call like read, the call should be restarted after the signal handler returns. The sa_restorer field is not meant to be used by application programs.

If SA_SIGINFO is specified in sa_flags, then sa_sigaction is to be used instead of the sa_handler. sa_sigaction is the pointer to the signal handler to be installed for the signum signal. This signal handling function has a prototype like,

void signal_handler_fn (int signum, siginfo_t *siginfo, void *context);

The first parameter, signum is the signal number for which this function is to be installed as a handler. The last parameter, pointer, is not to be used by the handler function. The second parameter is a pointer to a structure with members like,

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 Linux 2.6.32) */ 
    void    *si_call_addr; /* Address of system call instruction
                              (since Linux 3.5) */
    int      si_syscall;   /* Number of attempted system call
                              (since Linux 3.5) */
    unsigned int si_arch;  /* Architecture of attempted system call
                              (since Linux 3.5) */
}          

The above structure if for information about the fields only. For the actual structure, refer to the sub-directories in /usr/include. The first three members, si_signo, si_errno and si_code are there for all signals. si_signo is the signal number which has been delivered and has triggered this signal handler, si_errno is the error number associated with the signal and is not used in Linux. si_code is a value indicating why this signal was sent. About the rest of the fields, certain subsets are used for particular signals. It is important to use only the relevant fields for a signal because, in reality, the above structure might be having unions as members and the space for others fields might not really be there.

5.1 sigaltstack

#include <signal.h>

int sigaltstack (const stack_t *ss, stack_t *old_ss);

As mentioned above, the SA_ONSTACK flag causes the signal handler to be executed on an alternate stack. Using the sigaltstack call, we can define a new alternate signal stack and/or get the state of an existing alternate signal stack. The parameters ss and old_ss are pointers to typedef struct stack_t for new and old stacks. Either of these can be NULL if you are interested in only one of the stacks. The typedef struct stack_t is,

typedef struct {
    void  *ss_sp;     /* Base address of stack */
    int    ss_flags;  /* Flags */
    size_t ss_size;   /* Number of bytes in stack */
} stack_t;

Memory for the stack needs to be allocated and the base address and size of the stack needs to be passed via typedef struct stack_t. ss_flags can be zero or have the value SS_AUTODISARM which means that alternate signal stack settings are to be cleared upon entry into the signal handler. When the signal handler returns, the previous alternate signal stack settings are in place. This flag makes it safe to switch away from the signal handler with the swapcontext(3) function. If this flag was not used, a subsequent signal would corrupt the state of a switched away signal handler.

6.0 Generating signals synthetically

Signals are generated due to natural causes like a process might be trying to access non-existent memory, or there might be a divide by zero error, etc. But we can also generate signals synthetically by using certain system calls (or, the commands built using these system calls), and send these signals to relevant processes. Let’s look at these system calls.

6.1 kill

#include <sys/types.h>
#include <signal.h>

int kill (pid_t pid, int sig);

The kill system call is for sending the signal sig to the process identified by pid. A process can send a signal to another if it is a privileged process or its effective user id is equal to the real user id of the receiving process. If the pid is positive, the signal is sent to the process with that pid. If pid is zero, the signal is sent to every process in the process group of the calling process. If pid is -1, the signal is sent to every process to which the calling process has permission to send signals, except the init process (pid 1). If pid is less than -1, the signal is sent to every process in the process group whose id is -pid.

6.2 killpg

#include <signal.h>

int killpg (int pgrp, int sig);

killpg sends the sig signal to the process group identified by pgrp. If pgrp is zero, killpg sends the signal to the calling process’s process group.

6.3 pthread_kill

#include <signal.h>

int pthread_kill (pthread_t thread, int sig);

pthread_kill sends the signal sig to the thread identified by the first parameter. The receiving thread must be a part of the calling process. Now, if the signal is for terminate, it will terminate the calling process and not just the thread because the effect of signals is for the entire process. If the intention is to terminate a thread, pthread_cancel should be used instead.

6.4 raise

#include <signal.h>

int raise (int sig);

raise is a C function for sending a signal to the calling thread. That is, the calling thread sends the signal to itself.

6.5 abort

#include <stdlib.h>

void abort (void);

The abort function generates the SIGABRT signal for the calling process and causes its own abnormal termination. abort never returns. It first unblocks the SIGABRT signal, then raises the signal for the calling process. If SIGABRT signal is being ignored, it first resets the default action for SIGABRT and raises the SIGABRT signal the second time. All streams are closed and flushed and the process terminates.

7.0 sigwait system call

The sigwait system call waits for one of the signals specified in the first parameter, set.

#include <signal.h>

int sigwait (const sigset_t *set, int *sig);

sigwait suspends the execution of the calling thread until one of the signals specified in set becomes pending. When that happens, that signal is accepted and sigwait returns. The accepted signal is pointed by sig. On success, sigwait returns zero. In case of error, a positive integer indicating the error is returned.

Prior to making the sigwait call, the thread blocks the concerned signals specified in the set. So one or more of these signals can only be in the pending state for the calling thread. Once a signal in the set is pending, it is not delivered; it is “accepted” by the thread and the sigwait call returns with the accepted signal's id in the integer pointed by the second parameter, sig.

8.0 Signal handlers

The difficulty in programming signal handlers emanates from the fact that a signal can come at any time and the programmer does know about the state of the process at the time of its execution. A process might be in the middle of some library routine and if, by chance, you use the same function in signal handler, something erroneous might happen as the code might not be re-entrant. So, there are restrictions on what functions can be used in a signal handler. You cannot use printf, exit, etc. The complete list of safe functions that can be used in a signal handler is given in the manual (as printed by the "man signal-safety" command). Also, you cannot safely use a global variable unless it is the type volatile sig_atomic_t.

9.0 Signal handler – an example

/*

     basic-signals.c: basic signal handling

 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

void syserror (const char * const str);
void sig_handler (int signum);
void sig_child_handler (int signum);

int main (int argc, char **argv)
{
    /* handle signals */

    sigset_t set;

    if (sigfillset (&set) == -1) 
        syserror ("sigfillset");
    // block all signals for a while
    if (sigprocmask (SIG_SETMASK, &set, NULL) == -1)
        syserror ("sigprocmask");

    struct sigaction act;

    memset (&act, 0, sizeof (act));

    // ignore these signals
    act.sa_handler = SIG_IGN;

    if (sigaction (SIGHUP, &act, NULL) == -1)
        syserror ("sigaction");


    // set signal handler for following signals
    act.sa_handler = sig_handler;

    if (sigaction (SIGINT, &act, NULL) == -1)
        syserror ("sigaction");

    if (sigaction (SIGQUIT, &act, NULL) == -1)
        syserror ("sigaction");

    if (sigaction (SIGTERM, &act, NULL) == -1)
        syserror ("sigaction");

    // Terminating child processes should not turn into zombies 
    memset (&act, 0, sizeof (act));
    act.sa_flags = SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
    act.sa_handler = sig_child_handler;

    if (sigaction (SIGCHLD, &act, NULL) == -1)
        syserror ("sigaction");

    // reset the signal mask
    if (sigemptyset (&set) == -1)
        syserror ("sigemptyset");

    if (sigprocmask (SIG_SETMASK, &set, NULL) == -1)
        syserror ("sigprocmask");

    // rest of the code
    int pid;

    pid = fork ();

    if (pid == 0) // child
        exit (EXIT_SUCCESS);

    // parent

    while (1) 
        sleep (10); 
   
}

void sig_handler (int signum)
{
    switch (signum) {

        case SIGINT: write (2, "User pressed CTRL-C.\n", 21);
                      return;

        case SIGQUIT: write (2, "User pressed CTRL-\\.\n", 21);
                      return;

        case SIGTERM: write (2, "Termination signal received. Bye.\n", 34);
                      _exit (EXIT_FAILURE);

        default:  write (2, "Unexpected signal caught. Terminating.\n", 39);
                  _exit (EXIT_FAILURE);
    }
}

void sig_child_handler (int signum)
{
    switch (signum) {

        case SIGCHLD: write (2, "SIGCHLD received.\n", 18);
                      return;

        default:  write (2, "Unexpected signal caught. Terminating.\n", 39);
                  _exit (EXIT_FAILURE);
    }
}

void syserror (const char * const str)
{
    perror (str);
    exit (EXIT_FAILURE);
}

In the above example, the write and _exit system calls are used in the signal handler. These calls are a part of the list of functions that can be safely used in a signal handler.

10.0 See also

Share

Karunesh Johri

Software developer, working with C and Linux.
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments