トラップと SIGINT に関する奇妙な問題

トラップと 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 の初期の頃は、シェルがプロセスまたはパイプラインをバックグラウンドに置くときは常に、それらのプロセスが SIGINT と SIGQUIT を無視するように設定されていたため、ユーザーがその後フォアグラウンド タスクにCtrl+ C(割り込み) またはCtrl+ \(終了) を入力しても、それらのプロセスは終了されませんでした。ジョブ制御が導入されると、プロセス グループも導入されたため、現在ではシェルが行う必要があるのは、バックグラウンド ジョブを新しいプロセス グループに置くことだけです。それが現在のターミナル プロセス グループでない限り、プロセスはキーボードから来るシグナル ( Ctrl+ CCtrl+ 、\ およびCtrl+ (SIGTSTP)) を認識しません。シェルは、バックグラウンド プロセスをデフォルトのシグナル処理のままにすることができます。実際、プロセスをフォアグラウンドに置いたときに+Zで終了できるようにするには、おそらくそうする必要があるでしょう。CtrlC

しかし、非対話型シェルはジョブ制御を使用しません。非対話型シェルが、キーボードタイプのシグナルが送信された場合でもバックグラウンド プロセスが実行を継続できるようにするという歴史的な理由から、バックグラウンド プロセスの SIGINT と SIGQUIT を無視するという古い動作に戻るのは理にかなっています。また、シェル スクリプトは非対話型シェルで実行されます。

trapそして、コマンドの最後の段落を見ると、バッシュ(1)、 わかるでしょ

シェルに入るときに無視されたシグナルは、トラップまたはリセットできません。

だから、もしあなた./scriptb.sh &相互の作用シェルのコマンド プロンプトでは、シグナル処理はそのまま残され (バックグラウンドにあっても)、コマンドはtrap期待どおりに動作します。ただし、 を実行すると./scripta.sh( の有無にかかわらず&)、スクリプトは非対話型シェルで実行されます。また、その非対話型シェルが を実行すると./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を通じて継承されます。では何が行われているのでしょうか?経由で実行すると、次のようになります。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、シェルから直接実行すると、次のようになります。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

トラップと SIGINT に関する奇妙な問題

回答していただき、また問題を研究するために時間を割いていただいた皆様に感謝します。

もう一度要約して統合させてください(以下では明らかな点についてはお詫びします)。

1) 質問に追加するのを忘れましたが、SIGQUIT も試しましたが、SIGINT と同じように動作しました。

2) そこから、私はすでに、この問題がこれら 2 つのシグナルに対する対話型 bash のデフォルトの処理に関連しているのではないかと疑っていました。

3) bash と対話しているときにデフォルトのアクションが実行されないのは、プロンプトしか表示されていない場合に何かを終了したり中断したりしても意味がないためです。シェルを終了したい場合は、exit と入力するだけです。

4) SIGQUIT と SIGINT がジョブ制御において特別な役割を果たしているとは思えません (SIGTSTP、SIGTTOU、SIGTTIN とは異なります)。

5) これら 2 つのシグナルに対する対話型 bash のデフォルトの処理が、バックグラウンド (非対話型) シェル (この場合は scriptb.sh を実行するシェル) に継承されるというのは意味がわかりません。

6) 実際、フォアグラウンド プロセス グループが (それを開始したシェルから) SIGQUIT および SIGINT の処理を​​継承しないのと同様に、バックグラウンド プロセス グループでも同じことが起こるのは当然だと思います。

7) さらに、受け継がれた性質が何であれ、トラップはそれを変えるはずです。

8) 全体的に見て、私は thrig に同意し、ここで見られるものはバグであると考えます。

関連情報