For lack of a better analogy, signals are a way to poke a stick at a process. Programs generate signals to trigger a handler for that signal in another process. The operating system pokes toosome signals are generated on unusual system events and may kill the program if not handled. If this sounds a little like raising exceptions in Python, it should; signals are software-generated events and the cross-process analog of exceptions. Unlike exceptions, though, signals are identified by number, are not stacked, and are really an asynchronous event mechanism outside the scope of the Python interpreter controlled by the operating system.
In order to make signals available to scripts, Python provides a signal module that allows Python programs to register Python functions as handlers for signal events. This module is available on both Unix-like platforms and Windows (though the Windows version defines fewer kinds of signals to be caught). To illustrate the basic signal interface, the script in Example 5-21 installs a Python handler function for the signal number passed in as a command-line argument.
Example 5-21. PP3E\System\Processes\signal1.py
There are only two signal module calls at work here:
Here is what this script looks like running on Linux: a signal number to watch for (12) is passed in on the command line, and the program is made to run in the background with an & shell operator (available in most Unix-like shells):
[mark@toy]$ python signal1.py 12 &  809 [mark@toy]$ ps PID TTY TIME CMD 578 ttyp1 00:00:00 tcsh 809 ttyp1 00:00:00 python 810 ttyp1 00:00:00 ps [mark@toy]$ kill -12 809 [mark@toy]$ Got signal 12 at Fri Sep 8 00:27:01 2000 kill -12 809 [mark@toy]$ Got signal 12 at Fri Sep 8 00:27:03 2000 kill -12 809 [mark@toy]$ Got signal 12 at Fri Sep 8 00:27:04 2000 [mark@toy]$ kill -9 809 # signal 9 always kills the process
Inputs and outputs are a bit jumbled here because the process prints to the same screen used to type new shell commands. To send the program a signal, the kill shell command takes a signal number and a process ID to be signaled (809); every time a new kill command sends a signal, the process replies with a message generated by a Python signal handler function.
The signal module also exports a signal.alarm function for scheduling a SIGALRM signal to occur at some number of seconds in the future. To trigger and catch timeouts, set the alarm and install a SIGALRM handler as shown in Example 5-22.
Example 5-22. PP3E\System\Processes\signal2.py
Running this script on Linux causes its onSignal handler function to be invoked every five seconds:
[mark@toy]$ python signal2.py Setting at Fri Sep 8 00:27:53 2000 Got alarm 14 at Fri Sep 8 00:27:58 2000 Setting at Fri Sep 8 00:27:58 2000 Got alarm 14 at Fri Sep 8 00:28:03 2000 Setting at Fri Sep 8 00:28:03 2000 Got alarm 14 at Fri Sep 8 00:28:08 2000 Setting at Fri Sep 8 00:28:08 2000
Generally speaking, signals must be used with cautions not made obvious by the examples we've just seen. For instance, some system calls don't react well to being interrupted by signals, and only the main thread can install signal handlers and respond to signals in a multithreaded program.
When used well, though, signals provide an event-based communication mechanism. They are less powerful than data streams such as pipes, but are sufficient in situations in which you just need to tell a program that something important has occurred and don't need to pass along any details about the event itself. Signals are sometimes also combined with other IPC tools. For example, an initial signal may inform a program that a client wishes to communicate over a named pipethe equivalent of tapping someone's shoulder to get their attention before speaking. Most platforms reserve one or more SIGUSR signal numbers for user-defined events of this sort. Such an integration structure is sometimes an alternative to running a blocking input call in a spawned thread.
See also the os.kill(pid, sig) call for sending signals to known processes from within a Python script on Unix-like platforms (the required process ID can be obtained from the os.fork call's child process ID return value or from other interfaces). Also watch for the discussion about using signal handlers to clean up zombie processes in the Internet scripting part later in this book.