signals

Linux provides a mechanism to interrupt a running program. Some of the interrupts are the result of user programs doing something bad, and some of the interrupts are external events that are intending to get the attentions of a running program.

We are going to start out with the more traditional signal calling mechanism. A lot of existing programs use this interface. However, signal calls are considered unreliable and a newer mechanism is now available which is considered more robust --- we will cover this later in this set of notes.

some faulty program interrupts are :

  1. SIGFPE - floating point exception
  2. SIGILL - Illegal instruction
  3. SIGSEGV - Invalid memory segment access

Externally generated Interrupts:

  1. SIGALRM - Alarm clock
  2. SIGHUP - traditionally line hangup , but is used now for telling a process to reinitialize itself
  3. SIGINT - the results of a Control-C which normally cancels a running program
  4. SIGKILL - if Control-C doesn't work and nothing else seems to work then SIGKILL tells the Linux Kernel to take a sledge hammer to to the process and force it down. It is possible for running processes to ignore other signals and keep running, but SIGKILL can't be ignored.
  5. SIGTERM - termination request -- normal default when someone issues a command like:
    kill 1648
  6. SIGUSR1 - User defined signal 1
  7. SIGUSR2 - User defined signal 2
  8. SIGCLD - One of Children processes has terminated

Each of the above signals have a specific number. If you have a process associated with process ID 1648, then you can send a signal to a process with a command like:

kill -9 1648

This command sends signal number 9 (which happens to be SIGKILL) to process ID 1648. If you don't know what number is associated with a desired signal, you can issue the command to list the signal numbers for you:

kill -l

If a process doesn't handle a signal, then the process is terminated. For example, most processes don't handle SIGINT and that's why control-c terminates most processes. However, some processes want to use control-c for something else like maybe copy. In this case the application handles control-c and takes whatever action is desired for the SIGINT signal.

Consider the following example

/*  We'll start by writing the function which reacts to the signal
    which is passed in the parameter sig.
    This is the function we will arrange to be called when a signal occurs.
    We print a message, then reset the signal handling for SIGINT
    (by default generated by pressing CTRL-C) back to the default behavior.
    Let's call this function ouch.  */

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void ouch(int sig)
{
    printf("OUCH! - I got signal %d\n", sig);
    (void) signal(SIGINT, SIG_DFL);
}

/*  The main function has to intercept the SIGINT signal generated when we type Ctrl-C .
    For the rest of the time, it just sits in an infinite loop,
    printing a message once a second.  */

int main() 
{
    (void) signal(SIGINT, ouch);

    while(1) {
        printf("Hello World!\n");
        sleep(1);
    }
}

This program will print out Hello World forever until something interrupts it.

  1. If you type Control-C , you will generate a SIGINT signal. This program however, has defined a signal handler routine named outch to handle SIGINT signals. This was done with the call:
    signal(SIGINT, ouch)
  2. Now the outch routine itself decides to return the default SIGINT behavior back to the system default behavior for SIGINT which means that the program aborts on SIGINT signals. This was done with the call:
    signal(SIGINT, SIG_DFL);
  3. Issue another Control-C and you will see the program terminate.
  4. Modify the above program by getting rid of the signal call in the outch routine. Note that the program keeps running no matter how many control-C's are issued. To terminate your program you have a couple of options.
  5. Modify the above program by getting rid of the signal call in the main routine. Try the program again and notice that the first Control-C terminates the program.

Another Example:

Instead of issuing the kill command from the command line, you can also call kill from your C program. For example, the following C call will issue a SIGTERM to process 1648:
kill( 1648, SIGTERM);

This command has other features that can be discoved from the man pages --- for example, the kill command can be used to find out if a process still exists. Note that waitpid can be used for child processes to accomplish this need.

Another couple of useful routines:

This example is going to demonstrate that a signal handler can process more than one signal type. Your handler can discriminate which signal was actually called by looking at the integer passed in. Also this example is going to illustrate the SIGCLD signal which goes off when one of your child processes terminates. SIGCLD is very handy to know about because it gives you a better way to "harvest" your terminated children (remember if you don't do anything you leave around Zombie/Defunct processes).

There is an ALARM signal which can sometimes be useful. Here is the example from your book.

/*  In alarm.c, the first function, ding, simulates an alarm clock.  */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h> 

static int alarm_fired = 0;

void mysig(int sig)
{
    int status;
    pid_t pid;
        
    printf("Signal %d \n", sig);

    if (sig == SIGALRM)
    {
       alarm_fired = 1;
    }
    if (sig == SIGCLD)
    {
       // harvest terminated DEFUNCT child process
       pid = waitpid(-1, &status, WNOHANG); 
       printf(" Child Process(%d) terminated with a status of %d\n",
            pid, status); 
    }
}

/*  In main, we tell the child process to wait for five seconds
    before sending a SIGALRM signal to its parent.  */

int main()
{
    int pid;

    printf("alarm application starting\n");

    if((pid = fork()) == 0) {
     // Child process
        sleep(5);
    // Wake up parent process with a SIGALRM
        kill(getppid(), SIGALRM);
        exit(0);
    }

/*  The parent process arranges to catch SIGALRM with a call to signal
    and then waits for the inevitable.  */

    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, mysig);
    (void) signal(SIGCLD, mysig);
 
    do
    {
        pause(); // Wait for any signal call
    } while(!alarm_fired);
    
    printf("Ding!\007\n");

    printf("done\n");
    exit(0);
}



