使用PID号杀进程时如何避免杀错进程?

使用PID号杀进程时如何避免杀错进程?

我确实在 Bash 脚本中启动了一个长时间运行的后台进程。将进程发送到后台后,我将 PID 号保存在变量中,并在必要时使用该 PID 号来终止该进程。

但是,如果该后台进程在我的脚本杀死它之前以某种方式终止,并且系统为新创建的进程分配相同的 PID 号,那么当我使用该数字杀死该后台进程时,此操作可能会杀死该新创建的进程(取决于权限) , 当然)。

我知道,使用过的 PID 号不会在短时间内分配给任何新创建的进程,但我的脚本运行了数周,所以这是可能的。

怎样才能避免这样的事故发生呢?

答案1

正如评论中所建议的,该pkill实用程序可能有用。

既然你说“bash脚本”,你很可能必须运行pkill bash- 这是你不应该做的事情。

相反,您可以使用pkill -f <name>,它将使用完整的进程名称来匹配。因此,假设您的任务是bash /home/me/my_script.sh,您可以使用以下内容:

pkill -f -e my_script.sh

-e是可选的,只是打印出被杀死的内容。


选择:

将以下脚本保存为/usr/bin/mykill(或任何您想要的位置):

#!/bin/bash
mypid="$1"
if [[ ! -f /proc/$mypid/cmdline ]]; then
    echo "Process ID not found."
    exit 1
else
    echo "About to kill $(cat /proc/$mypid/cmdline)"
    echo "Press enter if you want to kill that process"
    read -p "Press CTRL-C if you don't want that"
    kill $mypid
fi

并将其运行为mykill <pid>

答案2

如果您的后台进程在您的控制之下,请在其命令行中添加额外的标识作为标签,您可以将其副本与 Pid 一起保留,然后签入ps -o args myPid

我使用像这样的选项--unique "${myTag}"

我从uuidgen, 或 a导出 myTagdate至纳秒精度。如果是 ssh 作业,请包含本地主机名。

如果您无法引入新选项:

.. 用于date +%s获取作业的开始时间,并与 Pid 一起存储。

..用于ps -o etimes获取该过程经过的时间(以秒为单位)。

.. 与当前比较date +%s(可能有几秒钟的误差)。

任何一种方法与 Pid 结合使用时,错误概率都应该可以忽略不计。

答案3

我将 PID 与 /proc/<PID> 的时间戳组合成一个 uniq id,而不用担心杀死错误的 PID。

保存$PID:

echo $PID $(stat --format %Z /proc/$PID/comm) > pid

安全地杀死 $PID:

read PID TIMESTAMP < pid
[[ $(stat --format %Z /proc/$PID/comm) != $TIMESTAMP ]] || kill -SIGKILL $PID

这样,即使$PID被另一个进程回收,它的创建时间(/proc/$PID/comm的时间戳)也会不同,因此不可能重复。

PS:这[[ ... ]] || cmd意味着如果[[ ]]为真则不执行任何操作,否则运行cmd

编辑:我们确实有其他许多解决方法,但我想为什么不直接解决它呢?这应该是最简单的直接方式,不需要后台服务,不需要高级系统库,例如大多数容器都没有很好支持的systemd。我在一个需要中断其他正在进行的进程的产品中使用了这种方式。

EDIT2:当你杀死你的后台进程时,你最好在一个单独的进程组中启动你的后台进程,例如setsid background_process &,然后在background_process的任何子进程中,你可以通过获取进程组id ps -o pgid= $$,然后在killer端杀死进程组ID,然后所有子进程将被原子地杀死。否则,你杀死一个正常的进程ID,那么它的子进程仍然活着,即使使用pkill -P parant_pid它仍然有机会让新的子进程逃脱。

答案4

我不同意这通常是不可能的。pkill如果可以避免的话,我不建议通过名称杀死它(如果您合法地希望某个命令的多个实例有单独的超时怎么办?)。我不明白有关答案如何jobs消除竞争条件;使用jobsand thenkill不是原子的。

但是,如果启用了作业控制,我们可以使用进程组 ID ( PGIDs ),或者使用子 shell 并通过进程父 ID ( PPID) 进行终止(如果这不是一个选项)。请参阅我的帖子:https://unix.stackexchange.com/a/649320/464414

我只会摘录首选方法并将其粘贴到此处;请参阅上面的帖子以了解注释和替代方案。

更新:以下版本现在适用于管道

timeOut() {
    checkArgs() { [ $(( ${1} )) -gt 0 -a "${*:2}" ]; }
    jobControlEnabled() { expr "${-}" : '.*m' >/dev/null; }
    terminalFDs() { [ -t 0 -a -t 1 ]; }
    groupLeader() { sh -c 'expr `ps -o pgid= ${PPID}` : "${PPID}" >/dev/null;'; }
    timeOutImpl() {
        groupLeader || { echo "Job control error - not group leader!"; return -1; }
        KILL_SUB="kill -- -`sh -c 'echo ${PPID}'`"
        { sleep ${1}; ${KILL_SUB}; } &
        "${@:2}"; ${KILL_SUB}
    }
    checkArgs "${@}" || { echo "Usage: timeOut <delay> <command>"; return -1; }
    if jobControlEnabled && terminalFDs; then
        ( timeOutImpl "${@}"; )
    else
        ( set -m; ( timeOutImpl "${@}"; ); )
    fi
}

相关内容