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 too—some 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. PP3ESystemProcessessignal1.py
########################################################################## # catch signals in Python; pass signal number N as a command-line arg, # use a "kill -N pid" shell command to send this process a signal; most # signal handlers restored by Python after caught (see network scripting # chapter for SIGCHLD details); on Windows, signal module is available, # but it defines only a few signal types there, and os.kill is missing; ########################################################################## import sys, signal, time def now(): return time.ctime(time.time( )) # current time string def onSignal(signum, stackframe): # python signal handler print 'Got signal', signum, 'at', now( ) # most handlers stay in effect signum = int(sys.argv[1]) signal.signal(signum, onSignal) # install signal handler while 1: signal.pause( ) # wait for signals (or: pass)
There are only two signal
module calls at work here:
signal.signal
Takes a signal number and function object and installs
that function to handle that signal number when it is raised.
Python automatically restores most signal handlers when signals
occur, so there is no need to recall this function within the
signal handler itself to reregister the handler. That is, except
for SIGCHLD
, a signal handler
remains installed until explicitly reset (e.g., by setting the
handler to SIG_DFL
to restore
default behavior, or to SIG_IGN
to ignore the signal).
SIGCHLD
behavior is platform
specific.
signal.pause
Makes the process sleep until the next signal is caught. A
time.sleep
call is similar
but doesn’t work with signals on my Linux box; it generates an
interrupted system call error. A busy while 1: pass
loop here would pause
the script too but may squander CPU resources.
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 &
[1] 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 2000kill -12 809
[mark@toy]$ Got signal 12 at Fri Sep 8 00:27:03 2000kill -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. PP3ESystemProcessessignal2.py
########################################################################## # set and catch alarm timeout signals in Python; time.sleep doesn't play # well with alarm (or signal in general in my Linux PC), so we call # signal.pause here to do nothing until a signal is received; ########################################################################## import sys, signal, time def now(): return time.ctime(time.time( )) def onSignal(signum, stackframe): # python signal handler print 'Got alarm', signum, 'at', now( ) # most handlers stay in effect while 1: print 'Setting at', now( ) signal.signal(signal.SIGALRM, onSignal) # install signal handler signal.alarm(5) # do signal in 5 seconds signal.pause( ) # wait for signals
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
pipe—the 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.
18.116.67.70