杀死所有后代进程

Mat*_*hid 95 process kill

我正在写一个应用程序。它能够产生各种外部进程。当应用程序关闭时,我希望它产生的任何进程都被杀死。

听起来很容易,对吧?查找我的 PID,并递归地遍历进程树,以自下而上的方式杀死所有可见的东西。

不同之处在于,这并不正常工作。在一种特定情况下,我 spawn foo,但foo只是spawn ,bar然后立即退出,继续bar运行。现在没有关于bar曾经是应用程序进程树一部分的事实的记录。因此,应用程序无法知道它应该杀死bar.

我很确定我不能成为地球上第一个尝试这样做的人。那么标准的解决方案是什么?我想我真的在寻找某种方式来“标记”一个进程,这样它产生的任何进程都将无条件地继承相同的标签。

(到目前为止,我能想到的最好的方法是以不同的用户身份运行应用程序。这样,您就可以随意终止属于该用户的所有进程。但这有各种访问权限问题......)

Gra*_*eme 85

更新

这是我显然应该更仔细地阅读问题的问题之一(尽管对于这个问题的大多数答案似乎都是这种情况)。我保留了原始答案不变,因为它提供了一些很好的信息,尽管它显然没有抓住问题的重点。

使用 SID

我认为这里最通用、最可靠的方法(至少对于 Linux)是使用 SID(会话 ID)而不是 PPID 或 PGID。这不太可能被子进程更改,并且在 shell 脚本的情况下,该setsid命令可用于启动新会话。在外壳之外,setuid可以使用系统调用。

对于作为会话领导者的 shell,您可以通过执行以下操作来终止会话中的所有其他进程(shell 不会杀死自己):

kill $(ps -s $$ -o pid=)
Run Code Online (Sandbox Code Playgroud)

注意:尾随等号参数pid=删除PID列标题。

否则,使用系统调用,getsid为每个进程调用似乎是唯一的方法。

使用 PID 命名空间

这是最强大的方法,但缺点是它仅适用于 Linux,并且需要 root 权限。此外,shell 工具(如果使用)是非常新的并且没有广泛使用。

有关 PID 命名空间的更详细讨论,请参阅此问题 -使用 `nsenter:` 监禁子进程的可靠方法。这里的基本方法是您可以通过CLONE_NEWPIDclone系统调用中使用标志(或通过unshare命令)来创建新的 PID 命名空间。

当 PID 命名空间中的进程被孤立时(即当它的父进程完成时),它会重新成为顶级 PID 命名空间进程的父级,而不是init. 这意味着您始终可以通过遍历流程树来识别顶级流程的所有后代。在 shell 脚本的情况下,下面的 PPID 方法将可靠地杀死所有后代。

进一步阅读 PID 命名空间:

原答案

杀死子进程

在 shell 脚本中执行此操作的简单方法pkill是:

pkill -P $$
Run Code Online (Sandbox Code Playgroud)

这会杀死当前给定进程的所有子进程($$扩展到当前 shell 的 PID)。

如果pkill不可用,POSIX 兼容方式是:

kill $(ps -o pid= --ppid $$)
Run Code Online (Sandbox Code Playgroud)

杀死所有后代进程

另一种情况是您可能想要杀死当前 shell 进程的所有后代以及直接子进程。在这种情况下,您可以使用下面的递归 shell 函数列出所有后代 PID,然后将它们作为参数传递给 kill:

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

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

  echo "$children"
}

kill $(list_descendants $$)
Run Code Online (Sandbox Code Playgroud)

双叉

需要注意的一件事是双重fork()技术,这可能会阻止上述方法按预期工作。这通常用于守护进程。顾名思义,要启动的进程在原始进程的第二个分支中运行。一旦进程启动,第一个 fork 就会退出,这意味着该进程成为孤立的。

在这种情况下,它将成为init进程的子进程,而不是启动它的原始进程。没有可靠的方法来识别哪个进程是原始父进程,因此如果是这种情况,您不能指望在没有其他识别方法(例如 PID 文件)的情况下杀死它。但是,如果使用了此技术,则不应在没有充分理由的情况下尝试终止该进程。

进一步阅读:


cuo*_*glm 24

您可以使用:

kill -TERM -- -XXX
Run Code Online (Sandbox Code Playgroud)

XXX您要杀死的进程组的组号在哪里。您可以使用以下方法检查它:

 $ 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
Run Code Online (Sandbox Code Playgroud)

有关进程组 ID 的更多详细信息,您可以查看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.
Run Code Online (Sandbox Code Playgroud)


slm*_*slm 16

如果您知道父进程 PID,则可以使用pkill.

例子

$ pkill -TERM -P 27888
Run Code Online (Sandbox Code Playgroud)

其中 PPID 是 27888。

摘自 pkill man

   -P, --parent ppid,...
          Only match processes whose parent process ID is listed.
Run Code Online (Sandbox Code Playgroud)

我在脚本中的 PID 是什么?

这可能是您的下一个问题,因此在 Bash 脚本中,您可以$$在顶部找到脚本的 PID 。

例子

说我有这个脚本:

$ more somescript.bash 
#!/bin/bash

echo "top: $$"
sleep 5
echo "bottom: $$"
Run Code Online (Sandbox Code Playgroud)

现在我运行它,背景:

$ ./somescript.bash &
[2] 28007
top: 28007
Run Code Online (Sandbox Code Playgroud)

通过pgrep节目偷看它,我们得到了正确的 PID:

$ pgrep somescript.bash
28007
$ bottom: 28007

[2]+  Done                    ./somescript.bash
Run Code Online (Sandbox Code Playgroud)

使用进程的 PGID

如果您使用此ps命令,您可以找到一个进程 PGID,您可以使用它来终止。

现在使用这个脚本,killies.bash

$ more killies.bash 
#!/bin/bash

sleep 1000 &
sleep 1000 &
sleep 1000 &

sleep 100
Run Code Online (Sandbox Code Playgroud)

我们像这样运行它:

$ killies.bash &
Run Code Online (Sandbox Code Playgroud)

检查它:

$ 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
Run Code Online (Sandbox Code Playgroud)

现在我们杀死 PGID:

$ pkill -TERM -g 28367
[1]+  Terminated              ./killies.bash
Run Code Online (Sandbox Code Playgroud)

附加方法

如果你看看这个 SO Q&A,你会发现更多的方法来做你想做的事:

参考

  • 1. 这是否只杀死直系子女或_所有_后代?2. 这是否解决了其中一个中间下降端退出、断开进程树中的链接的问题? (3认同)

Hau*_*ing 5

最好的方法是使用systemd(或使用 cgroups 的另一种方式)来启动和停止应用程序。一个进程可以离开它的进程组,但永远不能(至少不是没有 root 权限)离开它的 cgroup。因此systemd,为新进程创建一个新的 cgroup,然后简单地杀死 cgroup 中的所有内容。

  • 这个问题真的需要一个带有如何使用普通 cgroups 来做到这一点的说明的答案(没有 systemd)。 (2认同)