SIGINT 不会传播到子进程

SIGINT 不会传播到子进程

我有以下 2 个脚本。

script1.sh:

#!/usr/bin/env bash
set -x
set -e

./script2.sh &
pid=$!

max_retry=15
counter=0
until curl --silent --head --fail localhost:8000; do
  if ! jobs %1; then
    echo "Server quit unexpectedly before reporting successful status"
    exit 1
  fi
  if [[ $counter -eq $max_retry ]]; then
    echo "Server never reported healthy status"
    kill -INT $pid
    wait $pid
    exit 1
  fi
  echo "Server not ready. Sleeping for 1 second"
  sleep 1
  counter=$((counter + 1))
done
echo "Server responded healthy"
kill -INT $pid
wait $pid
echo "Done"

script2.sh:

#!/usr/bin/env bash
set -x
set -e

docker run \
  -p 8000:80 \
  nginxdemos/hello

当我运行 时./script1.sh,我希望脚本script2.sh在后台执行,运行curl直到成功,然后script2.sh以终止SIGINT。然而,这种情况并没有发生。第 26行kill不会终止脚本。相反,script2.sh继续运行,直到我按Ctrl+ C

为什么会发生这种情况?如何script2.sh正确终止并将SIGINT信号传播到任何子进程?

答案1

从低级操作系统的角度来看,正确的方法是将 PID 的负值传递给kill,指定进程组 ID (PGID)。

不幸的是,通常情况下,您想要通过组 ID 杀死的进程与想要杀死它的父进程位于同一组中。不过,整个组都将终止,包括父组。

这是一个愚蠢的 Unix 问题,只能通过 hack 来解决。

我有一个script1.sh背景script2.sh,与你的相似。docker我没有命令,而是sleep 3600script2.sh.如果我杀了script2.sh,他们sleep 3600就还活着。

script2.sh:

#!/bin/sh
sleep 3600

在 中script1.sh,我使用 Awk 程序来查找该进程的所有后代script2.sh

script1.sh:

#!/bin/sh

./script2.sh &
pid=$!

sleep 1

descendants=$(awk -f descendants.awk $pid)

kill -TERM $pid

for desc in $descendants; do
  kill -TERM $desc
done

# wait for script2.sh by pid
wait $pid

echo done

这是我的descentants.awk

BEGIN {
  # symbolic names for "ps -efj" columns
  PID=2
  PPID=3

  root_process = ARGV[1]

  while (("ps -efj" | getline) > 0)
    parent[$PID] = $PPID

  for (child in parent) {
    pid = child;

    do {
      par = parent[pid]
      if (par == root_process) {
        print child
        break
      }
      pid = par
    } while (par != 0 && par != 1)
  }
}

这是通过基于输出的第 2 列和第 3 列构建关联哈希来实现的ps -efj:该哈希为我们提供了系统中任何给定 PID 的父级。然后我们遍历所有的 pid。对于每个 pid,我们通过沿着亲缘链追溯其祖先。我们打印每个将给定 pid 作为直接或间接祖先的 pid。

这不会找到父母死亡并因此被重新设置为 PID 1 的后代(在我们的 Awk 脚本收集亲子关系数据之前)。

就像如果script2.sh放入sleep 3600 &后台然后退出,Awk 脚本将显示为空;将sleep继续运行,但其 PPID 为 1。

换句话说,我们假设该脚本及其后代表现良好。如果有任何东西自愿死亡,它会干干净净地死去,并且小心翼翼地不留下后代。如果我们突然杀掉它,我们就会照顾到后代。


PS 我正在 GNU Linux 上工作。我通过将输出与pstree -Tp. -T意思是隐藏线程。这是我们不做的另一件事;我们不讨论线程。我们假设如果我们杀死一个进程,它的线程将由线程库处理。线程实际上是 Linux 中的进程。

示例:这是输出的一部分pstree -Tp

           │               ├─firefox(22690)─┬─Isolated Web Co(22776)
           │               │                ├─Isolated Web Co(22880)
           │               │                ├─Privileged Cont(22870)
           │               │                ├─Socket Process(22748)
           │               │                ├─Web Content(22907)
           │               │                ├─Web Content(22934)
           │               │                ├─Web Content(27810)
           │               │                └─WebExtensions(22820)

这是脚本的输出:

$ awk -f descendants.awk 22690
22820
22880
22907
22776
22870
22934
27810
22748

它发现了相同的八个进程。子树深度超过 1 的另一个示例:

           │               ├─gnome-terminal-(6802)───bash(6814)─┬─git(365)───wish(368)─┬─aspell(382)
           │               │                                    │                      └─git-diff-index(15474)
           │               │                                    ├─git(26636)───wish(26641)───aspell(26650)
           │               │                                    ├─txr(15717)
           │               │                                    └─vi(933)
$ awk -f descendants.awk 6802
365
26641
368
382
15474
26636
26650
933
15717
6814

相关内容