09 - Signals

Class: CSCE-313


Notes:

Outline

  1. What is a “signal”?
  2. Signal “handling”.
  3. UNIX signals API.

What is a signal?

Signals

Examples:

Notes:

Example

cat -
> stty -a
speed 9600 baud; 52 rows; 49 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
        -echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
        -extproc
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8
        -ignbrk brkint -inpck -ignpar -parmrk
oflags: opost onlcr -oxtabs -onocr -onlret
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
        -dtrflow -mdmbuf
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
        eol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;
        min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T;
        stop = ^S; susp = ^Z; time = 0; werase = ^W;

If we look at the types of signals:

> kill -l
HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2

Q&A

What happens when you press Ctrl-C on the terminal with a running foreground process?

It is your terminal driver that is looking at the characters that is passing to the reader. There is a program at the other end that reads characters from that file, but in between there is this terminal driver that looks at all of these characters. It is the terminal drive that will send the SIGINT signal, not to a process but to the process group.

Notes:


How to deal with runaway processes

https://www.youtube.com/watch?v=z6sDujfLNfI

Signals are asynchronous

func (int a1, int a2) {
    int i, j = 2;
    for (i = a1; i < a2; i++) {
        j = j * 2;            // <----
        j = j / 127;          // <----
        // ...
    }
}

What is the purpose of signals?

  1. Allow humans to interact with programs using the terminal. For e.g.,
    • Ctrl+C sends an interrupt signal SIGINT
    • Ctrl+Z suspends the process by sending SIGTSTP
  2. Allow the kernel to enforce semantics
    • SIGSEGV is sent on memory exceptions
    • SIGILL sent on encountering illegal instructions
    • SIGPIPE when one writes to a pipe with no reader
      • This happens even mid-flight if readers exit

Notes:

Example of handler for an illegal isntruction:

void sigill_handler(int sig) {
	print("Caught SIGILL (Illegal instruction)")
}
...

UNIX System signals

00 - TAMU Brain/6th Semester (Spring 26)/CSCE-313/Lecture/Visual Aids/image-6.png

What can a process do about a signal-I?

A program can tell the kernel to do one of three things when a signal occurs. We call this the disposition of the signal, or the action associated with a signal.

  1. Accept the default action. All signals have a default action.
    • signal(SIGINT, SIG_DFL)
    • Ignore
    • Terminate
    • Terminate and dump core
  2. Ignore the signal. Works for most signals.
    • signal(SIGINT, SIG_IGN)
    • Note: cannot ignore SIGKILL and SIGSTOP. It is not advised to ignore hardware exception signals.
  3. Catch the signal. (Invoke a function). Tell the kernel to invoke a given function (signal handler) whenever the signal occurs.
    • signal(SIGINT, do_something)

Notes:

What can a process do about signal-II?

Notes:

Where do signals come from?

From the user: Terminal-generated signals: for e.g., C

From the kernel: CPU Exceptions delivered as signals

From processes: Software generated signals

Notes:

Generating Signals: kill(2) and raise(3)

#include <signal.h>
int kill(pid_t pid, int sig); /* send signal sig to process pid */
/* example: send signal SIGUSR1 to process 1234 */
if (kill(1234, SIGUSR1) == -1)
    perror("Failed to send SIGUSR1 signal");
    
/* example: kill parent process */
if (kill(getppid(), SIGTERM) == -1)
    perror("Failed to kill parent");
#include <signal.h>
int raise(int sig); /* Sends signal sig to itself. Part of ANSI C */

Q&A

What happens if a signal arrives while read() is blocking?

char buf[100];
read(fd, buf, 100);
  1. Does read() always return?
  2. What does it return?
  3. When does it automatically restart? You don't know this yet, but it

Notes:

Signal “handling”

Registering a handler for a signal

#include "apue.h"

void sighandler (int signum) {
    printf ("\nSignal caught!\n");
}

int main () {
    signal(SIGINT, sighandler);
    
    for(int i = 0; i < 5; i++) {
        printf("Sleeping...\n");
        sleep(5);
        printf("Awake\n");
    }
}
davidkebo@linux:~/code/week8$ ./a.out
Sleeping...
Awake
Sleeping...
Awake
Sleeping...
^C
Signal caught!
Awake
Sleeping...
Awake
Sleeping...
^C

Notes:

One handler for multiple signals

void sig_usr(int signo) { /* argument is signal number */
    if (signo == SIGUSR1)
        printf ("received SIGUSR1\n");
    else if (signo == SIGUSR2)
        printf ("received SIGUSR2\n");
    else
        printf ("received signal %d\n", signo);
    return;
}

int main(void) {
    signal(SIGUSR1, sig_usr);
    signal(SIGUSR2, sig_usr);
    
    for (;;) {
        pause ();
    }
}

Notes:

Q&A

What happens if a signal arrives while your signal handler is already running?

When a signal arrives while its signal handler is already running, the default behavior in POSIX systems is to block (defer) the new signal until the current handler finishes. The subsequent signal will then be delivered (and its handler called) after the first handler returns.

Notes:

Reaping child processes

void my_handler (int sig) {
    pid_t pid = wait (0);
    printf("Chd proc %d exited.\n", pid);
}

int main () {
    signal (SIGCHLD, my_handler);
    
    // ith proc sleeps for i sec and dies
    for (int i = 1; i <= 5; i++) {
        int pid = fork ();
        if (pid == 0) {
            sleep (i);
            return 0;
        }
    }
    
    // parent in an infinite busy loop,
    while (1) {
        printf ("Relaxing\n");
        sleep (1);
    }
}

Notes:

Reaping Child Processes With Simultaneous Termination

