Chapter 2. processes, pipes and signals A process is a running program On multi-CPU machines,...
-
date post
22-Dec-2015 -
Category
Documents
-
view
217 -
download
2
Transcript of Chapter 2. processes, pipes and signals A process is a running program On multi-CPU machines,...
processes, pipes and signals
• A process is a running program• On multi-CPU machines, processes execute
simultaneously on separate CPUs.• On single-CPU machines, processes “appear” to run
simultaneously through time-sharing.• Most processes execute independent of one another;
sharing nothing.• multitasking - one purpose with multiple threads of
execution – implemented with cloning (fork()) and threads
process state machine
RUN
WAIT
IO Blocking
waiting for CPU time
running on CPU
blocking because ofIO request
time out
IO complete
IO request
taking its turn
fork():
• fork() is called with no arguments and returns a result code
fds
$pid = fork();if ($pid>0) { # parent} else { # child $ppid = getppid();}
$pid = fork();if ($pid>0) { # parent} else { # child $ppid = getppid();}
parent child
parent runsthis code
child runsthis code
shared
copydata
can potentially be usedto share information
signal one another
in child, $pidnow has newvalue
same program in both processes
fork() (2):
$pid = fork();
in parent, this isthe child PID
in child, this is 0; to get parent PID childexecutes $pid = getppid();
NOTE: In case of error, fork() returns undef and $! contains the appropriate error message
process group:
• All processes that fork() from the same ancestor belong to the same process group. The group PID is usually the parent process PID.
• You can fork() from a child and produce grandchildren
• You can fork() repeatedly and produce siblings
$processid = getpgrp([$pid]);
if $pid is not specified then getthe group for the current process.
sharing
• Each member of group shares whatever filehandles were open when their common ancestor forked.
• Shared filehandles can be used to exchange information between processes in the same group.
• Processes in the same group share STDIN, STDOUT and STDERR.
sharing files:
• fork() is called with no arguments and returns a result code
a shared file/filehandle
RW RW
parent childshared
copydata
book calls this “shar[ing] the current values”, p36
Simple program:
#!/usr/bin/perl# file: fork.pl
print “PID = $$\n”; # $$ is current process PIDmy $child = fork();die “Can’t fork: $!” unless defined $child;if ( $child > 0 ) { # parent since $child != 0 print “parent PID = $$, child PID = $child\n”;} else { # child since $child == 0 my $ppid = getppid(); print “child PID = $$, parent PID = $ppid\n”;}
defined() is a functionthat tests if a variablehas a value
which print statement prints out first? on Windows? on Linux?
Executing other programs:
• Executes a command from inside a perl program; the program blocks until the command completes and then continues executing at the next line
• $result == 0 if all went well; -1 if command couldn’t start or command failed. Look at $? for the error.
• None of the command’s STDOUT output is returned to the program but is sent rather to whatever STDOUT is currently writing to. This is relevant since you often want to process the STDOUT output that is generated by something called from a perl program.
$result = system(“command and arguments”);
system():
• echoes “hello, world!” to
terminal
• sends “hello, world!”
to the file fileout
#!/usr/bin/perl# system.plsystem(“echo ‘hello, world!”);
$ ./system.plhello, world!$
#!/usr/bin/perl# system.plsystem(“echo ‘hello, world!”);
$ ./system.pl > fileout$ cat fileouthello, world!$
system() 2:
#!/usr/bin/perl# system.pl$result = system(“echo ‘hello, world!”);printf $result . “\n”;$result = system(“cat nofile”);$err = $? >> 8;printf $result . “\n”;printf “error: $err\n”;
$ ./system.plhello, world!0cat: nofile: no such file or directory256error: 1$
$result == 0 if ok
error code comes as mostsignificant byte of two byteword; shift right 8 bits to seeactual value.
$result != 0 if commandfails
exec():
• With exec() you actually replace the existing program with a new one using the same PID.
• All data and “knowledge” about its past self are lost to the process; it becomes the program being execed.
exec() 2:
• use fork() and exec() to make one program startup and run another
fds
$pid = fork();if ($pid>0) { # parent} else { # child $err = exec(“command and args”);}
$pid = fork();if ($pid>0) { # parent} else { # child $err = exec(“command and args”);}
parent child
parent runsthis code
shared
entire processreplaced with“command andargs”
child runs this code
exec() 3:
#!/usr/bin/perl
my $child = fork();if ( $child == 0 ) { open(STDOUT,”>log.txt”) or die “open() error: $!”; exec (“ls –l”); die “exec error: $!”; }exit 1; # parent exits
the parent process exits immediately
we only execute this only if exec() fails
redirect STDOUT outputto log.txt
the newly execed programinherits my STDOUT
Problem: IS STDOUT redirectedin the parent process?
pipes:
• Used for communication between two processes running on the same host.
• The processes may be related (parent-child-sibling) or unrelated.
• The above opens a pipe between (from) the ls command and (to) the INFILE handle of this program.
• The above opens a pipe between (from) this program (to) the OUTFILE handle of the wc command.
open(INFILE, “ls –la | “);
open (OUTFILE, “ | wc -l”);
Using pipes (simple example):
#!/usr/bin/perl# fle: whos_there.pl
use strict;my %who; # accumulate login count
open(WHOFH, “who |”) or die “can’t open who: $!”;while ( <WHOFH> ) { next unless /^(\S+)/; $who{$1}++;}foreach (sort {$who{$b} <=> $who{$a}} keys %who) { printf “%10s %d\n”,$_, $who{$_};}close WHOFH or die “can’t close who: $!”;
non-whitespace tokenanchored at start of line
$1 == value of match found inside ( )
fancy way to sorta list of keys by hashvalue in ascending order
can’t use redirect sincewho is a command
read about sort() at perldoc perlfunc use $_ if foreach has no specifiedvariable
read perldoc perlvar about exit codes
backtick (`…`):
• is the same as $arguments = “-lart”;$ls_output = `ls $arguments`;
$arguments = “-lart”;open(INFILE, “ls $arguments |”);while (<INFILE>) { $ls_output .= $_;}
all stdout output readinto a single scalar
concatenate
backtick and stderr:
• ` … ` returns only STDOUT output; what about STDERR output?
• The above delivers both STDOUT and STDERR output to $ls_output.
$ls_output = `ls 2>&1`;
pipe():
• This opens a pipe; one can read from READHANDLE and write to WRITEHANDLE.
• One uncommon usage is to open a pipe (in fact, two) just before forking a child. The pipe and all handles are shared by both processes. Close handles to leave two unidirectional pipes.
$result = pipe(READHANDLE,WRITEHANDLE);
parent child
pipeRW
WR
pipeRW
WR
close close
pipe (in detail):
WRITER
READER
WRITER
READER
parent child
READER
WRITER
pipe
pipe
before fork()
after fork()
pipe() 2:
child1parent
pipeWRITER
child2
pipeWRITER READER
fibonacci()factorial()print while <READER>;
only one pipe
two child processes
single READER handle in parent
single WRITER handle shared by two siblings
facfib.pl:#!/usr/bin/perl# file: facfib.pl
use strict;my $arg = shift || 10;
pipe(READER,WRITER) or die “Can’t open pipe: $!”);
if ( fork() == 0 ) {# child 1 close READER; select WRITER; $| = 1; factorial($arg); exit 0;}if ( fork() == 0 ) {# child 2 close READER; select WRITER; $| = 1; fibonacci($arg); exit 0;}
at this point defaultoutput is WRITER
facfib.pl 2:
sub factorial { my $target = shift; for (my $result = 1, ,y $i = 1; $i <= $target; $i++) { print “factorial($i) => “ , $result *= $i , “\n”; }}
sub fibonacci { my $target = shift; my ($a,$b) = (1,0); for (my $i = 1; $i <= $target; $i++) { my $c = $a + $b; print “fibonacci($i) => $c\n”; ($a,$b) = ($b,$c); }}
list assignment
default outputis still WRITER
pipe() as an alternative to open():
• The problem with
• is that you can only read from the program and you are constrained by reading in a loop inside your program.
• What if you want to read from and write to the program?
• What if you want to program to go off on its own to do its own thing while you are busy doing something else?
open(READER,”myprog.pl $args |”);
Example 1:
• almost identical to
open(READER,”myprog.pl $args |”);
pipe(READER,WRITER) or die “pipe no good: $!”;my $child = fork();die “can’t fork: $!” unless defined $child;if ( $child == 0 ) { close READER; open(STDOUT, “>&WRITER”); exec myprog.pl $args; die “exec failed: $!”;}close WRITER;…
at this point myprog.pl is executed in the process spaceoriginally intended for the childprocess; it inherits the STDOUThandle, which points to WRITER.
myprog.pl doesn’texecute in parallelin the open() example
bidirectional pipes:
• pipes are created unidirectional;
• is illegal
• For true bidirectional communication we use socketpair() which is introduced in Chapter 4
open(FH, “| $cmd |”);
Is a filehandle a pipe or what?
• perl offers a file testing mechanism to determine many things about a file
Test Description
-p Is the filehandle a pipe?
-t Is the filehandle opened on a terminal?
-S Is the filehandle a socket?
examples:
• Test if we have closed STDIN for batch mode.
• Test if a handle is a pipe.
if ( -t STDIN ) { printf “STDIN not closed yet.\n”;}
print “Not a pipe\n” if ! –p FILEHANDLE;
PIPE Error:
• Reading: no problem
Suppose you are reading at one end of a pipe and your childis writing at the other end.
Now suppose the child closes its end of the pipe or even exits?
parent child
pipeRW
WR
now close the pipeor exit the program
when next you read,EOF will happen
PIPE Error:
• Writing: a problem
Suppose you are writing at one end of a pipe and your parentis reading at the other end.
Now suppose the parent closes its end of the pipe or even exits?
parent child
pipeRW
WR
when you try to writeyou get the Broken piperror
now close the pipeor exit
program example: #!/usr/bin/perl#file: write_ten.pluse strict;open(PIPE,”| read_three.pl”) or die “Can’t open pipe: $!”;select PIPE; $| = 1; select STDOUT;my $count = 0;for ( 1..10) { warn “Writing line $_\n”; print PIPE “This is line number $_\n”; $count++; sleep 1; # or select(undef,undef,undef,0.5)}close PIPE or die “Can’t close PIPE: $!”;print “Write $count lines of text\n”;
#!/usr/bin/perl# file: read_three.pl
use strict;for (1..3) { last unless defined ($line = <>); warn “read_three got: $line”}
after the 3rd write, the read_three.plprogram terminates and closes the PIPE. The output is
program output:
Writing line 1Read_three got: This is line number 1Writing line 2Read_three got: This is line number 2Writing line 3Read_three got: This is line number 3Writing line 4Broken pipe
tried to write to a pipe closed by read_three.pl;no line was successfully passed to the read_three.pl
this error is called aPipe Exception
PIPE Exception:
• A PIPE Exception results in a PIPE signal being delivered to the writing process.
• Processes that receive the PIPE signal terminate by default.
• This also happens when writing across a network to a remote program that has closed its end of the communication channel or exited.
• To deal programmatically with a PIPE signal you mist write a signal handler.
Signals:
• A signal is a message sent to your program by the OS when something “important” occurs.
• Processes can signal each other.
Examples: - the program asks a stdlib routine to divide by zero - a user tries to interrupt the program (other than ^C). - a sub-process terminated but it is not the end of the world
Examples: - when user hits ^C the shell sends a signal to the program - a process sends signals to itself - a user sends the USR1 signal to a program (using the a shell to send the signal) to have the program reread its configuration file w/o recycling.
Common Signals:
• There are 19 standard signals; each has associated with it a small integer and a name.
HUP 1 A Hangup detected
INT 2 A Interrupt from kybd
QUIT 3 A Quit from kybd
ILL 4 A Illegal instruction
ABRT 6 C abort
FPE 8 C Floating pt exception
KILL 9 AF Termination signal
USR1 10 A User defined #1
SEGV 11 C Invalid memory ref
USR2 12 A User defined #2
PIPE 13 A Write to pipe; no reader
ALRM 14 A Timer signal
TERM 15 A Termination signal
CHLD 17 B Child terminated
CONT 18 E Continue if stopped
STOP 19 DF Stop process
TSTP 20 D Stop typed at tty
TTIN 21 D tty input: background
TTOU 22 D tty output: background
Name Value
Notes:A: Default is terminate processB: Default is ignore signalC: Default is terminate process and dump coreD: Default is stop processE: Default is resume processF: Signal can not be caught or ignored
Notes Comment
Further Signal Comments:
• We won’t use all these signals (italics)• HUP happens when a program is running in a terminal that is killed.• INT is send when someone hits ^C.• TERM and KILL used by one process (eg, shell) to terminate
another. ^C sends TERM but you can disable the default behaviour by writing a TERM signal handler.
• PIPE is sent when a process writes to a closed (remote end) pipe or socket.
• ALRM used with alarm() to send a prearranged signal to yourself.• CHLD sent to parent when child process status changes (exit, stop,
continue).• STOP suspends the current process; resumed by CONT.• TSTP suspends current process from tty (^Z); resumed by CONT
and can be caught by your own signal handler.
Catching Signals:
• %SIG holds references all signal handlers (functions).
• You catch a signal yourself by assigning a value to any signal except $SIG{KILL} and $SIG{TERM}. This value references a small function.
$SIG{INT} references the current/default INT signal handler
Example:
#!/usr/bin/perl#file: interrupt.pl
use strict;my $interruptions = 0;$SIG{INT} = \&handle_interruptions;
while ($interruptions < 3) { print “I am sleeping.\n”; sleep(5); # blocks for 5 seconds}
sub &handle_interruptions { $interruptions++; warn “Don’t interrupt me. You’ve already interrupted \ me ${interruptions}x.”;}
replaces defaultbehaviour (terminate)with pedantic counter
you need the {} because you can’t write$interruptionsx.
this is a real/namedsubroutine. You can alsouse an anonymous subroutine (see next slide).
Anonymous Subroutine:
$SIG{INT} = sub {$interruptions++; warn “Don’t interrupt me. You’ve already interrupted \ me ${interruptions}x.”; };
Special Cases:
• Perl recognizes two special values for an interrupt handler – DEFAULT and IGNORE.
• DEFAULT restores the default behaviour• IGNORE tells perl to ignore this signal if received.
Handler Reuse:
$SIG{TERM} = $SIG{HUP} = $SIG{INT} = \&handler;
sub &handler { my $sig = shift; warn “handling a $sig signal.\n”;}
The signal number is obviously passedautomatically to any signal handler as its first argument.
PIPE Exceptions 1:
#!/usr/bin/perl#file: write_ten_ph.pluse strict;$SIG{PIPE} = sub { undef $ok; }open(PIPE,”| read_three.pl”) or die “Can’t open pipe: $!”;select PIPE; $| = 1; select STDOUT;my $count = 0;for ( $_ = 1; $ok && $_ <= 10; $_++) { warn “Writing line $_\n”; print PIPE “This is line number $_\n”; $count++; sleep 1; # or select(undef,undef,undef,0.5)}close PIPE or die “Can’t close PIPE: $!”;print “Write $count lines of text\n”;
Now when read_three.plcloses, instead ofterminating write_ten_ph.plthe loop terminates gracefully (no Broken pipemessage).
PIPE Exceptions 2:#!/usr/bin/perl#file: write_ten_i.pluse strict;$SIG{PIPE} = ‘IGNORE’;open(PIPE,”| read_three.pl”) or die “Can’t open pipe: $!”;select PIPE; $| = 1; select STDOUT;my $count = 0;for ( 1..10) { warn “Writing line $_\n”; if (print PIPE “This is line number $_\n”) { $count++; } else { warn “An error occurred during writing: $!”; last; # break out of loop } sleep 1; # or select(undef,undef,undef,0.5)}close PIPE or die “Can’t close PIPE: $!”;print “Write $count lines of text\n”;
Now when read_three.plcloses, instead ofterminating write_ten_i.plthe loop terminates gracefully (we see Broken pipemessage only because weask to) by checking if theprint() function failed.
Exceptions 3:
• What if we wanted to handle the Broken pipe error separately from all other errors print() could return?
use Errno ‘:POSIX’;. . .unless (print PIPE “This is line number $_\n”) { last if $! == EPIPE; # break out of loop if Broken pipe die “IO error: $!”; # all other possible errors}
numeric value only available ifwe import Errno ‘:POSIX’ orErrno qw(EPIPE)
Sending Signals:
• From a process:
• Special kill signals:
$count = kill($signal, @processes);
you can use signal numbersor symbol names
you can send the signal to a listof processes but only if you have aright to do so – same user privileges orroot can kill anything.
number of processes successfully signaled
$count = kill(0,@processes);
returns the number of processes from list which could receive a signal but no signal is sent
$count = kill(-n,@processes);
kill treats abs(n) as a process group ID andelivers signal to all processes in the group
Caveats:
• What if you are doing something important or executing a system() or other system command when a signal arrives?– keep it simple ($g_var = 1;) and handle the heavy lifting back in
the main program– don’t do IO inside a handler (strip out warn() in production)– ok to call die() or exit() in a handler since you are exiting anyway
but NOT on a Windows machine– In 2001 Windows only implemented a minimal set of signal
handlers – CHLD was not implemented, for example.
Timing out slow system calls:
• If a signal occurs while perl is executing a slow system call, the handler is executed and the system call is restarted at the point it left off.
• Some slow system calls don’t restart however; sleep() for example.
$slept = sleep([$seconds]);
sleeps $seconds or until signal received;sleeps forever if no argument
returns how many seconds it
actually slept
What if Automatic Restart is no good:
my $timed_out = 0;$SIG{ALRM} = sub { $times_out = 1; };
printf STDERR “type your passwd;alarm(5);my $passwd = <STDIN>;alarm(0);
printf STDERR “You timed out \n” if $timed_out;
handles ALRM signal
sets ALRM signal to be sent in 5 seconds
if you type nothing, ALRM isreceived after 5 secondsturns timer off;
important or another slow system call could timeout
the logic is that if nothing is typed in 5 seconds the program moveson; perhaps to a prompt insisting on a password. In reality the <>call is restarted and the program hangs until you enter a password.
We need another solution.
Timing out keyboard input:print STDERR “type your password: “;my $passwd = eval { local $SIG{ALRM} = sub {die “timeout\n”;}; alarm(5); return <STDIN>; }alarm(0);print STDERR “you timed out\n” if $@ =~ /timeout/;
An eval{…} block is like an anonymous program.
When die() executes inside an eval{} block it only kills the eval{} block which then returns undef and the program continues on the line after the eval{} block.
If you enter a password before the timer expires then the eval{} block returns its value.
When an eval{} block dies, $@ is set to the last string error message.
same logic as beforebut this time the ALRMhandler calls die() contains pattern
‘timeout’