Χειρισμός σημάτων - %SIG#
Η %SIG είναι το hash όπου η Perl εκθέτει τους χειριστές σημάτων. Η ρύθμιση $SIG{NAME} εγκαθιστά χειριστή για το σήμα NAME· η διαγραφή του επαναφέρει την προεπιλεγμένη ενέργεια. Δύο ψευδο-σήματα - __WARN__ και __DIE__ - δεν είναι καθόλου πραγματικά σήματα OS αλλά hooks του διερμηνέα στις warn και die.
Ο πίνακας σημάτων#
Τα πραγματικά σήματα OS προέρχονται από τις λίστες POSIX και signal.h. Τα κοινά - κλειδιά που θα χρησιμοποιήσετε πραγματικά:
Κλειδί | Σήμα | Προεπιλεγμένη ενέργεια | Πότε το χειρίζεστε |
|---|---|---|---|
| SIGINT | τερματισμός | Ctrl-C: ομαλός τερματισμός |
| SIGTERM | τερματισμός |
|
| SIGHUP | τερματισμός | αποσύνδεση τερματικού· οι δαίμονες επαναφορτώνουν ρύθμιση |
| SIGQUIT | τερματισμός + core | Ctrl-: όπως INT αλλά με core dump |
| SIGPIPE | τερματισμός | εγγραφή σε κλειστό σωλήνα· συνήθως αγνοείται |
| SIGCHLD | αγνόηση | πέθανε μια διεργασία-παιδί ( |
| SIGUSR1 | τερματισμός | γεγονός ορισμένο από εφαρμογή |
| SIGUSR2 | τερματισμός | γεγονός ορισμένο από εφαρμογή |
| SIGALRM | τερματισμός | ο χρονομετρητής |
| SIGWINCH | αγνόηση | αλλαγή μεγέθους παραθύρου τερματικού |
| SIGIO | τερματισμός | ασύγχρονο I/O έτοιμο |
Η πλήρης λίστα - συμπεριλαμβανομένων σημάτων που δεν μπορείτε να πιάσετε (KILL, STOP) και σημάτων ειδικών πλατφόρμας - προέρχεται από το άρθρωμα POSIX, που εξάγει τις αριθμητικές σταθερές (SIGINT, SIGTERM, …) και τις μακροεντολές wait της οικογένειας WIFEXITED.
Εγκατάσταση χειριστή#
Γίνονται αποδεκτές τρεις μορφές τιμής χειριστή:
$SIG{INT} = \&handler; # code reference - preferred
$SIG{INT} = sub { ... }; # anonymous sub - also fine
$SIG{INT} = 'IGNORE'; # ignore the signal
$SIG{INT} = 'DEFAULT'; # restore default action
$SIG{INT} = ''; # same as DEFAULT
$SIG{INT} = undef; # same as DEFAULT
Μη χρησιμοποιείτε bareword ($SIG{INT} = 'handler') - αυτό αναφέρεται σε όνομα στο πακέτο main::, που η Perl αναζητά κάθε φορά που φτάνει το σήμα. Αν μετονομάσετε τη handler, η μορφή bareword σιωπηρά συνεχίζει να στοχεύει το παλιό όνομα.
Ο χειριστής λαμβάνει ένα όρισμα - το όνομα σήματος ως συμβολοσειρά:
sub handler {
my ($signame) = @_;
print "received SIG$signame\n";
}
$SIG{INT} = \&handler;
$SIG{TERM} = \&handler;
Ασφαλή σήματα - ο κανόνας αναβαλλόμενης παράδοσης#
Από την Perl 5.8, η παράδοση σημάτων είναι αναβαλλόμενη. Όταν φτάσει ένα σήμα, το OS θέτει μια σημαία στον διερμηνέα· ο πραγματικός χειριστής Perl εκτελείται μόνο στο επόμενο ασφαλές σημείο - τυπικά μεταξύ εκτελέσεων εντολών. Αυτό σημαίνει:
Ένας χειριστής δεν μπορεί να διακόψει μια
read,open,connect, ή οποιαδήποτε άλλη κλήση συστήματος στη μέση. Οι αργές κλήσεις συστήματος επιστρέφουν πρόωρα μεEINTRστη$!και ο χειριστής εκτελείται μετά.Ένας χειριστής δεν μπορεί να διακόψει μια αντιστοίχιση regex σε εξέλιξη. Ένα μακροχρόνιο regex σε κακόβουλη είσοδο δεν θα κοπεί σύντομα από SIGINT.
Ο χειριστής εκτελείται σε κανονικό περιβάλλον Perl. Μπορεί να καλέσει οποιονδήποτε κώδικα Perl, να δεσμεύσει μνήμη, να εκτοξεύσει εξαιρέσεις - κανένας κίνδυνος επανεισόδου χειριστών σημάτων C.
Αυτή είναι η σωστή ισορροπία για σχεδόν κάθε πρόγραμμα. Αν πραγματικά χρειάζεστε χειρισμό σημάτων μη ασφαλή προ-5.8 - χειριστές που εκτελούνται σύγχρονα στο περιβάλλον σήματος, με όλους τους κινδύνους επανεισόδου - θέστε τη μεταβλητή περιβάλλοντος PERL_SIGNALS=unsafe πριν ξεκινήσετε την Perl. Μην το κάνετε αυτό αβασάνιστα.
Η PetaPerl υλοποιεί χειρισμό σημάτων αναβαλλόμενης παράδοσης.
Το μοτίβο ομαλού τερματισμού#
Η τυπική μορφή: μια σημαία που τίθεται από τον χειριστή, ελέγχεται από τον κύριο βρόχο:
my $shutting_down = 0;
$SIG{INT} = sub { $shutting_down = 1 };
$SIG{TERM} = sub { $shutting_down = 1 };
while (my $job = $queue->next) {
last if $shutting_down; # check at a safe point
process($job);
}
if ($shutting_down) {
log_and_close();
exit 0;
}
Ο κύριος βρόχος ελέγχει τη σημαία σε σημείο όπου είναι ασφαλές να ξετυλιχθεί καθαρά. Ο χειριστής κάνει μόνο «θέτω τη σημαία» - χωρίς καταγραφή, χωρίς I/O - ελαχιστοποιώντας τον χρόνο εκτέλεσής του.
Για εφαρμογές που μπλοκάρουν σε μεγάλη κλήση συστήματος (accept, read, select), ο βρόχος πρέπει να δομηθεί ώστε να επαναλαμβάνει σε EINTR:
my $shutting_down = 0;
$SIG{INT} = sub { $shutting_down = 1 };
while (!$shutting_down) {
my $client = accept($srv, $sock);
unless (defined $client) {
next if $!{EINTR}; # signal woke us up - re-check flag
die "accept: $!";
}
handle($client);
}
Η $!{EINTR} (δείτε μεταβλητές σφάλματος) σας λέει ότι η κλήση συστήματος εξήλθε επειδή έφτασε ένα σήμα. Η σημαία τέθηκε από τον χειριστή· ο κύριος βρόχος βλέπει την αφύπνιση, ξαναελέγχει τη $shutting_down, και εξέρχεται καθαρά.
Συγκομιδή διεργασιών-παιδιών - SIGCHLD#
Όταν μια διεργασία-παιδί εξέρχεται, ο γονέας λαμβάνει SIGCHLD. Ο χειριστής πρέπει να καλέσει τη waitpid για να συγκομίσει το zombie:
$SIG{CHLD} = sub {
while ((my $kid = waitpid(-1, WNOHANG)) > 0) {
my $status = $?;
log_child_death($kid, $status);
}
};
Ο βρόχος while με WNOHANG (από τη POSIX :sys_wait_h) χειρίζεται την περίπτωση όπου δύο παιδιά εξέρχονται κοντά μεταξύ τους - παραδίδεται μόνο ένα SIGCHLD (δεν μπαίνει σε ουρά), οπότε ο χειριστής πρέπει να εκκενώσει όλες τις εκκρεμείς εξόδους κάθε φορά που εκτελείται.
Η $SIG{CHLD} = 'IGNORE' είναι ειδική περίπτωση: δεν αγνοεί απλώς το σήμα, λέει στον πυρήνα να συγκομίζει αυτόματα τα παιδιά. Χρησιμοποιήστε το όταν το σενάριό σας κάνει fork παιδιά των οποίων η κατάσταση εξόδου δεν σας ενδιαφέρει.
SIGPIPE - η περίπτωση κλειστού σωλήνα#
Όταν κάνετε print σε σωλήνα του οποίου ο αναγνώστης έχει φύγει, ο πυρήνας στέλνει SIGPIPE. Η προεπιλεγμένη ενέργεια είναι τερματισμός της διεργασίας - κάτι που σπάνια θέλετε. Τα περισσότερα προγράμματα φίλτρου πρέπει να αγνοούν SIGPIPE και να ελέγχουν αντ” αυτού την τιμή επιστροφής print:
local $SIG{PIPE} = 'IGNORE';
print {$child_pipe} $data
or die "write to pipe: $!"; # $! will be EPIPE if the reader closed
Με αγνοημένο τον χειριστή, η κλήση συστήματος επιστρέφει αποτυχία με EPIPE αντί να σκοτώσει το πρόγραμμα.
Χρονικά όρια μέσω SIGALRM#
Σε συνδυασμό με τη alarm και ένα μπλοκ eval, ο SIGALRM παρέχει φορητό χρονικό όριο για κάθε λειτουργία που μπλοκάρει:
sub with_timeout {
my ($seconds, $code) = @_;
my $result;
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm $seconds;
$result = $code->();
alarm 0; # cancel the timer
1;
} or do {
die $@ unless $@ eq "TIMEOUT\n";
return undef;
};
return $result;
}
my $line = with_timeout(5, sub { <$slow_socket> });
Το μοτίβο:
local $SIG{ALRM}- ο χειριστής έχει εμβέλεια μόνο για αυτή την κλήση.alarm $seconds- προγραμματισμός του σήματος.Εκτέλεση της εργασίας που μπλοκάρει.
alarm 0- ακύρωση του χρονομετρητή αν η εργασία πέτυχε.Μέσα στον χειριστή,
die "TIMEOUT\n"- η τελική\nκαταστέλλει το επίθεμα «at line N» και κάνει την τιμή σφάλματος εύκολη στον έλεγχο.
__WARN__ και __DIE__ - hooks διερμηνέα#
Αυτά τα δύο κλειδιά στη %SIG δεν είναι σήματα OS. Καλούνται όταν ο διερμηνέας πρόκειται να εκπέμψει μια προειδοποίηση (warn) ή να εκτοξεύσει μια μοιραία εξαίρεση (die).
$SIG{__WARN__} - υποκλοπή προειδοποιήσεων#
my @warnings;
{
local $SIG{__WARN__} = sub {
my ($msg) = @_;
push @warnings, $msg;
};
do_thing_that_might_warn();
}
# Now @warnings holds every warning that fired inside the block,
# without anything having printed to STDERR.
Ένας χειριστής __WARN__ που δεν κάνει τίποτα αποσιωπά εντελώς την προειδοποίηση. Ένας χειριστής που καλεί die αναβαθμίζει τις προειδοποιήσεις σε εξαιρέσεις:
local $SIG{__WARN__} = sub { die $_[0] };
eval { something_warny() }; # warns become catchable
Η λεξιλογική pragma use warnings FATAL => 'all' είναι πιο στοχευμένος τρόπος να γίνει αυτό - κάνει τις προειδοποιήσεις μοιραίες μόνο στη λεξιλογική εμβέλεια όπου η pragma είναι ενεργή. Χρησιμοποιήστε τη αντί για __WARN__ για απαιτήσεις «αντιμετώπισε τις προειδοποιήσεις ως σφάλματα».
$SIG{__DIE__} - υποκλοπή die#
Ένας χειριστής __DIE__ εκτελείται όταν η die πρόκειται να εκτοξεύσει - τόσο για μη πιασμένες εξαιρέσεις όσο και μέσα σε eval. Μέσα σε eval, ο χειριστής εκτελείται πριν πιαστεί η εξαίρεση, κάτι που είναι η κλασική πηγή σύγχυσης: ένας καθολικός χειριστής __DIE__ που καταγράφει κάθε «θάνατο» θα ενεργοποιηθεί σε κάθε eval { ... } που καλεί die για κανονική ροή ελέγχου.
$SIG{__DIE__} = sub { print STDERR "dying: $_[0]" };
eval { die "expected error" }; # the handler still runs!
Αν θέλετε καθολική καταγραφή θανάτων, φιλτράρετε μέσω $^S:
$SIG{__DIE__} = sub {
return if $^S; # inside eval - let it be caught
print STDERR "uncaught: $_[0]";
};
Ρεαλιστικά, αποφύγετε τη $SIG{__DIE__} σε νέο κώδικα. Η pragma use warnings και οι μεταβλητές σφάλματος καλύπτουν ό,τι χρειάζεστε χωρίς εκπλήξεις. Το σύγχρονο χαρακτηριστικό try/catch χειρίζεται εξαιρέσεις ρητά χωρίς τη δράση εξ αποστάσεως της $SIG{__DIE__}.
Εμφάνιση διαθέσιμων σημάτων#
use Config;
my @signals = split / /, $Config{sig_name};
print "available signals: @signals\n";
# HUP INT QUIT ILL TRAP ABRT BUS FPE KILL ...
Ή, για την αντιστοίχιση αριθμός→όνομα:
use Config;
my @names = split / /, $Config{sig_name};
my @nums = split / /, $Config{sig_num};
my %sig_num = map { $names[$_] => $nums[$_] } 0..$#names;
my %sig_name = map { $nums[$_] => $names[$_] } 0..$#names;
Αυτό κάνουν εσωτερικά τα περισσότερα εργαλεία εμφάνισης σημάτων.
Δείτε επίσης#
POSIX- η κανονική πηγή αριθμητικών σταθερώνSIG*και μακροεντολώνWIFEXITED/WEXITSTATUSγια αποκωδικοποίηση κατάστασης παιδιού σε χειριστή SIGCHLD.kill- ο σύντροφος της%SIG: αποστολή σήματος σε διεργασία (τυπικά για δοκιμή του δικού σας χειριστή).alarm- προγραμματισμός SIGALRM· η βάση μοτίβων χρονικού ορίου.waitpid- συγκομιδή παιδιού μετά από SIGCHLD.fork- παράγει τα παιδιά των οποίων τους θανάτους αναφέρει η SIGCHLD.Μεταβλητές σφάλματος - η
$!μετά απόEINTR, η$?μετά απόwaitpid, η$@μετά απόdieμέσα από χειριστή.Ενδοσκόπηση διερμηνέα - η
$^Sγια τον έλεγχο__DIE__«βρισκόμαστε μέσα σεeval».try/catch- η σύγχρονη εναλλακτική στον χειρισμό εξαιρέσεων βασισμένο σε$SIG{__DIE__}.