Kill all descendant processes

Update

This is one of those ones where I clearly should have read the question more carefully (though seemingly this is the case with most answers on to this question). I have left the original answer intact because it gives some good information, even though it clearly misses the point of the question.

Using SID

I think the most general, robust approach here (at least for Linux) is to use SID (Session ID) rather than PPID or PGID. This is much less likely to be changed by child processes and, in the case of shell script, the setsid command can be used to start a new session. Outside of the shell the setuid system call can be used.

For a shell that is a session leader, you can kill all the other processes in the session by doing (the shell won't kill itself):

kill $(ps -s $$ -o pid=)

Note: The trailing equals sign in argument pid= removes the PID column header.

Otherwise, using system calls, call getsid for each process seems like the only way.

Using a PID namspace

This is the most robust approach, however the downsides are that it is Linux only and that it needs root privileges. Also the shell tools (if used) are very new and not widely available.

For a more detailed discussion of PID namespaces, please see this question - Reliable way to jail child processes using `nsenter:`. The basic approach here is that you can create a new PID namespace by using the CLONE_NEWPID flag with the clone system call (or via the unshare command).

When a process in a PID namespace is orphaned (ie when it parent process finishes), it is re-parented to the top level PID namespace process rather than the init. This means that you can always identify all the descendants of the top level process by walking the process tree. In the case of a shell script the PPID approach below would then reliably kill all descendants.

Further reading on PID namespaces:

  • Namespaces in operation, part 3: PID namespaces
  • Namespaces in operation, part 4: more on PID namespaces

Original Answer

Killing child processes

The easy way to do this in a shell script, provided pkill is available is:

pkill -P $$

This kills all children of the current given process ($$ expands to the PID of the current shell).

If pkill isn't available, a POSIX compatible way is:

kill $(ps -o pid= --ppid $$)

Killing all descendent processes

Another situation is that you may want to kill all the descendants of the current shell process as well as just the direct children. In this case you can use the recursive shell function below to list all the descendant PIDs, before passing them as arguments to kill:

list_descendants ()
{
  local children=$(ps -o pid= --ppid "$1")

  for pid in $children
  do
    list_descendants "$pid"
  done

  echo "$children"
}

kill $(list_descendants $$)

Double forks

One thing to beware of, which might prevent the above from working as expected is the double fork() technique. This is commonly used when daemonising a process. As the name suggests the process that is to be started runs in the second fork of the original process. Once the process is started, the first fork then exits meaning that the process becomes orphaned.

In this case it will become a child of the init process instead of the original process that it was started from. There is no robust way to identify which process was the original parent, so if this is the case, you can't expect to be able to kill it without having some other means of identification (a PID file for example). However, if this technique has been used, you shouldn't try to kill the process without good reason.

Further Reading:

  • Why fork() twice
  • What is the reason for performing a double fork when creating a daemon?

You can use:

kill -TERM -- -XXX

where XXX is group number of process group you want to kill. You can check it using:

 $ ps x -o  "%p %r %c"
 PID   PGID COMMAND
 2416  1272 gnome-keyring-d
 2427  2427 gnome-session
 2459  2427 lightdm-session <defunct>
 2467  2467 ssh-agent
 2470  2427 dbus-launch
 2471  2471 dbus-daemon
 2484  2427 gnome-settings-
 2489  2471 gvfsd
 2491  2471 gvfs-fuse-daemo
 2499  2427 compiz
 2502  2471 gconfd-2
 2508  2427 syndaemon
 2513  2512 pulseaudio
 2517  2512 gconf-helper
 2519  2471 gvfsd-metadata

For more details about process groups ID, you can see man setpgid:

DESCRIPTION
       All  of  these interfaces are available on Linux, and are used for get‐
       ting and setting the process group ID (PGID) of a  process.   The  pre‐
       ferred,  POSIX.1-specified  ways  of doing this are: getpgrp(void), for
       retrieving the calling process's PGID; and  setpgid(),  for  setting  a
       process's PGID.

       setpgid()  sets  the  PGID of the process specified by pid to pgid.  If
       pid is zero, then the process ID of the calling process  is  used.   If
       pgid is zero, then the PGID of the process specified by pid is made the
       same as its process ID.  If setpgid() is used to move  a  process  from
       one  process  group to another (as is done by some shells when creating
       pipelines), both process groups must be part of the same  session  (see
       setsid(2)  and  credentials(7)).   In  this case, the pgid specifies an
       existing process group to be joined and the session ID  of  that  group
       must match the session ID of the joining process.

If you know the parent processes PID you can do this using pkill.

Example

$ pkill -TERM -P 27888

Where the PPID is 27888.

excerpt from pkill man

   -P, --parent ppid,...
          Only match processes whose parent process ID is listed.

What's my PID in a script?

This is probably your next question so when in a Bash script you can find out the script's PID using $$ at the top.

Example

Say I have this script:

$ more somescript.bash 
#!/bin/bash

echo "top: $$"
sleep 5
echo "bottom: $$"

Now I run it, backgrounded:

$ ./somescript.bash &
[2] 28007
top: 28007

Peeking at it with pgrep shows we've got the right PID:

$ pgrep somescript.bash
28007
$ bottom: 28007

[2]+  Done                    ./somescript.bash

Using a process' PGID

If you use this ps command you can find out a processes PGID, which you can kill using instead.

Using now this script, killies.bash:

$ more killies.bash 
#!/bin/bash

sleep 1000 &
sleep 1000 &
sleep 1000 &

sleep 100

We run it like so:

$ killies.bash &

Checking in on it:

$ ps x -o  "%p %r %c"
  PID  PGID COMMAND
28367 28367 killies.bash
28368 28367 sleep
28369 28367 sleep
28370 28367 sleep
28371 28367 sleep

Now we kill the PGID:

$ pkill -TERM -g 28367
[1]+  Terminated              ./killies.bash

Additional methods

If you take a look at this SO Q&A you'll find still more methods for doing what you want:

  • Best way to kill all child processes

References

  • How does a Linux/Unix Bash script know its own PID?

Tags:

Process

Kill