📄 perlipc.pod
字号:
=head1 NAMEperlipc - Perl interprocess communication (signals, fifos, pipes, safe subprocesses, sockets, and semaphores)=head1 DESCRIPTIONThe basic IPC facilities of Perl are built out of the good old Unixsignals, named pipes, pipe opens, the Berkeley socket routines, and SysVIPC calls. Each is used in slightly different situations.=head1 SignalsPerl uses a simple signal handling model: the %SIG hash contains names orreferences of user-installed signal handlers. These handlers will be calledwith an argument which is the name of the signal that triggered it. Asignal may be generated intentionally from a particular keyboard sequence likecontrol-C or control-Z, sent to you from another process, ortriggered automatically by the kernel when special events transpire, likea child process exiting, your process running out of stack space, orhitting file size limit.For example, to trap an interrupt signal, set up a handler like this.Do as little as you possibly can in your handler; notice how all we do isset a global variable and then raise an exception. That's because on mostsystems, libraries are not re-entrant; particularly, memory allocation andI/O routines are not. That means that doing nearly I<anything> in yourhandler could in theory trigger a memory fault and subsequent core dump. sub catch_zap { my $signame = shift; $shucks++; die "Somebody sent me a SIG$signame"; } $SIG{INT} = 'catch_zap'; # could fail in modules $SIG{INT} = \&catch_zap; # best strategyThe names of the signals are the ones listed out by C<kill -l> on yoursystem, or you can retrieve them from the Config module. Set up an@signame list indexed by number to get the name and a %signo tableindexed by name to get the number: use Config; defined $Config{sig_name} || die "No sigs?"; foreach $name (split(' ', $Config{sig_name})) { $signo{$name} = $i; $signame[$i] = $name; $i++; }So to check whether signal 17 and SIGALRM were the same, do just this: print "signal #17 = $signame[17]\n"; if ($signo{ALRM}) { print "SIGALRM is $signo{ALRM}\n"; }You may also choose to assign the strings C<'IGNORE'> or C<'DEFAULT'> asthe handler, in which case Perl will try to discard the signal or do thedefault thing.On most Unix platforms, the C<CHLD> (sometimes also known as C<CLD>) signalhas special behavior with respect to a value of C<'IGNORE'>.Setting C<$SIG{CHLD}> to C<'IGNORE'> on such a platform has the effect ofnot creating zombie processes when the parent process fails to C<wait()>on its child processes (i.e. child processes are automatically reaped).Calling C<wait()> with C<$SIG{CHLD}> set to C<'IGNORE'> usually returnsC<-1> on such platforms.Some signals can be neither trapped nor ignored, such asthe KILL and STOP (but not the TSTP) signals. One strategy fortemporarily ignoring signals is to use a local() statement, which will beautomatically restored once your block is exited. (Remember that local()values are "inherited" by functions called from within that block.) sub precious { local $SIG{INT} = 'IGNORE'; &more_functions; } sub more_functions { # interrupts still ignored, for now... }Sending a signal to a negative process ID means that you send the signalto the entire Unix process-group. This code sends a hang-up signal to allprocesses in the current process group (and sets $SIG{HUP} to IGNORE soit doesn't kill itself): { local $SIG{HUP} = 'IGNORE'; kill HUP => -$$; # snazzy writing of: kill('HUP', -$$) }Another interesting signal to send is signal number zero. This doesn'tactually affect another process, but instead checks whether it's aliveor has changed its UID. unless (kill 0 => $kid_pid) { warn "something wicked happened to $kid_pid"; }You might also want to employ anonymous functions for simple signalhandlers: $SIG{INT} = sub { die "\nOutta here!\n" };But that will be problematic for the more complicated handlers that needto reinstall themselves. Because Perl's signal mechanism is currentlybased on the signal(3) function from the C library, you may sometimes be somisfortunate as to run on systems where that function is "broken", thatis, it behaves in the old unreliable SysV way rather than the newer, morereasonable BSD and POSIX fashion. So you'll see defensive people writingsignal handlers like this: sub REAPER { $waitedpid = wait; # loathe sysV: it makes us not only reinstate # the handler, but place it after the wait $SIG{CHLD} = \&REAPER; } $SIG{CHLD} = \&REAPER; # now do something that forks...or even the more elaborate: use POSIX ":sys_wait_h"; sub REAPER { my $child; while (($child = waitpid(-1,WNOHANG)) > 0) { $Kid_Status{$child} = $?; } $SIG{CHLD} = \&REAPER; # still loathe sysV } $SIG{CHLD} = \&REAPER; # do something that forks...Signal handling is also used for timeouts in Unix, While safelyprotected within an C<eval{}> block, you set a signal handler to trapalarm signals and then schedule to have one delivered to you in somenumber of seconds. Then try your blocking operation, clearing the alarmwhen it's done but not before you've exited your C<eval{}> block. If itgoes off, you'll use die() to jump out of the block, much as you mightusing longjmp() or throw() in other languages.Here's an example: eval { local $SIG{ALRM} = sub { die "alarm clock restart" }; alarm 10; flock(FH, 2); # blocking write lock alarm 0; }; if ($@ and $@ !~ /alarm clock restart/) { die }If the operation being timed out is system() or qx(), this techniqueis liable to generate zombies. If this matters to you, you'llneed to do your own fork() and exec(), and kill the errant child process.For more complex signal handling, you might see the standard POSIXmodule. Lamentably, this is almost entirely undocumented, butthe F<t/lib/posix.t> file from the Perl source distribution has someexamples in it.=head1 Named PipesA named pipe (often referred to as a FIFO) is an old Unix IPCmechanism for processes communicating on the same machine. It worksjust like a regular, connected anonymous pipes, except that theprocesses rendezvous using a filename and don't have to be related.To create a named pipe, use the Unix command mknod(1) or on somesystems, mkfifo(1). These may not be in your normal path. # system return val is backwards, so && not || # $ENV{PATH} .= ":/etc:/usr/etc"; if ( system('mknod', $path, 'p') && system('mkfifo', $path) ) { die "mk{nod,fifo} $path failed"; }A fifo is convenient when you want to connect a process to an unrelatedone. When you open a fifo, the program will block until there's somethingon the other end.For example, let's say you'd like to have your F<.signature> file be anamed pipe that has a Perl program on the other end. Now every time anyprogram (like a mailer, news reader, finger program, etc.) tries to readfrom that file, the reading program will block and your program willsupply the new signature. We'll use the pipe-checking file test B<-p>to find out whether anyone (or anything) has accidentally removed our fifo. chdir; # go home $FIFO = '.signature'; $ENV{PATH} .= ":/etc:/usr/games"; while (1) { unless (-p $FIFO) { unlink $FIFO; system('mknod', $FIFO, 'p') && die "can't mknod $FIFO: $!"; } # next line blocks until there's a reader open (FIFO, "> $FIFO") || die "can't write $FIFO: $!"; print FIFO "John Smith (smith\@host.org)\n", `fortune -s`; close FIFO; sleep 2; # to avoid dup signals }=head2 WARNINGBy installing Perl code to deal with signals, you're exposing yourselfto danger from two things. First, few system library functions arere-entrant. If the signal interrupts while Perl is executing one function(like malloc(3) or printf(3)), and your signal handler then calls thesame function again, you could get unpredictable behavior--often, acore dump. Second, Perl isn't itself re-entrant at the lowest levels.If the signal interrupts Perl while Perl is changing its own internaldata structures, similarly unpredictable behaviour may result.There are two things you can do, knowing this: be paranoid or bepragmatic. The paranoid approach is to do as little as possible in yoursignal handler. Set an existing integer variable that already has avalue, and return. This doesn't help you if you're in a slow system call,which will just restart. That means you have to C<die> to longjump(3) outof the handler. Even this is a little cavalier for the true paranoiac,who avoids C<die> in a handler because the system I<is> out to get you.The pragmatic approach is to say ``I know the risks, but prefer theconvenience'', and to do anything you want in your signal handler,prepared to clean up core dumps now and again.To forbid signal handlers altogether would bars you frommany interesting programs, including virtually everything in this manpage,since you could no longer even write SIGCHLD handlers. =head1 Using open() for IPCPerl's basic open() statement can also be used for unidirectional interprocesscommunication by either appending or prepending a pipe symbol to the secondargument to open(). Here's how to start something up in a child process youintend to write to: open(SPOOLER, "| cat -v | lpr -h 2>/dev/null") || die "can't fork: $!"; local $SIG{PIPE} = sub { die "spooler pipe broke" }; print SPOOLER "stuff\n"; close SPOOLER || die "bad spool: $! $?";And here's how to start up a child process you intend to read from: open(STATUS, "netstat -an 2>&1 |") || die "can't fork: $!"; while (<STATUS>) { next if /^(tcp|udp)/; print; } close STATUS || die "bad netstat: $! $?";If one can be sure that a particular program is a Perl script that isexpecting filenames in @ARGV, the clever programmer can write somethinglike this: % program f1 "cmd1|" - f2 "cmd2|" f3 < tmpfileand irrespective of which shell it's called from, the Perl program willread from the file F<f1>, the process F<cmd1>, standard input (F<tmpfile>in this case), the F<f2> file, the F<cmd2> command, and finally the F<f3>file. Pretty nifty, eh?You might notice that you could use backticks for much thesame effect as opening a pipe for reading: print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`; die "bad netstat" if $?;While this is true on the surface, it's much more efficient to process thefile one line or record at a time because then you don't have to read thewhole thing into memory at once. It also gives you finer control of thewhole process, letting you to kill off the child process early if you'dlike.Be careful to check both the open() and the close() return values. Ifyou're I<writing> to a pipe, you should also trap SIGPIPE. Otherwise,think of what happens when you start up a pipe to a command that doesn'texist: the open() will in all likelihood succeed (it only reflects thefork()'s success), but then your output will fail--spectacularly. Perlcan't know whether the command worked because your command is actuallyrunning in a separate process whose exec() might have failed. Therefore,while readers of bogus commands return just a quick end of file, writersto bogus command will trigger a signal they'd better be prepared tohandle. Consider: open(FH, "|bogus") or die "can't fork: $!"; print FH "bang\n" or die "can't write: $!"; close FH or die "can't close: $!";That won't blow up until the close, and it will blow up with a SIGPIPE.To catch it, you could use this: $SIG{PIPE} = 'IGNORE'; open(FH, "|bogus") or die "can't fork: $!"; print FH "bang\n" or die "can't write: $!"; close FH or die "can't close: status=$?";=head2 FilehandlesBoth the main process and any child processes it forks share the sameSTDIN, STDOUT, and STDERR filehandles. If both processes try to accessthem at once, strange things can happen. You may also want to closeor reopen the filehandles for the child. You can get around this byopening your pipe with open(), but on some systems this means that thechild process cannot outlive the parent.=head2 Background ProcessesYou can run a command in the background with: system("cmd &");The command's STDOUT and STDERR (and possibly STDIN, depending on yourshell) will be the same as the parent's. You won't need to catchSIGCHLD because of the double-fork taking place (see below for moredetails).=head2 Complete Dissociation of Child from ParentIn some cases (starting server processes, for instance) you'll want tocompletely dissociate the child process from the parent. This isoften called daemonization. A well behaved daemon will also chdir()to the root directory (so it doesn't prevent unmounting the filesystemcontaining the directory from which it was launched) and redirect itsstandard file descriptors from and to F</dev/null> (so that randomoutput doesn't wind up on the user's terminal). use POSIX 'setsid'; sub daemonize { chdir '/' or die "Can't chdir to /: $!"; open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; defined(my $pid = fork) or die "Can't fork: $!"; exit if $pid; setsid or die "Can't start a new session: $!"; open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; }The fork() has to come before the setsid() to ensure that you aren't aprocess group leader (the setsid() will fail if you are). If yoursystem doesn't have the setsid() function, open F</dev/tty> and use theC<TIOCNOTTY> ioctl() on it instead. See L<tty(4)> for details.Non-Unix users should check their Your_OS::Process module for othersolutions.=head2 Safe Pipe OpensAnother interesting approach to IPC is making your single program gomultiprocess and communicate between (or even amongst) yourselves. Theopen() function will accept a file argument of either C<"-|"> or C<"|-">to do a very interesting thing: it forks a child connected to thefilehandle you've opened. The child is running the same program as theparent. This is useful for safely opening a file when running under anassumed UID or GID, for example. If you open a pipe I<to> minus, you canwrite to the filehandle you opened and your kid will find it in hisSTDIN. If you open a pipe I<from> minus, you can read from the filehandleyou opened whatever your kid writes to his STDOUT. use English; my $sleep_count = 0; do { $pid = open(KID_TO_WRITE, "|-"); unless (defined $pid) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -