Sending and Receiving Signals

14.7 Sending and Receiving Signals

A Unix signal is a tiny message sent to a process. It can't say much; it's like a car horn honking does that honk you hear mean "look out the bridge collapsed" or "the light has changed get going" or "stop driving you've got a baby on the roof" or "hello, world"? Well, fortunately, Unix signals are a little easier to interpret than that, because there's a different one for each of these situations.[22]

[22] Well, not exactly these situations, but analogous Unix-like ones. For these, the signals are SIGHUP, SIGCONT, SIGINT, and the fake SIGZERO (signal number zero).

Different signals are identified by a name (such as SIGINT, meaning "interrupt signal") and a corresponding small integer (in the range from 1 to 16, 1 to 32, or 1 to 63, depending on your Unix flavor). Signals are typically sent when a significant event happens, such as pressing the interrupt character (typically Control-C) on the terminal, which sends a SIGINT to all the processes attached to that terminal.[23] Some signals are sent automatically by the system, but they can also come from another process.

[23] And you thought that pressing Control-C stopped your program. Actually, it simply sends the SIGINT signal, and that stops the program by default. As we'll see later in this chapter, you can make a program that does something different when SIGINT comes in, rather than stopping at once.

You can send signals from your Perl process to another process, but you have to know the target's process ID number. How to figure that out is a bit complicated,[24] but let's say you know that you want to send a SIGINT to process 4201. That's easy enough:

[24] Usually you have the process ID because it's a child process you produced with fork, or you found it in a file or from an external program. Using an external program can be difficult and problematic, which is why many long-running programs save their own current process ID into a file, usually described in the program's documentation.

kill 2, 4201 or die "Cannot signal 4201 with SIGINT: $!";

It's named "kill" because one of the primary purposes of signals is to stop a process that's gone on long enough. You can also use the string 'INT' in place of the 2 there, because signal number 2 is SIGINT. If the process no longer exists,[25] you'll get a false return value, so you can also use this technique to see whether a process is still alive. A special signal number of 0 says "just check to see whether I could send a signal if I wanted to, but I don't want to, so don't actually send anything." So a process probe might look like:

[25] Sending a signal will also fail if you're not the superuser and it's someone else's process. It would be rude to send SIGINT to someone else's programs, anyway.

unless (kill 0, $pid) {
  warn "$pid has gone away!";
}

Perhaps a little more interesting than sending signals is catching signals. Why might you want to do this? Well, suppose you have a program that creates files in /tmp, and you normally delete those files at the end of the program. If someone presses Control-C during the execution, that leaves trash in /tmp, a very unpolite thing to do. To fix this, create a signal handler that takes care of the cleanup:

my $temp_directory = "/tmp/myprog.$$"; # create files below here
mkdir $temp_directory, 0700 or die "Cannot create $temp_directory: $!";
 
sub clean_up {
  unlink glob "$temp_directory/*";
  rmdir $temp_directory;
}
 
sub my_int_handler {
  &clean_up;
  die "interrupted, exiting...\n";
}
 
$SIG{'INT'} = 'my_int_handler';
.
.  # Time passes, the program runs, creates some temporary
.  # files in the temp directory, maybe somone presses Control-C
.
# Now it's the end of normal execution
&clean_up;

The assignment into the special %SIG hash activates the handler (until revoked). The key is the name of the signal (without the constant SIG prefix), and the value is a string[26] naming the subroutine, without the ampersand. From then on, if a SIGINT comes along, Perl stops whatever it's doing and jumps immediately to the subroutine. Our subroutine cleans up the temp files and then exits. (And if nobody presses Control-C, we'll still call &clean_up at the end of normal execution.)

[26] The value can also be a subroutine reference, but we're not doing those here.

If the subroutine returns rather than exiting, execution resumes right where it was interrupted. This can be useful if the interrupt needs to actually interrupt something rather than causing it to stop. For example, suppose processing each line of a file takes a few seconds, which is pretty slow, and you want to abort the overall processing when an interrupt is processed, but not in the middle of processing a line. Just set a flag in the interrupt procedure, and check it at the end of each line's processing:

my $int_count;
sub my_int_handler { $int_count++ }
$SIG{'INT'} = 'my_int_handler';
...
$int_count = 0;
while (<SOMEFILE>) {
  ... some processing that takes a few seconds ...
  if ($int_count) {
  # interrupt was seen!
  print "[processing interrupted...]\n";
  last;
  }
}

Now as each line is processed, the value of $int_count will be 0 if no one has pressed Control-C, and so the loop continues to the next item. However, if an interrupt comes in, the interrupt handler increments the $int_count flag, breaking out of the loop when checked at the end.

So, you can either set a flag or break out of the program, and that covers most of what you'll need from catching signals. The current implementation of signal handlers is not entirely without faults,[27] however, so keep the stuff you're doing in there to an absolute minimum, or your program may end up blowing up sometime when you least expect it.

[27] This is one of the top items on the Perl developers' list of things to be fixed, so we expect reliable signal handling to be one of the first items on the new feature list for Perl 6. The problem is that a signal may come in at any time, even when Perl isn't ready for one. If Perl is (for example) in the middle of allocating some memory when a signal comes in, the signal handler can accidentally try to allocate some memory and your program is dead. You can't control when your Perl code will allocate memory, but XSUB code (usually written in C) can safely handle signals. See the Perl documentation for more information about this advanced topic.

 



Learning Perl
Learning Perl, 5th Edition
ISBN: 0596520107
EAN: 2147483647
Year: 2001
Pages: 205

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net