123

I'm writing an application. It has the ability to spawn various external processes. When the application closes, I want any processes it has spawned to be killed.

Sounds easy enough, right? Look up my PID, and recursively walk the process tree, killing everything in sight, bottom-up style.

Except that this doesn't work. In one specific case, I spawn foo, but foo just spawns bar and then immediately exits, leaving bar running. There is now no record of the fact that bar was once part of the application's process tree. And hence, the application has no way of knowing that it should kill bar.

I'm pretty sure I can't be the first person on Earth to try to do this. So what's the standard solution? I guess really I'm looking for some way to "tag" a process in such a way that any process it spawns will unconditionally inherit the same tag.

(So far, the best I can come up with is running the application as a different user. That way, you can just indescriminently kill all processes beloning to that user. But this has all sorts of access permission problems...)

4
  • Have you thought about having the nth child process self reap rather than killing it?
    – 111---
    Commented Apr 10, 2014 at 19:16
  • Closely related: Process descendants. Process groups are the way to go if they work for you. Commented Apr 10, 2014 at 22:21
  • @Gilles I disagree that PGID is the best way to do this. For me simply doing sh -c 'sleep 10m' & ps -jH shows that the sh command has a different PGID from the calling shell.
    – Graeme
    Commented Apr 11, 2014 at 1:35
  • 1
    protip: if you use systemd, you can tell it to keep track of this for you using cgroups.
    – strugee
    Commented Apr 11, 2014 at 6:49

5 Answers 5

110

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 namespace

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:

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).

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:

18
  • The descendant process needn't double fork - it can fork once, but if it does so it will be orphaned if its parent process later forks. This occurs all of the time, and is not necessarily related to intentional daemonization at all.
    – mikeserv
    Commented Apr 10, 2014 at 19:55
  • But we've all already answered how to kill all child processes in various ways - the question is not how to ensure one process becomes and remains the child of another. Can you get in testing? Besides, I already did it: unix.stackexchange.com/questions/124162/…
    – mikeserv
    Commented Apr 10, 2014 at 20:49
  • @mikserv, ok, I'll have a go. If Gilles doesn't get there first :)
    – Graeme
    Commented Apr 10, 2014 at 20:55
  • 1
    @AdrianGünter, well spotted. Took that part out of the answer
    – Graeme
    Commented Jun 14, 2023 at 16:13
  • 1
    @AdrianGünter, you can't just do something along the lines of ps -o ppid -o pid | grep "^$$" | cut -f 2 ?
    – Graeme
    Commented Jun 16, 2023 at 11:48
30

You can use:

kill -TERM -- -XXX

where XXX is group number of process group you want to kill.

Care should be taken that you only use this method from a top-level script, otherwise you could also kill parent and sibling processes that are part of the process group.

You can check process group 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.
8
  • 1
    How do process groups work? Most of the tasks in your example seem to be in one of two groups... Commented Apr 10, 2014 at 15:58
  • It's a result of forking from shell command line or server startup. It's only short example.
    – cuonglm
    Commented Apr 10, 2014 at 16:01
  • 1
    The process group looks like it might be the thing I'm looking for, but I still want to understand exactly when two processes will have the same group ID, and when it will be different. Commented Apr 10, 2014 at 16:14
  • Updated my answer.
    – cuonglm
    Commented Apr 10, 2014 at 16:34
  • 1
    PGID is not a reliable way to identify children of a particular process since setpgid() can be called on any process. PPID is a more robust way since this is more difficult to alter.
    – Graeme
    Commented Apr 10, 2014 at 19:15
21

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:

References

3
  • 4
    1. Does this kill only immediate children, or all descendents? 2. Does this address the issue where one of the intermediate descentends exits, breaking the link in the process tree? Commented Apr 10, 2014 at 15:58
  • 1
    This kills every process that's parent is your script.
    – slm
    Commented Apr 10, 2014 at 15:59
  • This worked for me. This terminates the parent process and all its child processes. Commented Oct 25, 2021 at 9:11
5

The best way to do this is to use systemd (or another way using cgroups) for starting and stopping the application. A process can leave its process group but can never (at least not without root privilege) leave its cgroup. Thus systemd creates a new cgroup for the new process and later simply kills everything in the cgroup.

5
  • 1
    namespaces would be a more generalized solution. In that way you need only rely on in-kernel structures and not on some init's implementation. You can also easily capture a process's pid at launch without relying on either regardless of its PPID, as I demonstrate in my own answer. lwn.net/Articles/531114
    – mikeserv
    Commented Apr 10, 2014 at 18:11
  • @mikeserv cgroups are a pure in-kernel structure. Commented Apr 11, 2014 at 8:40
  • this is true. systemd, on the other hand, is not. I've only just now noticed the (or another...) part. If youll allow me I'll remove my comment, or if you prefer it can remain and we can all laugh at how stupid I am.
    – mikeserv
    Commented Apr 11, 2014 at 8:42
  • systemd is not killing all the cgroup process, can you please help: unix.stackexchange.com/questions/138221/… Commented Jun 20, 2014 at 8:31
  • 2
    This question really needs an answer with instructions how to use plain cgroups to do this (without systemd). Commented Dec 20, 2015 at 7:50
2
${PROC_CMD} &
pid=$!
kids=$(grep -l "PPid.*$$" /proc/*/status | grep -o "[0-9]*"
    for kid in $(cat /proc/$pid/task/*/children); do 
        kids="$kid $kids $(cat /proc/$kid/task/*/children)"
    done
    printf '%s ' $kids)
kill $kids

That kills all children of ${PROC_CMD}, which is backgrounded in the first line and its $pid captured in the next.

cat /proc/$pid/task/*/children

The above will just list the child processes.

It's important to remember that a process can escape its $PPID. For instance:

echo $( grep "Pid" /proc/self/status & )

Pid: 349 PPid: 1 TracerPid: 0
6
  • This misses all cases where a child dies before the grandchild. Commented Apr 10, 2014 at 22:19
  • @Gilles - thank you. I've alleviated that somewhat, but, still, the only sure way to handle this is with namespaces or some other kind of container. Or so I think, anyway.
    – mikeserv
    Commented Apr 10, 2014 at 22:29
  • @Gilles and now I think I also get all processes claiming the initial as the PPid. Probably it's redundant, but I'm not sure.
    – mikeserv
    Commented Apr 10, 2014 at 22:42
  • I don't see what your latest edits change. Suppose that P forks Q, then Q forks R, then Q exits, then your code runs. Nothing that you do will identify R as a descendant of P. Commented Apr 10, 2014 at 22:44
  • @Gilles - Agreed! Is there a solution here that handles that?
    – mikeserv
    Commented Apr 10, 2014 at 22:45

Not the answer you're looking for? Browse other questions tagged .