linux - 信号处理/子进程的传统做法

标签 linux bash child-process sigterm process-group

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


我无法理解与 Linux 中的子进程相关的 SIGTERM 的常规处理方式。


我正在 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 时在进程组中启动我的程序?

针对 child 的 SIGTERM 流程管理职责的最佳实践/惯例是什么?

最佳答案

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));
  }
})();

在该程序结束时, sleep 进程终止。如果直接调用命令而不使用run-pgrp,则 sleep 进程将继续运行。

案例研究:超时

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

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

有趣的是,超时也将自身放入进程组中,以避免转发信号的复杂性,但会导致一些奇怪的行为。 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 转发到子组。

关于linux - 信号处理/子进程的传统做法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70994907/

相关文章:

bash - 在Visual Studio代码中直接在终端中执行文本编辑器中当前选择的行

linux - 在 Makefile 中自定义 SHELL (set SHELL=my_shell.sh)

javascript - Node.js child_process exec 的标准输出被缩短

node.js - nodejs/express - 立即将标准输出流式传输到客户端

linux - 安装 iPod 时显示奇怪的 URI 字符串

linux - 如何在uclinux中访问USB大容量存储设备...?

linux - 系统启动 10 分钟后运行 bash 脚本

c - 添加到 Linux 的系统调用的历史?

linux - 在(CentOS 6.5)2.6.32内核中编译2.4.20内核

python - 从 python spawn 子进程接收连续输出不起作用