なぜSIGPIPEが存在するのですか?


92

私の理解からは、SIGPIPE唯一の結果として発生する可能性がありwrite()、そのことができます(とし)リターン-1とセットerrnoEPIPE...私たちは信号の余分なオーバーヘッドを持っていますだから、なぜ?パイプを使用するたびに無視SIGPIPEし、結果として痛みを感じたことはありませんが、何か不足していますか?

回答:


111

以前に承認された回答は購入しません。SIGPIPEは事前にwriteEPIPEはなく、で失敗したときに正確に生成されます-実際、SIGPIPEグローバル信号の処理を変更せずに回避する1つの安全な方法は、で一時的にマスクしpthread_sigmask、を実行してwriteから、sigtimedwait(タイムアウトなしで)実行して保留中のSIGPIPE信号(に送信される)を消費することですプロセスではなく呼び出しスレッド)を再度マスク解除します。

理由SIGPIPEはもっと単純だと思います。連続的に入力を読み取り、なんらかの方法で変換し、出力を書き込む純粋な「フィルター」プログラムの正しいデフォルトの動作を確立します。なしではSIGPIPE、これらのプログラムが明示的に書き込みエラーを処理してすぐに終了しない限り(いずれにしても、すべての書き込みエラーにとって望ましい動作とは限りません)、出力パイプが閉じていても、入力がなくなるまで実行を続けます。もちろんSIGPIPE、明示的にチェックしEPIPEて終了することでの動作を複製できますが、その目的はSIGPIPE、プログラマが怠惰なときにデフォルトでこの動作を実現することでした。


15
+1。手掛かりはデフォルトでSIGPIPEがあなたを殺すという事実にあります-それはシステムコールを中断するように設計されていません、それはあなたのプログラムを終了するように設計されています!シグナルハンドラーでシグナルを処理できる場合は、の戻りコードも処理できますwrite
ニコラスウィルソン

2
そうですね、そもそもなぜそれを受け入れたのかはわかりません。この答えは理にかなっていますが、IMOは奇妙ですが、たとえばLinuxでは、この遅延はlibcではなくカーネルによって実現されます。
Shea Levy

5
この答えは、基本的には「例外はなかったため」に要約されているようです。ただし、Cの戻りコードを無視する人は、単なるwrite()呼び出しよりもはるかに広い問題です。書き込みを特別なものにするには、独自のシグナルが必要なのですか?おそらく、純粋なフィルタープログラムは、私が想像しているよりもはるかに一般的です。
Arvid、2015

@Arvid SIGPIPEは、フィルタープログラムが非常に一般的である環境で彼らが抱えていた問題を解決するために、Unixの人々によって発明されました。
Kaz

@SheaLevyどのUnixシステムが純粋にlibcにSIGPIPEを実装していますか?
Kaz

23

あなたのプログラムがI / Oを待っているか、そうでなければ一時停止している可能性があるからです。SIGPIPEはプログラムに非同期に割り込み、システムコールを終了するため、すぐに処理できます。

更新

パイプラインについて考えてみましょうA | B | C

明確にするために、Bは正規のコピーループであると仮定します。

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    write(STDOUT,bufr,sz);

Bが終了するときからのデータを待つread(2)呼び出しでブロックされます。write(2)からの戻りコードを待つ場合、Bはいつそれを見るでしょうか?もちろん、答えはAがさらにデータを書き込むまではありません(これは長い待ち時間になる可能性があります-Aが他の何かによってブロックされている場合はどうなりますか?)。ちなみに、これにより、よりシンプルでクリーンなプログラムも作成できることに注意してください。書き込みのエラーコードに依存している場合は、次のようなものが必要になります。AC

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    if(write(STDOUT,bufr,sz)<0)
        break;

別のアップデート

ああ、あなたは書き込みの動作について混乱しています。保留中の書き込みを含むファイル記述子が閉じられると、SIGPIPEがすぐに発生します。書き込みは最終的に-1を返しますが、シグナルの要点は、書き込みができなくなったことを非同期で通知することです。これは、パイプのエレガントなコルーチン構造全体がUNIXで機能するための一部です。

さて、いくつかのUNIXシステムプログラミングブックでの全体的な議論を紹介することができますが、もっと良い答えがあります。これを自分で確認できます。簡単なBプログラム[1] を書いてください-すでに根性があり、必要なのはmainといくつかのインクルードだけです-そしてのためのシグナルハンドラを追加しSIGPIPEます。次のようなパイプラインを実行する

cat | B | more

別のターミナルウィンドウで、デバッガーをBに接続し、Bシグナルハンドラー内にブレークポイントを配置します。

今、もっと殺すとBはあなたのシグナルハンドラーで壊れるはずです。スタックを調べます。読み取りがまだ保留中であることがわかります。シグナルハンドラが進みましょうと戻り、およびによって返された結果を見て書き込み -であろう、その後も-1。

[1]もちろん、BプログラムはCで記述します。:-)


