请解释一下:
#!/bin/bash
# This is scripta.sh
./scriptb.sh &
pid=$!
echo $pid started
sleep 3
while true
do
kill -SIGINT $pid
echo scripta.sh $$
sleep 3
done
-
#!/bin/bash
# This is scriptb.sh
trap "echo Ouch;" SIGINT
while true
do
echo scriptb.sh $$
sleep 1
done
当我运行./scripta.sh
陷阱时不打印。如果我从 SIGINT 切换到任何其他信号(我尝试了 SIGTERM、SIGUSR1),陷阱将按预期打印“Ouch”。怎么会发生这种事呢?
答案1
如果我从 SIGINT 切换到任何其他信号(我尝试过 SIGTERM、SIGUSR1),陷阱会按预期打印“Ouch”。
显然你没有尝试SIGQUIT;您可能会发现它的行为与 SIGINT 相同。
问题在于工作控制。
在 Unix 的早期,每当 shell 将进程或管道放入后台时,它都会将这些进程设置为忽略 SIGINT 和 SIGQUIT,因此如果用户随后键入Ctrl+ C(中断)或Ctrl+ \(退出),它们就不会被终止。前台任务。当作业控制出现时,它也带来了进程组,因此现在 shell 需要做的就是将后台作业放入一个新的进程组中;只要不是当前的终端进程组,进程就不会看到来自键盘的信号(Ctrl+ C、Ctrl+\ 和Ctrl+ Z(SIGTSTP))。 shell 可以让后台进程保持默认的信号处理。事实上,当进程进入前台时,它们可能必须被Ctrl+杀死。C
但非交互式 shell 不使用作业控制。出于历史原因,非交互式 shell 会退回到忽略后台进程的 SIGINT 和 SIGQUIT 的旧行为,这是有道理的 - 允许后台进程继续运行,即使键盘类型的信号发送给它们。 shell 脚本在非交互式 shell 中运行。
而且,如果您查看trap
命令下的最后一段重击(1), 你会看到的
进入 shell 时被忽略的信号无法被捕获或重置。
所以,如果你逃离./scriptb.sh &
你的交互的shell 的命令提示符,其信号配置保持不变(即使它被放入后台),并且命令trap
按预期工作。但是,如果您运行./scripta.sh
(使用或不使用&
),它将在非交互式 shell 中运行该脚本。当非交互式 shell 运行时./scriptb.sh &
,它会将scriptb
进程设置为忽略中断并退出。因此trap
命令默默地scriptb.sh
失败了。
答案2
通过一些追踪:
strace -o aaa ./scripta
我们可以观察到默认情况下
read(255, "#!/bin/bash\n# this is scripta.sh"..., 153) = 153
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
lseek(255, -108, SEEK_CUR) = 45
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1ee9b7ca10) = 2483
scripta
阻塞INT
和CHLD
信号也是如此;通过(此处称为)scriptb
继承这些设置。以及正在做什么?如果我们从 via 运行它:fork
clone
scriptb
scripta
strace -o bbb ./scriptb &
然后寻找信号相关的东西,我们发现:
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_IGN, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, 8) = 0
这表明没有任何东西被阻止,然后该INT
信号首先被给予默认处理,然后被忽略。scriptb
直接从shell下运行strace
对比显示:
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
rt_sigaction(SIGINT, {0x45fbf0, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [INT CHLD], 8) = 0
...
或者在进入然后重复的睡眠呼叫处理之前从未被忽略。好的。呃。让我们在scripta
和之间放置一个垫片scriptb
,重置SIGINT
为默认状态......
#include <getopt.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int ch;
while ((ch = getopt(argc, argv, "h?")) != -1) {
switch (ch) {
case 'h':
case '?':
default:
abort();
}
}
argc -= optind;
argv += optind;
if (argc < 1) abort();
signal(SIGINT, SIG_DFL);
execvp(*argv, argv);
abort();
return 1;
}
使用方法如下:
$ make defaultsig
cc defaultsig.c -o defaultsig
$ grep defaultsig scripta.sh
./defaultsig ./scriptb.sh &
$ ./scripta.sh
12510 started
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
...
是的,现在效果很好。但是,我不知道为什么bash
会这样,也许向他们提交错误?中的代码sig.c
看起来非常复杂,并且其他地方有更多的信号处理......
答案3
感谢大家的回答以及您花时间研究这个问题。
请允许我重述并整合(对以下内容中可能显而易见的内容表示歉意):
1)我忘了在我的问题中添加我也尝试过SIGQUIT,它的行为与SIGINT相同;
2)由此,我已经怀疑问题与这两个信号的交互式 bash 的默认配置有关;
3)与 bash 交互时,它们的默认操作不会发生,因为当您唯一拥有的就是提示符时,退出或中断任何事情都是没有意义的。如果你想离开 shell,只需输入 exit;
4)我不认为SIGQUIT和SIGINT在作业控制中发挥特殊作用(与SIGTSTP、SIGTTOU、SIGTTIN相反);
5) 对于我来说,这两个信号的交互式 bash 的默认配置由后台(非交互式)shell(在我们的例子中执行 scriptb.sh 的 shell)继承是没有意义的;
6) 事实上,正如前台进程组不继承(从启动它的 shell)SIGQUIT 和 SIGINT 的配置一样,恕我直言,后台进程组发生同样的情况应该是有意义的。
7)此外,无论继承的性格是什么,trap都应该改变它。
8)总而言之,我倾向于同意 thrig 的观点,并认为我们在这里看到的是一个错误。