更新

更新

我正在写一个应用程序。它有能力产生各种外部进程。当应用程序关闭时,我希望杀死它生成的所有进程。

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

但这并不工作。在一种特定情况下,我生成foo,但foo只是生成bar然后立即退出,保持bar运行。现在有无记录事实上它bar曾经是应用程序进程树的一部分。因此,应用程序无法知道它应该杀死bar

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

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

答案1

更新

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

使用SID

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

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

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

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

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

使用 PID 命名空间

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

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

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

关于 PID 命名空间的进一步阅读:

原答案

杀死子进程

在 shell 脚本中执行此操作的简单方法(只要pkill可用)是:

pkill -P $$

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

终止所有后代进程

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

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

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

  echo "$children"
}

kill $(list_descendants $$)

双叉

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

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

进一步阅读:

答案2

您可以使用:

kill -TERM -- -XXX

其中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

有关进程组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.

答案3

如果您知道父进程 PID,则可以使用 来执行此操作pkill

例子

$ pkill -TERM -P 27888

其中 PPID 为 27888。

摘自 pkill 人

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

脚本中我的 PID 是什么?

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

例子

假设我有这个脚本:

$ more somescript.bash 
#!/bin/bash

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

现在我在后台运行它:

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

看看它,pgrep我们已经得到了正确的 PID:

$ pgrep somescript.bash
28007
$ bottom: 28007

[2]+  Done                    ./somescript.bash

使用进程的 PGID

如果您使用此ps命令,您可以找到进程 PGID,您可以使用它来杀死它。

现在使用这个脚本killies.bash

$ more killies.bash 
#!/bin/bash

sleep 1000 &
sleep 1000 &
sleep 1000 &

sleep 100

我们像这样运行它:

$ killies.bash &

检查一下:

$ 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

现在我们杀死 PGID:

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

附加方法

如果您查看此 SO Q&A,您会发现更多方法来完成您想做的事情:

参考

答案4

${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

这会杀死${PROC_CMD},第一行背景中的所有孩子,并$pid在下一行中捕获。

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

上面只是列出了子进程。

重要的是要记住一个过程可以逃脱例如$PPID.

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

Pid: 349 PPid: 1 TracerPid: 0

相关内容