CS330 Interprocess Communication and Signals


Highlights of this lab:


Introduction to Signals

When is a signal generated?

When a process terminates abnormally it tries to send a signal indicating what went wrong.
C programs and UNIX can catch these signals for diagnostics.

The kernel can send signals. There are two kinds of kernel signals:

  1. Hardware Conditions--For instance SIGSEGV (the segmentation fault signal), which indicates that there has been an addressing violation.
  2. Software Conditions--For instance SIGPOLL or SIGIO (signaling that I/0 is possible on a file descriptor)

You can also send your own signals through:

  1. keyboard sequences such as CTRL-C
  2. the kill command at the shell level
  3. kill() system call in a C program

Signals vs Interrupts

Signals are software generated interrupts that are sent to a process when a event occurs.

Signals are the software version of a hardware interrupt. Just like a hardware interrupt, a signal can be blocked from being delivered in the future.

Signal Default Action

Each signal has a default action which is one of the following:

Signals are listed in a system header file:

There is also usually a man page with a list of signals and their default actions:

NOTE: Certain signals cannot be caught or ignored. These are SIGKILL and SIGSTOP.


Signals on the Shell Level

Let's experiment with signals on the shell.

To do this, we will use an infinitely looping program that prints an incrementing number every second:

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

int main (void)
{
        int i;

        for(i=0;; ++i)
        {
                printf("%i\n",i);
                sleep(1);
        }
}

We are going to experiment with keyboard sequences and the kill command.


Keyboard Sequences

Once you have compiled the above program, run it.

It will loop infinitely counting up from 0.

How can we stop it?

Try:

These keyboard sequences are actually mapped to signals. To see the current mappings of keystrokes for the interrupt and quit signals, use the following command:

stty -a

kill at the Command Line

The general format of the kill command is:

% kill [-signal] pid

When issued, the kill command will send a signal to the indicated process. The signal can be an integer or the symbolic signal name with the SIG prefix removed. If no signal is specified then SIGTERM (terminate) is sent by default.

To find the pid, you can use the ps command.

Try running the program given above and use the following signals to give you the four possible actions of a signal:

To give you a list of signals that you can use with kill use:

kill -l 

A few notes on kill:


System Calls for Signals

In addition to working with signals on the command line, we can also implement signals in C code.

The main system calls that we will be mentioning in this lab are:

kill() provides a way of sending a signal and signal() provides a way of handling or catching a signal.


Sending Signals: kill()

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

       int kill(pid_t pid, int sig);

The kill system call can be used to send any signal to any process group or process.

If pid is positive, then signal sig is sent to pid.

If pid equals 0, then sig is sent to every process in the process group of the current process. (the man pages on kill)

Example:

kill(getpid(), SIGINT);

would send the interrupt signal to the id of the calling process.
-This would have an effect similar to the exit() command.

Note: kill(pid, SIGKILL); sends a signal value 9 which is a sure kill. This signal cannot be caught or ignored

 

For completeness, there is another function, called raise(), which also sends signals. However, raise() is a library function rather than a system call.

       #include <signal.h>

       int raise(int sig);

The raise() function sends a signal to the current process. It is equivalent to

            kill(getpid(), sig); 

Handling Signals: signal()

In the beginning, we mentioned that each signal has a default action. Sometimes, you may want to overwrite this default action so that you can control what will happen when a certain signal is received.

You can overwrite the action of all signals except for SIGKILL and SIGSTOP.

To give a signal a new action, you can use the signal() system call:

       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

By using signal() in your code, you are saying that if the process receives a signal signum, then a call will be made to the user-defined function called handler.

If signal() is unsuccessful (at assigning a signal to a signal handler), then -1 is returned.

In the place of the name of a handler function, you could use:

Example 1:

/* ignore the interrupt signal */
signal(SIGINT, SIG_IGN);

Example 2:

/* revert to default action */
signal(SIGINT, SIG_DFL);

Example 3:

/* set CTRL-C and CTRL-\ to be trapped by a function called signal_catcher */
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int main (void)
{
        int i;
        void signal_catcher(int);

        if (signal(SIGINT,signal_catcher)==SIG_ERR)
        {
                perror("Sigset cannot set SIGINT");
                exit(SIGINT);
        }
        if (signal(SIGQUIT, signal_catcher)==SIG_ERR)
        {
                perror("Sigset can not set SIGQUIT");
                exit(SIGQUIT);
        }
        for(i=0;; ++i)
        {
                printf("%i\n",i);
                sleep(1);
        }
}
void signal_catcher(int the_sig)
{
        //The following line is commented out, but may be necessary in
        //some implementations. Otherwise, the signal may return to its
        //default action after one occurance of the signal is handled
        //signal(the_sig, signal_catcher); //reset
        printf("\nSignal %d received. \n", the_sig);
        if (the_sig == SIGQUIT)
                exit(1);
}

Note: instead of the signal names, the following integers could have been used:

A function defined as a signal handler may have the following format, and must return void:

void handler(int);
....
void handler(int the_signal)
   .... code to be executed
}

A signal, once processed, may be reset to the default condition (depending on your implementation). If you wish to continue processing with your own signal(s) after you have trapped a particular signal, you may have to reset the signal. (The line showing this is commented out in the above code).

You may also wish to try a function called sigset(), which is similar to signal().


Communicating Between Processes

What does the following code do?

//code from http://jan.netcomp.monash.edu.au/OS/l8_1.html
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>     //added this to the original
#include <signal.h>
#include <sys/types.h>

void int_handler(int sig)
{
  printf("Ouch - shot in the ...\n");
  exit(2);
}

int main(int argc, char *argv[])
{
  pid_t pid;

  if ((pid = fork()) < 0)
  {
    fprintf(stderr,
            "fork failed\n");
    exit(1);
  }

  if (pid == 0)
  {
    signal(SIGINT, int_handler);
    while (1)
      printf("Running amok!!!\n");
  }

  sleep(3);
  kill(pid, SIGINT);
  exit(0);
}

References