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:
- 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.
- The CPU looks up the OS interrupt service routine in the interrupt table.
- 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.
- 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:
- SIGSEGV, segmentation fault, is delivered when your
program accesses an out-of-bounds memory address. If you manipulate the
memory map, you can actually resume from this signal.
- SIGFPE, floating-point (or arithmetic) exception, is
delivered when you divide by zero or encounter a problem with
floating-point (like a signalling NaN).
- SIGILL, illegal instruction, is delivered when your
program hits an invalid instruction, usually caused by overwriting your
own code or jumping to a bad function pointer.
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.