129

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? 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. 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. Commented Apr 11, 2014 at 6:49

5 Answers 5

116

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. 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/… Commented Apr 10, 2014 at 20:49
  • @mikserv, ok, I'll have a go. If Gilles doesn't get there first :) Commented Apr 10, 2014 at 20:55
  • 1
    @AdrianGünter, well spotted. Took that part out of the answer 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 ? 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. 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. 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. Commented Apr 10, 2014 at 19:15
24

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

6
  • 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 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. Commented Apr 11, 2014 at 8:42
  • 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
  • 1
    @user1338062 The answer to using plain cgroups is that you cannot, at least not on current distros, and the only way you ever could is deprecated in the kernel because it doesn't work well. cgroups are designed to be managed by one userspace process for the whole machine, on most distros that have one that process is systemd. Commented Nov 21, 2024 at 16:14
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. 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. 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? Commented Apr 10, 2014 at 22:45

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.