SIGINTが親プロセスに伝播するのを防ぎます

SIGINTが親プロセスに伝播するのを防ぎます

親プログラム(C ++プログラムまたはシェルスクリプトのいずれか)がサブシェルスクリプトを実行するシナリオを考えてみましょう。サブシェルスクリプトの実行中にCtrl + C(またはINTR文字で構成される文字)を押すと、SIGINTは次のようになります。のすべてのプロセスをフォアグラウンドプロセスグループに送信します。これには親プロセスが含まれます。

源泉:POSIX.1-2008 XBDセクション11.1.9

このデフォルトの動作をオーバーライドする方法はありますか?子プロセスはシグナルを親プロセスに伝播せずに単独で処理しますか?

引用:スタックオーバーフローポスト - 子プロセスが中断されると親プロセスは完了しません(TRAP INT)

ベストアンサー1

(Gilesの答えからインスピレーションを得た)

フラグが設定されている場合、スクリプトが親プロセスをインポートせずにインポートする唯一の方法は、ISIG独自のプロセスグループにスクリプトを配置することです。これはオプションで行うことができます。ChildSIGINTSIGINTset -m

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

ここにいる-mPOSIX オプションの説明:

-m実装がユーザー移植性ユーティリティオプションをサポートしている場合は、このオプションをサポートする必要があります。すべてのジョブは独自のプロセスグループで実行する必要があります。バックグラウンドジョブが完了した直後にシェルプロンプトが表示される前に、バックグラウンドジョブの終了ステータスを報告するメッセージを標準エラーに記録する必要があります。フォアグラウンドジョブが停止すると、シェルはジョブユーティリティで説明されている形式で標準エラーに関するメッセージを作成する必要があります。また、ジョブが終了せずに状態が変更された場合(たとえば、入力または出力が停止した場合、または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ことが起こります。CChild

$ ./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. SIGINT期間中に無視されるように、フォアグラウンドプロセスグループの他のプロセスを変更しますChild。これで問題は解決しませんが、欲しいものを手に入れることができます。
  2. に変更Child:
    1. stty -g現在の端末設定をバックアップするために使用されます。
    2. 、および文字を含む信号は実行stty -isig時に生成されません。INTRQUITSUSP
    3. バックグラウンドで端末入力を読み取り、必要に応じて信号自体を送信します(例:kill -QUIT 0読み取り+で実行、読み取り+で実行)。これは些細な問題ではなく、スクリプトや実行中のすべての項目が対話型である場合、スムーズに機能しない可能性があります。Control\kill -INT $$ControlCChild
    4. 終了する前に端末設定を復元してください(好ましくは上記EXIT)。
  3. を実行する代わりに、stty -isigユーザーが殺す前Enterに押すか、他の非特殊キーを押すのを待つことを除いて、#2と同じですChild
  4. setpgidsetpgid()以下はおおよその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
    

おすすめ記事