1

编辑:如果管理shell 脚本的子进程真的纯粹是一个“意见”问题......难怪有这么多可怕的 shell 脚本。感谢您继续这样做。


我很难理解 SIGTERM 是如何与 Linux 中的子进程相关的传统处理方式。


我正在 Bash 中编写命令行实用程序。

看起来像

command1
command2
command3

很简单,对吧?

但是,如果向我的程序发送 SIGTERM 信号,Bash 脚本将结束,但当前子进程(例如command2)将继续。

但是有了更多的代码,我可以像这样编写我的程序

trap 'jobs -p | xargs -r kill' TERM

command1 &
wait
command2 &
wait
command3 &
wait

这会将 SIGTERM 传播到当前正在运行的子进程。我不经常看到这样编写的 Bash 脚本,但这就是它所需要的。


我是不是该:

  1. 每次创建子进程时都以第二种样式编写程序?
  2. 或者希望用户在发送 SIGTERM 时在进程组中启动我的程序?

关于儿童 SIGTERM 的流程管理职责的最佳实践/约定是什么?

4

1 回答 1

1

tl;博士

第一种方式。

如果一个进程启动一个子进程并等待它完成(示例),则无需特别注意。

如果一个进程启动一个子进程并可能提前终止它,它应该在一个新的进程组中启动该子进程并向该组发送信号。

细节

奇怪的是,这适用的频率(比如,每个 shell 脚本),我找不到关于约定/最佳实践的好答案。

一些扣除:

创建和发送信号进程组非常常见。特别是,交互式 shell 可以做到这一点。所以(除非它采取额外的步骤来防止它)一个进程的子进程可以在任何时候收到 SIGINT 信号,在非常正常的情况下。

为了支持尽可能少的范式,总是依赖它似乎是有意义的。

这意味着第一种样式是可以的,并且进程管理的负担放在了在常规操作期间故意终止其子进程的进程(这相对较少见)。

另请参阅下面的“案例研究:超时”以获取更多证据。

怎么做

虽然问题的角度来自普通被调用程序的要求,但这个答案提出了一个问题:如何在新进程组中启动进程(在非普通情况下希望过早中断进程)?

这在某些语言中很容易,而在其他语言中则很困难。我创建了一个实用程序 run-pgrp来协助后一种情况。

#!/usr/bin/env python3
# Run the command in a new process group, and forward signals.
import os
import signal
import sys

pid = os.fork()
if not pid:
    os.setpgid(0, 0)
    os.execvp(sys.argv[1], sys.argv[1:])

def receiveSignal(sig, frame):
    os.killpg(pid, sig)
signal.signal(signal.SIGINT, receiveSignal)
signal.signal(signal.SIGTERM, receiveSignal)

_, status = os.waitpid(-1, 0)
sys.exit(status)

调用者可以使用它来包装它过早终止的进程。

Node.js 示例:

const childProcess = require("child_process");
(async () => {
  const process = childProcess.spawn(
    "run-pgrp",
    ["bash", "-c", "echo start; sleep 600; echo done"],
    { stdio: "inherit" }
  );
  /* leaves orphaned process
  const process = childProcess.spawn(
    "bash",
    ["-c", "echo start; sleep 600; echo done"],
    { stdio: "inherit" }
  );
  */
  await new Promise(res => setTimeout(res, /* 1s */ 1000));
  process.kill();
  if (process.exitCode == null) {
    await new Promise(res => process.on("exit", res));
  }
})();

在这个程序结束时,休眠进程终止。如果直接调用的命令不带run-pgrp,则睡眠进程继续运行。

案例研究:超时

GNUtimeout实用程序是一个可以终止其子进程的程序。

值得注意的是,它在一个新的进程组中运行子进程。这支持了这样的结论,即在潜在的中断之前应该创建一个新的进程组。

然而,有趣的是,超时也将自己置于进程组中,以避免转发信号的复杂性,但会导致一些奇怪的行为。https://unix.stackexchange.com/a/57692/56781

例如,在交互式 shell 中,运行

bash -c "echo start; timeout 600 sleep 600; echo done"

尝试打断这个 (Ctrl+C)。它没有响应,因为timeout从来没有收到信号!

相反,我的run-pgrp实用程序将自己保留在原始进程组中,并将 SIGINT/SIGTERM 转发给子组。

于 2022-02-06T18:45:48.080 回答