3
なぜBはSIGPIPEでCの終了をもっと早く見るのでしょうか?Bは、何かがそのSTDINに書き込まれるまで読み取り時にブロックされたままになります。その時点でBはwrite()を呼び出し、それから初めてSIGPIPEが発生し、-1が返されます。
Shea Levy

2
私は答えが本当に好きです:SIGPIPEは、パイプラインの出力端から即座に死を伝播させます。これがないと、パイプラインのN個の要素ごとにコピープログラムの最大1サイクルがかかり、パイプラインが強制終了され、入力側でN行が生成され、最後まで到達しません。
イットリル

18
この答えは間違っています。SIGPIPEは読み取り中には配信されませんが、中には配信されwriteます。あなただけ実行し、それをテストするためのCプログラムを記述する必要はありませんcat | headし、pkill head別の端末インチ あなたはそれを見ることができますcat喜んで待っているに住んでそのread()-onlyあなたが何かを入力し、プレスがする入力したときにcat、それは書き込み出力しようとしたまさにので、壊れたパイプで金型を。
user4815162342 2013年

5
SIGPIPEがを試行するまで生成されないため、ブロックされているBBは-1 を配信できません。同時に呼び出しているスレッドは、「I / Oを待機している、または中断されている」ことはありません。readSIGPIPEBwritewrite
Dan Molding

3
SIGPIPEブロックされている間に発生したことを示す完全なプログラムを投稿できますreadか?私はその振る舞いをまったく再現することができません(そして、なぜ私がこれを最初に受け入れたのか実際にはわかりません)
Shea Levy

7

https://www.gnu.org/software/libc/manual/html_mono/libc.html

このリンクは言う:

パイプまたはFIFOは、両端で同時に開いている必要があります。プロセスへの書き込みがないパイプまたはFIFOファイルから読み取る場合(おそらく、それらがすべてファイルを閉じたか、終了したため)、読み取りはファイルの終わりを返します。読み取りプロセスのないパイプまたはFIFOへの書き込みは、エラー状態として扱われます。SIGPIPEシグナルを生成し、シグナルが処理またはブロックされると、エラーコードEPIPEで失敗します。

—マクロ:int SIGPIPE

壊れたパイプ。パイプまたはFIFOを使用する場合、別のプロセスが書き込みを開始する前に、あるプロセスが読み取り用にパイプを開くようにアプリケーションを設計する必要があります。読み取りプロセスが開始されない、または予期せず終了する場合、パイプまたはFIFOへの書き込みによりSIGPIPEシグナルが発生します。SIGPIPEがブロック、処理、または無視される場合、問題のある呼び出しはEPIPEで失敗します。

パイプとFIFO特殊ファイルについては、パイプとFIFOで詳しく説明しています。


5

私は、パイプへのすべての書き込みで多くのコードを必要とせずにエラー処理を正しく行うことだと思います。

一部のプログラムは、の戻り値を無視しますwrite()。なしでSIGPIPE、彼らは無駄にすべての出力を生成します。

の戻り値をチェックするプログラムは、write()失敗するとエラーメッセージを出力する可能性があります。これは実際にはパイプライン全体のエラーではないため、壊れたパイプには不適切です。


2

マシン情報:

Linux 3.2.0-53-generic#81-Ubuntu SMP Thu Aug 22 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gccバージョン4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)

私はこのコードを以下に書きました:

// Writes characters to stdout in an infinite loop, also counts 
// the number of characters generated and prints them in sighandler
// writestdout.c

# include <unistd.h>
# include <stdio.h>
# include <signal.h>
# include <string.h>

int writeCount = 0;    
void sighandler(int sig) {
    char buf1[30] ;
    sprintf(buf1,"signal %d writeCount %d\n", sig, writeCount);
    ssize_t leng = strlen(buf1);
    write(2, buf1, leng);
    _exit(1);

}

int main() {

    int i = 0;
    char buf[2] = "a";

    struct sigaction ss;
    ss.sa_handler = sighandler;

    sigaction(13, &ss, NULL);

    while(1) {

        /* if (writeCount == 4) {

            write(2, "4th char\n", 10);

        } */

        ssize_t c = write(1, buf, 1);
        writeCount++;

    }

}

// Reads only 3 characters from stdin and exits
// readstdin.c

# include <unistd.h>
# include <stdio.h>

int main() {

    ssize_t n ;        
    char a[5];        
    n = read(0, a, 3);
    printf("read %zd bytes\n", n);
    return(0);

}

出力:

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11486

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 429

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 281

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 490

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 433

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 318

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 468

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11866

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 496

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 284

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 271

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 416

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11268

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 427

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 8812

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 394

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10937

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10931

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 3554

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 499

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 283

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11133

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 451

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 493

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 233

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11397

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 492

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 547

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 441

すべてのインスタンスSIGPIPEで、書き込みプロセスによって3文字以上が書き込まれる(試行される)後にのみ受信されることがわかります。

これはSIGPIPE、読み取りプロセスが終了した直後ではなく、閉じたパイプにさらにデータを書き込もうとした後に生成されたことを証明していませんか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.