Skip to content

Standard Signals

alt text

Setting Signal Dispositions: signal()

typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);
// pointer to previous signal disposition on success, SIG_ERR on error (does not set errno)
  • Explain:

    • int sig: Signal from the figure 8.26
    • sighandler_t handler: a pointer to a function returning nothing and taking 1 integer argument
  • Instead of specifying the address of a function as the handler argument of signal(), we can specify one of the following values: SIG_DFL and SIG_IGN:

    • SIG_DFL: Reset the disposition of the signal to its default. This is useful for undoing the effect of an earlier call to signal() that changed the disposition for the signal.
    • SIG_IGN: Ignore the signal. If the signal is generated for this process, the kernel silently discards it. The process never even knows that the signal occurred.
  • Example:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void sigint_handler(int sig) { printf("Caught SIGINT\n"); }
// Never use `stdio` functions from within a signal handler in real-world application
int main() {
if (signal(SIGINT, sigint_handler) == SIG_ERR)
printf("Signal error\n");
pause();
}

Sending Signals: kill()

  • One process can send a signal to another process using the kill() system call:
#include <signal.h>
int kill(pid_t pid, int sig);
// Returns 0 on success, or –1 on error and errno is set to indicate the error.

Explain

  • The pid argument identifies one or more processes to which the signal specified by sig is to be sent:

    • If pid > 0, the signal is sent to the process with the process ID specified by pid.
    • If pid == 0: the signal is sent to every process in the same process group as the calling process, including the calling process itself.
    • If pid < -1: the signal is sent to all of the processes in the process group whose ID equals the absolute value of pid.
    • If pid == -1 (broadcast): the signal is sent to every process for which the calling process has permission to send a signal, except init (process ID 1) and the calling process. If a privileged process makes this call, then all processes on the system will be signaled, except for these last two.
  • A process needs appropriate permissions to be able send a signal to another process. The permission rules are as follows:

    • A privileged (CAP_KILL) process may send a signal to any process.
    • The init process (process ID 1), which runs with user and group of root, is a special case. It can be sent only signals for which it has a handler installed.
    • An unprivileged process can send a signal to another process if RUID or EUID of the sending process matches the RUID or SSUID of the receiving process. alt text
  • Error:

    • If no process matches the specified pid, kill() fails and sets errno to ESRCH (“No such process”).
    • If a process doesn’t have permissions to send a signal to the requested pid, then kill() fails, setting errno to EPERM. Where pid specifies a set of processes (i.e., pid is negative), kill() succeeds if at least one of them could be signaled.

Example:

kill_demo.c
#include "dbg.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
int s, sig, pid;
check(argc == 3 && strcmp(argv[1], "--help") != 0, "%s sig-num pid\n",
argv[0]);
// Simplify arg check
// Assume that sig is correct.
pid = atoi(argv[1]);
sig = atoi(argv[2]);
s = kill((pid_t)pid, sig);
if (s == -1) {
// Error
if (errno == EPERM)
log_info(
"Process exists, but we don't have permission to send it a signal");
else if (errno == ESRCH)
log_info("Process doesn't exist");
else
sentinel("kill");
} else {
// Kill successfully
log_info("Process exists and we can send it a signal\n");
}
exit(EXIT_SUCCESS);
error:
return -1;
}

Sending Signals to the process itself: raise()

Sometimes, it is useful for a process to send a signal to itself

#include <signal.h>
int raise(int sig);
// Returns 0 on success, or nonzero on error

Explain

In a single-threaded program, a call to raise() is equivalent to the following call to kill():

kill(getpid(), sig);

On a system that supports threads, raise(sig) is implemented as:

pthread_kill(pthread_self(), sig)

Error

raise() returns a nonzero value (not necessarily –1) on error. The only error that can occur with raise() is EINVAL, because sig was invalid.

Sending Signals to a Process group: killpg()

#include <signal.h>
int killpg(pid_t pgrp, int sig);
// Returns 0 on success, or –1 on error

Explain

A call to killpg() is equivalent to the following call to kill():

kill(-pgrp, sig);

If pgrp == 0, then the signal is sent to all processes in the same process group as the caller.

Setting Signal Dispositions: setaction()

  • One limitation of signal() is that it does not provide a straightforward way to query or retrieve the current disposition of a signal without altering it.
  • The sigaction() system call is an alternative to signal() for setting the disposition of a signal. It provides much more flexibility.
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
// Returns 0 on success, or –1 on error

Explain

  • The sig argument identifies the signal whose disposition we want to retrieve or change. This argument can be any signal except SIGKILL or SIGSTOP

  • The act argument is a pointer to a structure specifying a new disposition for the signal. If we are interested only in finding the existing disposition of the signal, then we can specify NULL for this argument

  • The oldact argument is a pointer to a structure of the same type, and is used to return information about the signal’s previous disposition. If we are not interested in this information, then we can specify NULL for this argument.

  • The structures pointed to by act and oldact are of the following type:

    struct sigaction {
    void (*sa_handler)(int); /* Address of handler */
    sigset_t sa_mask; /* Signals blocked during handler invocation */
    int sa_flags; /* Flags controlling handler invocation */
    void (*sa_restorer)(void); /* Not for application use */
    };
    • The sa_handler is similar to handler argument given to signal().
    • The sa_mask (interpreted <=> sa_handler != SIG_IGN or SIG_DFL): specifies a mask of signals which should be blocked during the execution of the signal handler. In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER flag is used. These signals remain in the process signal mask until the signal handler returns, at which time they are automatically removed. This field allows us to specify a set of signals that aren’t permitted to interrupt execution of this handler.
    • The sa_flags: is a bit mask specifying various options controlling how the signal is handled. The following bits may be ORed (|) together in this field:
      NameDescription
      SA_NOCLDSTOPIf sig is SIGCHLD, don’t generate this signal when a child process is stopped or resumed as a consequence of receiving a signal
      SA_NOCLDWAIT(since Linux 2.6) If sig is SIGCHLD, don’t transform children into zombies when they terminate
      SA_NODEFERWhen this signal is caught, don’t automatically add it to the process signal mask while the handler is executing.
      SA_ONSTACKInvoke the handler for this signal using an alternate stack installed by sigaltstack().
      SA_RESETHANDWhen this signal is caught, reset its disposition to the default (i.e., SIG_DFL) before invoking the handler. (By default, a signal handler remains established until it is explicitly disestablished by a further call to sigaction().)
      SA_RESTARTAutomatically restart system calls interrupted by this signal handler
      SA_SIGINFOInvoke the signal handler with additional arguments providing further information about the signal

Example

#include "dbg.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
volatile sig_atomic_t unprocessedSig = 0;
void sighandler(int sig) {
if (sig == SIGINT)
unprocessedSig = 1;
}
int main() {
printf("This is pid %d\n", getpid());
struct sigaction act;
act.sa_handler = &sighandler;
// All signal will be blocked while our signal handler function is running.
// The original signal mask will be restored when our signal handler exits normally;
sigfillset(&act.sa_mask);
/*
If the signal handler gets run in the middle of system call (like open(),
read(),...) and the signal handler returns normally, restart system calls
interrupted by the signal handler
*/
act.sa_flags = SA_RESTART;
if (sigaction(SIGINT, &act, NULL) == -1)
sentinel("sigaction");
printf("IMPORTANT: kill this process by running 'killall -9 sigaction_demo' "
"in another terminal\n");
while (1) {
if (unprocessedSig) {
unprocessedSig = 0;
printf("SIGINT signal occurred.\n");
}
}
return 0;
error:
return -1;
}