当我在 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 被丢弃。
我的问题:
- 它是否正确?我如何验证这是否是实际发生的情况?
- 假设我无法修改要在后台运行的程序。我如何确保它已经处于活动状态并且将处理信号?一个简单的方法
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 test
Bash 以了解其-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 :; done
simple
如果退出得足够快或从未执行(例如由于某些错误),将无限循环。
我认为这是一个合理的做法:
./simple & while [ "/proc/$!/exe" -ef "/proc/$$/exe" ]; do :; done; kill -s INT "$!"
在我的测试中,信号simple
在处理程序设置之前一直到达;每次主 shell 稍后通知我时,工作都会被中断。您写道“我不在乎是否设置了预期的信号处理程序,只关心正确的二进制文件运行”。是的,这就是你可以做到的。