More Reliable Signal Handling:

One of the problems with the old way of doing signals is the fact that while processing a signal, you can get another signal. Also, there are times where it is very awkward dealing with signals.

So to fix this we need to use a Mask to specify which signals are masked. In the following example we are repeating one of our previous examples using the new method. In this case we will make sure that if we are processing on Control-C, we can't get a second Control-C signal.

Instead of a signal call, we use a sigaction call. You pass a structure called sigaction that contains 3 fields:

  1. sa_flags - miscellaneous flags that we won't pursue now
  2. sa_mask - a mask of the Signals that are blocked while processing this Signal
  3. sa_handler - the signal handler to call when the signal occurs

To set the sa_mask we can use the following calls:

  1. sigemptyset - creates a completely empty mask
  2. sigfillset - creates a mask with everything set
  3. sigdelset - remove one signal from the mask
  4. sigaddset - add one signal to the mask
The following code does that same thing as our previous SIGINT example, except that it uses the newer signal interface.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
struct sigaction act_open;

void ouch(int sig)
{
    printf("OUCH! - I got signal %d\n", sig);
    
    // Set SIGINT back to the default action
    act_open.sa_handler = SIG_DFL;
    sigaction(SIGINT, &act_open, 0);

}

int main()
{
    act_open.sa_flags = 0;
    
  // Create a mostly open mask -- only masking SIGINT
    sigemptyset(&act_open.sa_mask);
    sigaddset(&act_open.sa_mask, SIGINT);

    act_open.sa_handler = ouch;
    

    sigaction(SIGINT, &act_open, 0);

  while(1) {
    printf("Hello World!\n");
    sleep(1);
  }
}


sigprocmask: Block Signals during critical operations

An additional feature that the new interface provides is a way to block specified signals during critical portions of code. This is very good, because it is tough to recover from signals in certain situations.

To block or unblock signals, use the sigprocmask routine.

The first parameter can take on values of:

  1. SIG_SETMASK - just set all of the signal blocking bits the way you want
  2. SIG_UNBLOCK - only disable the specified signals, leave the unspecified signals alone.
  3. SIG_BLOCK - only enable the specified signals, leave the unspecified signals alone.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
struct sigaction act_protected;

void ouch(int sig)
{
    printf("OUCH! - I got signal %d\n", sig);
}

int main()
{
    struct sigaction act_open;
    sigset_t signals_open, signals_protected;

// Create a protected interupt mask -- only unmasking SIGSEGV
    sigfillset(&signals_protected);
    sigdelset(&signals_protected, SIGSEGV);

// Create a mostly open interupt mask -- only masking SIGINT
    sigemptyset(&signals_open);
    sigaddset(&signals_open, SIGINT);


// Fill in sigaction -- only masking SIGINT    
    act_open.sa_flags = 0;
    sigemptyset(&act_open.sa_mask);
    sigaddset(&act_open.sa_mask, SIGINT);

    act_open.sa_handler = ouch;
    
    // during SIGINT call, further SIGINT calls are blocked
    sigaction(SIGINT, &act_open, 0);

  while(1) {
    sigprocmask(SIG_SETMASK, &signals_open, NULL);
// Now all signals are allowed except SIGINT
    printf("slightly critical only SIGINT blocked\n");
    sleep(5);
       
    // the following enables SIGINT 
    sigprocmask(SIG_UNBLOCK, &signals_open, NULL);
// Now all signals are allowed including SIGINT
    printf("Open completely to interrupts\n");
    sleep(5);    
    
    sigprocmask(SIG_BLOCK, &signals_protected, NULL);
// All signals are blocked except SIGSEGV
    printf("IMPORTANT DATABASE WORK complete protected -- except SIGSEGV\n");
    sleep(5);
      }
}


sigusr1, sigusr2

Linux/Unix provides you with 2 signals that you can do whatever you want with. The following example demonstrates a parent and a child alternately scheduling each other with sigusr1 signals.

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

int ntimes=0;

main()
{
    pid_t pid, ppid;
    void p_action(int), c_action(int);
    static struct sigaction pact, cact;
    
    /* set SIGUSR1 action for parent */;
    pact.sa_handler = p_action;
    sigaction(SIGUSR1, &pact, NULL);
    
    switch (pid = fork())
    {
    case -1: // Shouldn't get here
        perror("synchro");
        exit(1);
    case 0:  // child
        cact.sa_handler = c_action;
        sigaction(SIGUSR1, &cact, NULL);
        
        // get parent process-id
        ppid = getppid();
        for (;;)
        {
            sleep(1);
            // wake up parent
            kill (ppid, SIGUSR1);
            pause(); // wait for parent signal
        }
     default: // parent
        for (;;)
        {
            pause(); // wait for child signal
            sleep(1);
            // wake up child
            kill (pid, SIGUSR1);
        }
        
    }
} // end of main

void p_action(int sig)
{
    printf("Parent caught signal %d\n", ++ntimes);
}
void c_action(int sig)
{
    printf("Child caught signal %d\n", ++ntimes);
}



What happens if you eliminate the command inside of the child logic that does the following (WHY?):

//sigaction(SIGUSR1, &cact, NULL);