void my_handler (int sig) {
    pid_t pid;
    while ((pid = waitpid(-1, 0, WNOHANG)) > 0) {
        printf("Child process %d exited.\n", pid);
    }
}
int main () {
    signal (SIGCHLD, my_handler);
    // create 5 child processes.
    for (int i = 1; i <= 5; i++) {
        int pid = fork ();
        if (pid == 0) {
            sleep (5);
            return 0;
        }
    }
    // parent in an infinite loop, busy doing something else
    while (1) {
        printf ("Relaxing\n");
        sleep (1);
    }
}

Notes:

Nested signals

int s = 0;

static void sig_quit(int signo) {
    printf("In sig_quit, s=%d. Now sleeping...\n", ++s);
    sleep(5);
    printf("sig_quit, s=%d: exiting\n", s);
}

static void sig_int(int signo) {
    printf("Now in sig_int, s=%d. Returning immediately.\n", ++s);
}

int main(void) {
    printf("\n=> Establishing initial signal handler via signal.\n");
    signal(SIGQUIT, sig_quit);
    signal(SIGINT, sig_int);
    
    sleep(5);
    
    printf("Now exiting.\n");
    exit(EXIT_SUCCESS);
}

Notes:

=> Establishing initial signal hander via signal.
^\In sig_quit, s=1. Now sleeping...
^CNow in sig_int, s=%2. Returning immediately.
sig_quit, s=2: exiting
Now exiting.

The problem with asynchrony

#include <signal.h>

typedef struct {
    int x;
    int y;
} computation_state_t;

computation_state_t state;

int main() {
    void handler (int);

    signal(SIGINT, handler);
    long_running_procedure();
}

long_running_procedure() {
    while(1) {
        update_state(&state);
        compute_more();
    }
}

void handler(int sig) {
    display (&state);
}

Notes:

UNIX signals API

The signal function

The simplest interface to the signal features of the UNIX System is the signal function.

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
// Returns: previous disposition of signal if OK, SIG_ERR on error

signum is the name of the signal.

handler is either (1) the constant SIG_IGN, (2) the constant SIG_DFL, or (3) the address of a function to call when the signal occurs.

Notes:

kill and raise functions

The term kill in the UNIX System is a misnomer.

The kill() function sends a signal to a process or a group of processes.

The raise() function allows a process to send a signal to itself.

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);

Note: The call raise(signo) is equivalent to kill(getpid(), signo);

kill function

The kill() function sends a signal to a process or a group of processes.

#include <signal.h>
int kill(pid_t pid, int signo);

There are four different conditions for the pid argument to kill.

Note:

alarm

The alarm function sets a timer that will expire at a specified time Δ in the future. When the timer expires, the SIGALRM signal is generated.

If we don't catch this signal, its default action is to terminate the process.

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
// Returns: 0 or number of seconds until previously set alarm

There is only one alarm per process.

pause function

The pause function suspends the calling process until a signal is caught.

#include <unistd.h>
int pause(void); // Returns: -1 with errno set to EINTR

The only time pause returns is if a signal handler is executed and that handler returns.

Notes:

Signal Sets

POSIX. 1 defines the data type sigset_t to contain a signal set and the following five functions to manipulate signal sets (multiple signals).

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo); //ret 1 if true, 0 if false, - 1 on error

In all the functions that take a signal set as an argument, we always pass the address of the signal set as the argument.
typedef unsigned long sigset_t; (/usr/include/x86_64-linux-gnu/asm/signal.h)

Notes:

Automatic and manual signal blocking

Blocking is automatic for a signal when the signal handler is invoked

  1. First, if SIGINT is delivered, the process's SIGINT handler is invoked
  2. Simultaneously, SIGINT is blocked for the process.
    • I.e., if another SIGINT occurs during the handler execution, it is recorded in the pending bit vector, but not delivered
  3. When handler returns, signals of that type can start to be delivered again.

However, a process can also manually block/unblock the delivery of a signal, using sigprocmask()

Notes:

sigprocmask

The signal mask of a process is the set of signals currently blocked from delivery to that process. Think of it as a bit vector.

A process can examine its signal mask, change its signal mask, or perform both operations in one step by calling the following function.

int sigprocmask(int option, const sigset_t *new_set, sigset_t *old_set)
SIG_BLOCK The new signal mask for the process is the union of its current signal mask and the signal set pointed to by set. That is, set contains the additional signals that we want to block.
SIG_UNBLOCK The new signal mask for the process is the intersection of its current signal mask and the complement of the signal set pointed to by set. That is, set contains the signals that we want to unblock.
SIG_SETMASK The new signal mask for the process is replaced by the value of the signal set pointed to by set.
Notes:

Asynchrony revisited

computation_state_t state;
sigset_t set;
int main() {
    void handler (int);
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    signal(SIGINT, handler);
    long_running_procedure();
}
long_running_procedure() {
    while(1) {
        sigset t old set;
        sigprocmask(SIG_BLOCK, &set, &old_set);
        update_state(&state);
        sigorocmask(SIG_SETMASK, &old_set, 0);
        
        compute_more();
    }
}
void handler(int sig) {
    display(&state);
}

Notes:

sigaction function

The sigaction() function examines, changes, or both examines and changes the action associated with a specific signal.

#include <signal.h>
int sigaction(int signo, struct sigaction *act, struct sigaction *oact);

sig: (Input) A signal from the list defined in Control Signals Table.

*act: (Input) A pointer to the sigaction structure that describes the action to be taken for the signal.

*oact: (Output) A pointer to a storage location where sigaction() can store a sigaction structure. This structure contains the action currently associated with sig.