运行前台进程直到后台进程在 shell 中退出

运行前台进程直到后台进程在 shell 中退出

我在模式下运行 QEMU 虚拟机-daemonize,然后生成一个任意前台(可能是交互式)进程,旨在与 QEMU 实例交互。一般来说,一旦前台进程完成,我就会通过 pidfile 清理 QEMU 实例:

qemu-system ... -pidfile ./qemu.pid -daemonize

/my/custom/interactive/process

pkill -F ./qemu.pid

然而,在某些情况下,QEMU 可以独立退出,而我的前台进程继续运行。但我想阻止它以防万一。所以我的自定义交互过程的行为应该是这样的:

tail -f --pid=./qemu.pid /dev/null

我怎样才能做得很好呢?也许有某种类似超时的包装器,所以我可以运行类似的东西:

trackpid ./qemu.pid /my/custom/interactive/process

答案1

您可以轮询 qemu 进程是否消失,并在它消失时提前终止。这是经过快速测试的代码,特别是没有使用qemu-system.

也有相当多的。您可以删除这些#DEBUG行,但如果您有兴趣了解它们如何组合在一起,请取消注释并将程序输出与代码进行比较。

#!/bin/bash

InvokeQemu()
{
    local i pid pidFile=qemu.pid

    # Start the qemu process, and return the PID if possible
    #
    (
        # qemu-system ... -pidFile "$pidFile" -daemonize
        ( sleep 30 & sleep 0.5 && echo $! >"$pidFile" )    # FAKE IT for half a minute
    ) >/dev/null 2>&1 </dev/null

    #echo "InvokeQemu: checking for successful daemonisation" >&2    #DEBUG
    for i in 1 2 3
    do
        # Does the PID file exist yet
        #echo "InvokeQemu: attempt $i" >&2    #DEBUG
        if [[ -s "$pidFile" ]] && pid=$(cat "$pidFile") && [[ -n "$pid" ]]
        then
            printf "%s\n" $pid
            #echo "InvokeQemu: pid=$pid" >&2    #DEBUG
            return 0
        fi

        # Pause a moment or so before trying again
        sleep 2
    done
    return 1
}

MonitorPIDs()
{
    local pid

    for pid in "$@"
    do
        #echo "MonitorPIDs: checking pid $pid" >&2    #DEBUG
        if err=$(kill -0 "$pid" 2>&1) || [[ "$err" == *permitted* || "$err" == *denied* ]]
        then
            # Process still exists
            :
            #echo "MonitorPIDs: pid $pid still alive" >&2    #DEBUG
        else
            #echo "MonitorPIDs: pid $pid has died" >&2    #DEBUG
            echo "$pid"
            return 1
        fi
    done
    #echo "MonitorPIDs: all good" >&2    #DEBUG
    return 0
}

########################################################################
# Go
myPid=$$

# Start the qemu emulator
echo "Starting qemu emulator"
qemuPid=$(InvokeQemu)

if [[ -z "$qemuPid" ]]
then
    echo "Could not start qemu" >&2
    exit 1
fi

# Start the monitor process
#
# Once any of them is no longer running it will fire SIGTERM to its
# remaining PIDs and then exit
echo "Starting monitor process"
(
    while MonitorPIDs $qemuPid $myPid >/dev/null
    do
        #echo "(Monitor): all good" >&2    #DEBUG
        sleep 2
    done
    kill $qemuPid $myPid 2>/dev/null
) &

# Start your interactive foreground process
#
# You will receive SIGTERM within a few seconds of the emulator exiting,
# so you may want to trap that
echo "Starting interactive process"
while read -p "What do you want to do? " x
do
    echo "OK"
    sleep 1
done
exit 0

答案2

最后我得到了以下代码:

qemu-system ... -pidfile ./qemu.pid -daemonize

{
  tail -f --pidfile="$(cat ./qemu.pid)" /dev/null
  kill -INT 0
} &

/my/custom/interactive/process
kill $!

pkill -F ./qemu.pid

大括号中的脚本实际上是在后台运行的 pidfile 监视器。一旦 pid 消失,监视器就会杀死当前进程组。我使用它是kill -INT 0因为它为我提供了最可靠和干净的结果。

其他选项是:

kill -- 0(使用 TERM 信号终止,无法正确终止交互进程)

kill -INT $$(仅杀死 shell 进程,不能正确终止交互进程)

kill -- -$$(杀死由 shell 的 pid 表示的进程组,并不总是正常工作,我认为是由于sudo作为进程组领导者进行调用)

pkill -P $$(仅杀死子进程,实际上有效,但我更喜欢使用内置的 shell 并依赖 Ctrl-C 处理行为)。

另一点是,如果我的交互式进程已自行完成,我必须终止监视器进程,以避免进一步推断退出和清理脚本。

相关内容