10

I would like to start a process in its own process group (or, alternatively, change its group once started) and:

  • have the processes in the group respond to Ctrl + C from the terminal
  • get the id of the process group so that I can terminate all the processes in the group via the kill command.

Note: I tried setsid prog [args] but the processes do not respond to Ctrl+C from the terminal nor I could get the new process group id.

I also tried to change the process group via Perl's setpgrp($pid, $pid) and POSIX::setpgid($pid, $pid), to no avail.

Edit: The larger problem:

I have a process (single-threaded; let's call it the "prolific" process P) that starts many child processes synchronously (one after another; it starts a new one when the previous child process terminates). From the terminal, I want to be able to kill P and the tree of processes below it. To do that, I could simply arrange to kill the processes in P's group. However, the default behavior is that P is in the group of its parent process. That means that P's parent will be killed if I kill all the processes in P's group, unless I have P and its tree be in their own group.

My intention is to kill P and the tree below it, but not P's parent. Also, I cannot modify P's code itself.

codeforester
  • 39,467
  • 16
  • 112
  • 140
beluchin
  • 12,047
  • 4
  • 24
  • 35
  • Please clarify what difference from the default behavior of process launching (as observed by ninjalj below) are you needing to accomplish? – Chris Stratton Apr 27 '11 at 20:42
  • I hope the edit I added above (The larger problem) helps clarify the intent. – beluchin Apr 28 '11 at 13:07
  • 1
    You can `fork` and `setpgrp` to have the process on a new process group. Then, instead of P and its children you will have Pp, Pc, and Pc's children. Since you immediately `setpgrp`'d Pc after `fork`, it will be on its own process group, so you can kill it and its children in one go. Have Pp `wait`, and it will wait until Pc's death then exit (or whatever you want it to do). – ninjalj Apr 28 '11 at 17:48
  • this is precisely what was required. Thank you!! Only one piece of the puzzle is left to put in place. I want my parent Perl script to terminate with an error status indicating that it was interrupted. Please see my post on the Answers section below. – beluchin Apr 28 '11 at 18:57

3 Answers3

5

What do you mean "start a process in its own process group"? The shell launches processes in their own process groups, that's how it does job control (by having a process group for processes in the foreground, and several process groups for every pipeline launched on the background).

To see that the shell launches a new process group for every pipeline, you can do this:

ps fax -o pid,pgid,cmd | less

which will show something like:

11816 11816  |   \_ /bin/bash
4759   4759  |       \_ ps fax -o pid,pgid,cmd
4760   4759  |       \_ less

Note that the shell has created a new process group for the pipeline, and every process in the pipeline shares the process group.

Edit:

I think I know what you are getting at. You are calling system from Perl. Apparently, sh -c doesn't create new process groups, since it's a shell without job control.

What I would do would be to fork, then on the child:

setpgrp;
system("ps fax -o pid,pgid,cmd");

and wait on the parent.

ninjalj
  • 42,493
  • 9
  • 106
  • 148
  • 1
    Interesting point... what is the intent? Own process group or own session id? I kind of assumed the later, but we'll have to wait for the poster to clarify – Chris Stratton Apr 27 '11 at 20:39
  • @Chris: I think the OP used `setsid` because there isn't any `setpgrp` utility. – ninjalj Apr 27 '11 at 20:45
  • @ninjalj: That could be it. I wonder what the intent was. Anyway, I learned some things while putting together my perhaps-not-relevant answer ;-) – Chris Stratton Apr 27 '11 at 20:48
  • @Chris: your answer _is_ relevant. I was just adding a different angle. – ninjalj Apr 27 '11 at 20:52
  • 1
    @ninjalj: You are right on the money with the thought about Perl. The "profilic" process that I refer above is a Perl process that calls system repeatedly a number of times. When its current child process is terminated, P creates another child process, oblivious to the fact that the previous child was interrupted. My goal is to arrange for P and the process tree below it to be killed when interrupted from the terminal, but **not P's parent** – beluchin Apr 28 '11 at 13:17
  • @beluchin: As I said, you can `fork` and `setpgrp` on the child. Then you can kill the child's process group from the parent. – ninjalj Apr 28 '11 at 17:42
1

EDIT: If what you wanted to do was use setsid but find the session id and/or pid of the resulting process:

If you launch a process through the setsid command it won't be attached to your terminal, so of course it won't respond to ctrl-c.

You could find it by grepping through the output of

ps x -O sid 

or something more limited like

ps x -o %c,%p,sid

Or simple trolling through proc/[pid]/stat for all entries and looking at the session id and whatever else is of interest (see man proc for details)

The man page for setsid is not giving any flags to directly generate output, but you could trivially make your own version that prints out the desired information, by modifying the standard.

For example, grab a copy of setsid.c from one of the results for

http://www.google.com/codesearch?as_q=setsid&as_package=util-linux

Comment out the nls include, the locale stuff and the _("") error macro which will cause problems and then add this right before the execvp line:

    printf("process will be pid %d sid %d\n", getpid(), getsid(0));
Chris Stratton
  • 39,853
  • 6
  • 84
  • 117
  • What I should have said was "some macro not available in a single-file build that was being used to display an error message" – Chris Stratton Apr 27 '11 at 20:37
1

Here is the answer in Perl code following ninjalj's suggestions above:

prolific_wrapper.pl

my $pid = fork();
if (not defined $pid) {

    die 'resources not available';

} elsif ($pid == 0) {

    # CHILD
    setpgrp;
    exit system(prolific => @ARGV);

} else {

    # PARENT
    my $was_killed = 0;
    local $SIG{INT} = sub {
        say 'kill prolific and its tree ...';
        kill KILL => -$pid;
        $was_killed = 1;
    };
    wait;
    my $child_status = $?;
    $SIG{INT} = 'DEFAULT';
    if ($was_killed) {kill INT => $$}
    else {exit $child_status}

}

Many thanks again!

beluchin
  • 12,047
  • 4
  • 24
  • 35
  • You can always `kill INT => $$` instead of `exit`, though I'm not sure it will be that clean to exit that way. – ninjalj Apr 28 '11 at 19:23
  • OTOH `perl -e 'kill INT => $$;'; echo $?` shows 130 in my box. So for me it would be `exit 130` – ninjalj Apr 28 '11 at 19:24
  • Anyway, I'm not sure you need to catch SIGINT, ^C should send it to the whole process group, your child Perl process ignores it, but it would get the exit code from the `system` call. – ninjalj Apr 28 '11 at 19:26
  • As can be seen by `perl -e 'exit system"sleep 5"'; echo $?` and ^C – ninjalj Apr 28 '11 at 19:27
  • 1. I need to trap SIGINT in the wrapper (the parent) because I need to kill the processes in the group headed by prolific. 2. I would use exit 130. I am not sure what the effect of `kill INT => $$` will be within a signal handler. – beluchin Apr 28 '11 at 19:42