我正在写一个应用程序。它有能力产生各种外部进程。当应用程序关闭时,我希望杀死它生成的所有进程。
听起来很容易,对吧?查找我的 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