親プロセスへのSIGINTの伝播の防止


10

子シェルスクリプトの実行中にControl + C(またはINTR文字に設定されている文字)を押すと、親プログラム(C ++プログラムまたはシェルスクリプト)が子シェルスクリプトを実行するシナリオを考えます。 SIGINTは、フォアグラウンドプロセスグループのすべてのプロセスに送信されます。これには、親プロセスが含まれます。

出典:POSIX.1-2008 XBDセクション11.1.9

このデフォルトの動作をオーバーライドする方法はありますか?子プロセスだけが親に伝播せずに信号を処理することですか?

参照:スタックオーバーフローポスト-子が中断されたときに親プロセスが完了しない(TRAP INT)

回答:


11

(ジルの答えに触発されて)

ISIGフラグセット、のための唯一の方法Childスクリプトを取得するSIGINT親が得ることなくがSIGINT、それは、独自のプロセスグループであるためにです。これはset -mオプションで実行できます。

シェルスクリプトの-mオプションをオンにすると、Child対話型でなくてもジョブ制御が実行されます。これにより、別のプロセスグループで実行されSIGINTINTR文字が読み取られたときに親がを受信できなくなります。

ここでのPOSIXの説明-mオプションは

-mこのオプションは、実装がUser Portability Utilitiesオプションをサポートしている場合にサポートされます。すべてのジョブは、独自のプロセスグループで実行されます。バックグラウンドジョブの完了後、シェルがプロンプトを出す直前に、バックグラウンドジョブの終了ステータスを報告するメッセージが標準エラーに書き込まれます。フォアグラウンドジョブが停止した場合、シェルは、ジョブユーティリティによって記述された形式でメッセージを標準エラーに書き込みます。さらに、ジョブが終了以外のステータスを変更した場合(たとえば、ジョブが入力または出力のために停止した場合、またはSIGSTOPシグナルによって停止した場合)、シェルは次のプロンプトを書き込む直前に同様のメッセージを書き込む必要があります。このオプションは、対話型シェルではデフォルトで有効になっています。

-mオプションは次のようにある-iが、それは、シェルの動作はほぼ同じくらい変更されることはありません-iありません。

例:

  • Parentスクリプト:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    ./Child
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • Childスクリプト:

    #!/bin/sh -m
    #         ^^        
    # notice the -m option above!
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

入力を待っている間にControl+ Cを押すと、次のようになりますChild

$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally

親のSIGINTハンドラーが実行されないことに注意してください。

または、のParent代わりに変更する場合はChild、次のようにできます。

  • Parentスクリプト:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    sh -m ./Child  # or 'sh -m -c ./Child' if Child isn't a shell script
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • Childスクリプト(通常;は必要ありません-m):

    #!/bin/sh
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

代替案

  1. フォアグラウンドプロセスグループ内の他のプロセスを変更して、の間無視SIGINTChildます。これはあなたの質問に対処しませんが、あなたが望むものを得るかもしれません。
  2. 変更Child
    1. stty -g現在の端末設定をバックアップするために使用します。
    2. 実行stty -isigの信号を生成しないようにINTRQUITSUSPの文字が。
    3. 背景には、端末入力を読み、必要に応じて信号を自分で送る(例えば、実行kill -QUIT 0時にControl+ \、読み込まれkill -INT $$たときにControl+ C読まれます)。これは簡単なことではなく、Childスクリプトやスクリプトがインタラクティブなものを意図している場合は、これをスムーズに機能させることができない場合があります。
    4. 終了する前に端末の設定を復元します(理想的にはのトラップからEXIT)。
  3. #2と同じですがstty -isig、実行するのではなく、ユーザーがヒットするのを待つEnterか、その他の特殊でないキーを待ってからkillしChildます。
  4. setpgid呼び出しに使用できる独自のユーティリティをC、Python、Perlなどで記述しますsetpgid()。以下は粗雑なCの実装です:

    #define _XOPEN_SOURCE 700
    #include <unistd.h>
    #include <signal.h>
    
    int
    main(int argc, char *argv[])
    {
        // todo: add error checking
        void (*backup)(int);
        setpgid(0, 0);
        backup = signal(SIGTTOU, SIG_IGN);
        tcsetpgrp(0, getpid());
        signal(SIGTTOU, backup);
        execvp(argv[1], argv + 1);
        return 1;
    }

    からの使用例Child

    #!/bin/sh
    
    [ "${DID_SETPGID}" = true ] || {
        # restart self after calling setpgid(0, 0)
        exec env DID_SETPGID=true setpgid "$0" "$@"
        # exec failed if control reached this point
        exit 1
    }
    unset DID_SETPGID
    
    # do stuff here

4

POSIXから引用した章で説明されているように、SIGINTはフォアグラウンドプロセスグループ全体に送信されます。したがって、親プログラムの強制終了を回避するには、プログラムを独自のプロセスグループで実行するように調整します。

シェルはsetpgrp組み込みまたは構文の構成を介してにアクセスすることはできませんが、シェルをインタラクティブに実行するという間接的な方法があります。(トリックのStéphaneGimenezに感謝します。)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'

+1は賢いアイデアですが、対話型シェルの場合はシェルの動作が変わることに注意してください(少なくともPOSIXシェルはそうです。私はの詳細に精通していませんksh)。例:${ENV}それはエラーが発生した場合、シェルはすぐに終了しません、供給され、SIGQUITそしてSIGTERM無視されます。
Richard Hansen

1

まあ、あなたがそれを参照したスタックオーバーフローの質問から、親が信号を処理するように構成する必要があることを明確に述べています。

第2に、POSIXリファレンスには、「ISIGが設定されている場合、処理時にINTR文字は破棄される」と明記されています。

これが2つのオプションです。3番目は、独自のプロセスグループで子を実行することです。


お返事をありがとうございます。また、ユーザーがスクリプトを終了する方法を理解する必要があります。ISIGを設定し、同時にいくつかのCONTRLキーの組み合わせ(CTRL-Qが可能)を使用して、親などにシグナルを送信せずにシェルスクリプトを終了する方法はありますか?また、親とは別のプロセスグループで子を実行する方法を教えてください。
Guddu 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.