Приглашаем посетить
Вересаев (veresaev.lit-info.ru)

Section 14.7.  Sending and Receiving Signals

Previous
Table of Contents
Next

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 outthe bridge collapsed," "the light has changedget going," "stop drivingyou've got a baby on the roof," or "hello, world"? Fortunately, Unix signals are easier to interpret than that because there's a different one for each of these situations.[*] 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 Ctrl-C) on the terminal, which sends a SIGINT to all the processes attached to that terminal.[Section 14.7.  Sending and Receiving Signals] Some signals are sent automatically by the system, but they can come from another process.

[*] 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).

[Section 14.7.  Sending and Receiving Signals] And you thought that pressing Ctrl-C stopped your program. Actually, it simply sends the SIGINT signal, and that stops the program by default. As you'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,[Section 14.7.  Sending and Receiving Signals] but say you know that you want to send a SIGINT to process 4201. That's easy enough:

[Section 14.7.  Sending and Receiving Signals] 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 use the string 'INT' in place of the 2 there because signal number 2 is SIGINT. If the process no longer exists,[*] you'll get a false return value, so you can use this technique to see if a process is alive. A special signal number of 0 says, "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." A process probe might look like:

[*] 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 more interesting than sending signals is catching signals. Why might you want to do this? 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 Ctrl-C during the execution, that leaves trash in /tmp, an impolite 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 someone presses Ctrl-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[Section 14.7.  Sending and Receiving Signals] naming the subroutine without the ampersand. From then on, if a SIGINT comes along, Perl will stop whatever it's doing and jump to the subroutine. Our subroutine cleans up the temp files and exits. (If nobody presses Ctrl-C, we'll still call &clean_up at the end of normal execution.)

[Section 14.7.  Sending and Receiving Signals] The value can be a subroutine reference, but we're not doing those here.

If the subroutine returns rather than exiting, execution will resume right where it was interrupted. This can be useful if the interrupt needs to interrupt something rather than causing it to stop. For example, suppose processing each line of a file takes a few seconds, which is slow, and you want to abort the overall processing when an interrupt is processed but not in the middle of processing a line. 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;
      }
    }

As each line is processed, the value of $int_count will be 0 if no one has pressed Ctrl-C, 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 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,[*] however, so keep the stuff you're doing in there to a minimum, or your program may end up blowing up sometime when you least expect it.

[*] This is one of the top items on the Perl developers' list of things to fix, 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. For example, if Perl is in the middle of allocating some memory when a signal comes in, the signal handler can accidentally 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.

    Previous
    Table of Contents
    Next