Signals

CS 321 Lecture, Dr. Lawlor

The Silberschatz book documents signals in section 4.4.3, and 21.9.1.

Signals: Interrupts for ordinary processes

Signals can be seen as a standardized interface for delivering interrupts to user programs. Exactly like interrupts, a signal handler is just a subroutine that gets called when something weird happens.

Overall signal delivery looks like this:

  1. Something causes an interrupt--a hardware device needs attention, or a program reads a bad memory address, divides by zero, executes an illegal or privileged instruction, etc.
  2. The CPU looks up the OS interrupt service routine in the interrupt table.
  3. The OS's interrupt service routine figures out if it can handle the interrupt, or if it should deliver the interrupt to a process as a signal.
  4. To deliver a signal, the OS essentially just calls your process's subroutine.
To set yourself up to receive signals (``add a signal handler''), you just call an operating system routine like signal. You pass in the name of the signal you want to receive, and a function to execute once the signal is received.  For example:
(Executable NetRun Link)
#include <signal.h>

void myHandler(int i)
{
printf("Sorry dude--you just hit signal %d\n",i);
exit(1);
}

int foo(void) {
int *badPointer=(int *)0;
printf("Installing signal handler\n");
signal(SIGSEGV,myHandler); /* <------------- */
printf("Signal handler installed. Segfaulting...\n");
(*badPointer)++;
printf("Back from segfault?!\n");
return 0;
}

Which on my machine prints out:

Installing signal handler
Signal handler installed. Segfaulting...
Sorry dude--you just hit signal 11

Signals are available on all POSIX operating systems (including Windows, Linux, Mac OS X), and include:

On UNIX machines, there's also a slightly more sophisticated interface called sigaction.

Signals can also be used to indicate that I/O is ready (SIGIO, enabled using ``fcntl''), that a timer has expired (SIGALRM, SIGPROF, or SIGVPROF, enabled using ``setitimer''), that the operating system wants you to shut down (SIGTERM, SIGQUIT, SIGKILL, all UNIX-specific), that various events have happened on the terminal (SIGHUP, SIGWINCH, SIGPIPE, SIGTTIN, SIGTTOU, all UNIX-specific), or for application-defined purposes (SIGUSR1/SIGUSR2, which must be sent explicitly).  See signal.h for the full list of signals.

Signals, exactly like interrupts, are hence a generic ``catch-all'' notification mechanism, used for a variety of unrelated tasks.

"Upcalls" and Function Pointers

Notice that the "signal" function above takes the name of one of your functions as a parameter.  This is a fairly common, and suprisingly handy technique.

You can tell C about a whole group of functions that take the same arguments and return types, and then "point" to one of those functions.  This is called a "function pointer", which in assembly is just the address of the code to run.  The ugliest part about function pointers (by far!) is the syntax--I recommend you always use a typedef to define a function pointer.  For example, here's how you make a new type "fn_ptr_t" that takes a short and returns an int:
    typedef int (*fn_ptr_t)(short param);
Now you can declare variables of type "fn_ptr_t", assign compatible functions to them, and finally call them:
int case0(short val) {return 0xd00dE0+val;}
int case1(short val) {return 0x373371;}
int case2(short val) {return 0xd00d02;}

int foo(void) {
typedef int (*fn_ptr_t)(short param); /* "fn_ptr_t" is a function pointer */
fn_ptr_t fn;
fn=case0;
if (read_input()) fn=case1;
return fn(3); /* call the function pointer */
}
(executable NetRun Link)

Function pointers are really handy when you need somebody else to call one of your routines--for example, when the OS needs to call your signal handler.