sinmissing has asked for the wisdom of the Perl Monks concerning the following question:

I've read a variety of docs and examples for how to fork processes, how to get the child pid, and how to kill a pid. I haven't found the right example that illustrates what I wish to accomplish.

My platform is redhat. What I'm attempting to accomplish is this:

fork this process to the background "tcpdump -ni int0 -c 5 -w /tmp/somefile"

sleep while the child process completes, or kill it if it is running longer than 10 seconds ( eg: tcpdump doesn't capture any data ).

my sample code looks like this:

use POSIX; $SIG{CHLD} = 'IGNORE'; my $snoop = fork(); if ( $snoop == 0 ){ setpgrp; system("tcpdump -ni int0 -c 5 -w /tmp/somefile &"); } my $sleeper = 0; while ( $sleeper < 11 ){ # How do I check the status of my child pid? I don't want to wait +for it to finish, just know if it is still running and 'last;' if not +. sleep 1; $sleeper++ } # Only kill the child pid, if it is running. need help here. kill -1, getpgrp($snoop); # continue on with my code
I should add, that I don't have an option for using specific perl modules to enhance by capabilities here.

Replies are listed 'Best First'.
Re: forking, waiting, and killing child pids
by Eliya (Vicar) on Apr 11, 2012 at 21:12 UTC

    Your usage of "&" in the command is not only unnecessary (the fork already runs "in the background"), it also is the primary reason your code doesn't work.  Otherwise, your approach to create a new process group is perfectly fine, in case you need or want to kill an entire tree of child processes.

    Compare the following (I replaced tcpdump with a simple sleep, which is irrelevant to the discussion):

    #!/usr/bin/perl -w use strict; sub ps { system "ps Tf -o pid,ppid,pgrp,sid,cmd"; } $SIG{CHLD} = 'IGNORE'; my $snoop = fork(); if ( $snoop == 0 ){ setpgrp; system("sleep 20 &"); exit; } ps(); sleep 1; print "PID: $snoop\n"; printf "PGRP: %d\n", getpgrp($snoop); kill -1, getpgrp($snoop); ps(); __END__ PID PPID PGRP SID CMD 2360 2358 2360 2360 bash -rcfile .bashrc 19529 2360 19529 2360 \_ /usr/bin/perl -w ./964597.pl 19531 19529 19529 2360 \_ ps Tf -o pid,ppid,pgrp,sid,cmd 19533 1 19530 2360 sleep 20 PID: 19530 PGRP: -1 PID PPID PGRP SID CMD 2360 2358 2360 2360 bash -rcfile .bashrc 19529 2360 19529 2360 \_ /usr/bin/perl -w ./964597.pl 19534 19529 19529 2360 \_ ps Tf -o pid,ppid,pgrp,sid,cmd 19533 1 19530 2360 sleep 20
    #!/usr/bin/perl -w use strict; sub ps { system "ps Tf -o pid,ppid,pgrp,sid,cmd"; } $SIG{CHLD} = 'IGNORE'; my $snoop = fork(); if ( $snoop == 0 ){ setpgrp; system("sleep 20 ;"); exit; } ps(); sleep 1; print "PID: $snoop\n"; printf "PGRP: %d\n", getpgrp($snoop); kill -1, getpgrp($snoop); ps(); __END__ PID PPID PGRP SID CMD 2360 2358 2360 2360 bash -rcfile .bashrc 19537 2360 19537 2360 \_ /usr/bin/perl -w ./964597.pl 19538 19537 19538 2360 \_ /usr/bin/perl -w ./964597.pl 19540 19538 19538 2360 | \_ sh -c sleep 20 ; 19541 19540 19538 2360 | \_ sleep 20 19539 19537 19537 2360 \_ ps Tf -o pid,ppid,pgrp,sid,cmd PID: 19538 PGRP: 19538 PID PPID PGRP SID CMD 2360 2358 2360 2360 bash -rcfile .bashrc 19537 2360 19537 2360 \_ /usr/bin/perl -w ./964597.pl 19542 19537 19537 2360 \_ ps Tf -o pid,ppid,pgrp,sid,cmd

    As you can see in the first example, the child process (sleep) dissociates, i.e. the original process (your $snoop PID) is no longer alive, so getpgrp($snoop) fails, and you effectively kill nothing...

    In the second example, however, without using "&"1, things work as intended. I.e., you have created a new process group (19538 here), which you are then killing successfully.

    That said, unless you actually are running multiple child processes, there is no need to use the process group technique. As long as you can make sure that tcpdump is the only and immediate child process (for example using exec, as noted by halfcountplus (but without your "&" !)), killing that process directly would work without a problem.

    BTW, to check whether a process is running, you can send it the "0" pseudo signal, e.g.

    printf "child%s alive\n", kill(0, $snoop) ? "":" not";

    ___

    1note that I'm using a ";" here in place of the "&", because the example is also meant to show that using an extra process group to kill multiple processes in one go, is working just fine.  Without a shell meta character in the command (the semicolon in this case), Perl would optimize away the extra shell, so there would only be a single child process, which kind of defeats the purpose of using a process group...

      You're sample of forks without & is very close to how I solved my problem, based on your original reply. I used Proc::ProcessTable to get the pid associated with snoop.

      The following is a mock-up of pstree.

      sshd,30982 --bash,30983 --su,31002 --bash,31003 --my.pl,6893 ./my.pl debug --sh,6934 -c ... --tcpdump,6935 --my.pl,6933 ./my.pl debug

      I achieved these results by using open, as in "open, $snoop, $snoop_cmd". Then, while $snoop is running, I start a counter, and test whether the actual snoop pid is running.

      while ( $counter < 31 ) { my $snoop_running = 1; for my $p ( @{ $ps->table } ) { if ( ( $p->pid == $snoop_pid ) && ( $p->ppid == getpgrp($fork_pid) ) && ( ( $p->cmndline ) =~ '$snoop_cmd' ) ) { $snoop_running = 0; print "packet capture still running (", $p->pid, ") ", $p->cmndline, "\n" if (( $debug eq 0 ) && (( $p->cmndline ) =~ '$snoop_ +cmd' )); } } if ( $snoop_running == 1 ) { #snoop must have finished already for my $p ( @{ $ps->table } ) { kill 9, ( $p->pid ) if ( $p->ppid == getpgrp($fork_pid +) ); } last; } $counter++; sleep 1;

      I used this timer method, because at certain check points; 5, 10, and 20 seconds; I wanted to execute additional system commands, but I did not want to execute any if my snoop had already completed. Next, I kill the fork once packet capture is done. That level of fine pid control is admittedly overly fine, and not for everyone.

      I don't think I would have figured this out, without your initial reply. Thank you very much!

Re: forking, waiting, and killing child pids
by halfcountplus (Hermit) on Apr 11, 2012 at 18:48 UTC

    $snoop in the parent is the child pid. Unfortunately, system() performs a further fork, (and I think tcpdump would be still another child of that) -- and killing the parent doesn't kill the child.

    HOWEVER, if you use exec() instead of system(), there are no additional children spawned, so you can stop the process by killing the fork. Just beware, there should not be any calls after exec(), because it doesn't return.

    my $pid = fork(); if (!$pid) { exec("tcpdump ..."); # no more code here!!! } sleep(11); kill 9, $pid;

    Should work. qv. exec

Re: forking, waiting, and killing child pids
by eyepopslikeamosquito (Archbishop) on Apr 12, 2012 at 02:23 UTC

    I did something similar a while ago at Timing and timing out Unix commands -- this node includes a complete example Unix program that does something very similar to what you describe.

Re: forking, waiting, and killing child pids
by Anonymous Monk on Apr 12, 2012 at 00:18 UTC