trap 和 SIGINT 的奇怪问题

trap 和 SIGINT 的奇怪问题

请解释一下:

#!/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+ CCtrl+\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阻塞INTCHLD信号也是如此;通过(此处称为)scriptb继承这些设置。以及正在做什么?如果我们从 via 运行它:forkclonescriptbscripta

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

trap 和 SIGINT 的奇怪问题

感谢大家的回答以及您花时间研究这个问题。

请允许我重述并整合(对以下内容中可能显而易见的内容表示歉意):

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 的观点,并认为我们在这里看到的是一个错误。

相关内容