Bash 后台进程信号丢失

Bash 后台进程信号丢失

当我在 Bash 5.1 中生成后台作业并立即向其发送信号时,该信号似乎丢失了。简短演示:

$ cat simple.cc 
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

static void handler(int, siginfo_t *, void *)
{
        write(2, "Got SIGINT\n", 11);
        exit(0);
}

int main()
{
        struct sigaction act;
        act.sa_flags = SA_SIGINFO | SA_RESTART;
        act.sa_sigaction = handler;
        if (sigaction(SIGINT, &act, nullptr)) { exit(1); }

        while (true) {}
}

$ g++ -Wall -Wextra simple.cc -o simple

$ ./simple & kill -SIGINT $!
[1] 540434

$ # nothing happens
$

$ kill -SIGINT 540434
Got SIGINT

我的假设是,当信号命中时,分叉的后台进程仍然执行 Bash。 Bash 尝试将 SIGINT 转发到前台进程,可惜没有,因此 SIGINT 被丢弃。

我的问题:

  1. 它是否正确?我如何验证这是否是实际发生的情况?
  2. 假设我无法修改要在后台运行的程序。我如何确保它已经处于活动状态并且将处理信号?一个简单的方法sleep 1会有所帮助,但我正在寻找正确的同步。请注意,我不关心是否设置了预期的信号处理程序,只关心正确的二进制文件运行。

答案1

注意:此答案需要/proc.


[...] SIGINT 被丢弃。 […] 它是否正确?

基本上是的,尽管我不确定你的描述是否准确(“转发SIGINT”?)。当主(在您的示例中:交互式)bash分叉并且该行的执行超出 时&,就会有一个带有 PID 的进程$!并且信号会到达它。问题是该过程最初是另一个bash即将exec./simple显然是对方bash收到信号。但它不会退出,而是将自身替换为./simple,但信号已经“用完”。

我既不是程序员也不是 *nix 专家。我不确定我的描述是否准确。即使它不完全准确,这个答案的其余部分也可能有用。


我如何验证这是否是实际发生的情况?

问题发生的时间范围非常窄。通常,推迟kill“解决”问题就足够了。理论上,任何延迟,无论多大,在特定尝试中都可能不够大。当您写到“正确的同步”时,您似乎理解了这一点。

kill是 Bash 中的内置函数,它的速度足以触发该问题。可以使用外部kill(例如)。/bin/kill它是作为一个单独的进程生成的,这需要时间,并且在我的测试中它从未触发该问题。

我试图在另一个bash替换为./simple;之前抓住它。我试着检查一下/proc/$!/exe。不幸的是ls -l或者readlink太慢,它们不是内置的。

有用的内置函数是test或其同义词[。我的bash/bin/bash,我可以这样做:

./simple & [ "/proc/$!/exe" -ef /bin/bash ] && echo gotcha

(在测试时,不要忘记killall simple这样做。)请参阅help testBash 以了解其-ef作用。

bash让我们看看在变成之前我可以执行多少测试simple

./simple & while [ "/proc/$!/exe" -ef /bin/bash ]; do echo a; done

在我的测试中,回声字符串的数量约为五个。最初我尝试通过管道来对它们进行计数,wc -l但这引入了延迟,结果是0

主代码继续执行而后台进程尚未执行的时间窗口./simple确实很窄,但它确实存在。


假设我无法修改要在后台运行的程序。我如何确保它已经处于活动状态并且将处理信号? [...] 请注意,我不关心是否设置了预期的信号处理程序,只关心正确的二进制文件运行。

/proc/$!/exe循环测试。bash成为后退出循环simple。测试不必非常快。请注意这一点:

  • while [ "/proc/$!/exe" -ef /bin/bash ]; do :; done硬编码/bin/bash
  • until [ "/proc/$!/exe" -ef ./simple ]; do :; donesimple如果退出得足够快或从未执行(例如由于某些错误),将无限循环。

我认为这是一个合理的做法:

./simple & while [ "/proc/$!/exe" -ef "/proc/$$/exe" ]; do :; done; kill -s INT "$!"

在我的测试中,信号simple在处理程序设置之前一直到达;每次主 shell 稍后通知我时,工作都会被中断。您写道“我不在乎是否设置了预期的信号处理程序,只关心正确的二进制文件运行”。是的,这就是你可以做到的。

相关内容