4 Signals

4 minute read

Published:

Signals and system calls

Sending signals

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);    // same as kill(), but yourself
  • Both return: 0 if OK, -1 on error
  • if pid < 0, the signal is sent to the process group with pgid == |pid|

Terminal-generated signals:

  • Ctrl-C sends SIGINT to foreground process group
  • Ctrl-\ sends SIGQUIT to foreground PG
  • Ctrl-Z sends SIGTSTP to foreground PG

signal()

typedef void (*sighandler_t)(int);    // type: void (*)(int)
sighandler_t signal(int signum, sighandler_t handler);

Takes a number, and a pointer to the handler. Returns the old handler. handler can be:

  • SIG_IGN: ignore the signal
  • SIG_DFL: take the default action
  • a handler (function) of type void (*)(int) called to handle signal

Unreliability

read()

Slow system call

static void	sig_int(int);		/* our signal-catching function */

int main(void)
{    
	if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal error");
}

void sig_int(int signo)
    printf("interrupt\n%% ");

With this, the program prints “interrupt”, but still terminated

Signal is a very old, and not portable. When signal is caught, the handler

  1. “Slow” system calls (can stuck forever (read())) may get interrupted on signals
    • Slow underlying read() syscall gets interrupted. errno set to EINTR, causes fgets() to return NULL.
      • errno is a global variable set to the ID of the last error
      • some OS kernels (e.g., FreeBSD, macOS) automatically restart the interrupted syscall
    • Fix: check EINTR and manually restart the syscall
      • ideally, specify restart semantics in handler installation instead of doing it manually
  2. Signals get lost (de-registered once triggered)
    • Handler set with signal()  may reset after each signal
    • Fix: Re-register the hander after detecting EINTR
      • but race condition: what if we get another signal before we set disposition?
      • ideally, specify disposition reset semantics in handler installation instead of doing it manually
      • Also not portable across OSs

[[#Reentrancy]] issue:

  • can’t call malloc()free(), functions with static buffers, standard I/O functions are unsafe!
  • It will enter functions asynchronously, might corrupt data

alarm() and pause()

alarm() for timeout, generates SIGALRM. Default behavior is to terminate

static void	sig_alrm(int);

int main(void)
{
    int		n;
    char	line[MAXLINE];

    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        err_sys("signal(SIGALRM) error");

    alarm(10);   // sets alarm
    if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
        err_sys("read error");
        // if something typed without 10 seconds, alarm will go
    alarm(0);   // cancels alarm

    write(STDOUT_FILENO, line, n);
    exit(0);
}

static void sig_alrm(int signo)
{
    /* nothing to do, just return to interrupt the read */
}

Problems:

  1. Does not work if slow syscall read() gets automatically restarted
  2. If system too busy and this program gets hanged by the OS. Alarm taken by OS
    • Solution: select()

Portable solution

  • Generated when the event triggering the signal occurs
  • Signal pending…
  • Delivered when the signal action taken
    • A process can block the signal delivery, so it stays at pending until unblocked

Signal sets

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
// All four above return: 0 if OK, -1 on error
int sigismember(const sigset_t *set, int signo);
// Returns: 1 if true, 0 if false, -1 on error

Implementation: bit mask

sigaction()

int sigaction(int signo, struct sigaction *act, struct sigaction *oact); 
        // Returns: 0 if OK, -1 on error

struct sigaction {
    void     (*sa_handler)(int);  /* addr of signal handler, */
                                  /* or SIG_IGN, or SIG_DFL */
    sigset_t sa_mask;    /* additional sigs to block while handling current sig*/
    int      sa_flags;            /* signal options, Figure 10.16 */
    /* alternate handler */
    void     (*sa_sigaction)(int, siginfo_t *, void *);
};

sigset_t sa_mask: additional signal set to temporarily block when signo is handled by  sa_handler

  • signo itself is automatically blocked while in sa_handler int sa_flags: handling options – some notable ones:
  • SA_INTERRUPT: Don’t automatically restart slow system call (default)
  • SA_RESTART: Automatically restart slow system call (opposite)
  • SA_RESETHAND: Reset signo to SIG_DFL (sig_default, auto de-register)

Reliable implementation of signal() with sigaction()

Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction    act, oact;

    act.sa_handler = func;   // set handler to signal that comes in
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    // calls sigaction()
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}

Signal mask

A set of signals currently [[#^258d08 |blocked]] from delivery

  • Can be examined or changed
    int sigprocmask(int how, sigset_t *set, sigset_t *oset);
    

    Manipulates process’s signal mask

  • oset: old signal mask set
  • set: New set
  • how: how the current signal mask is modified (block, unblock, change)
int sigpending(sigset_t *set);

Retrieves a set of pending, blocked signals to *set

Both return 0 if OK, -1 on error