Nach meinem Verständnis SIGPIPEkann dies nur als Ergebnis von a auftreten write(), das -1 zurückgeben kann (und tut) und errnoauf EPIPE... gesetzt wird. Warum haben wir also den zusätzlichen Overhead eines Signals? Vermisse ich jedes Mal etwas, wenn ich mit Rohren arbeite, die ich ignoriere SIGPIPEund infolgedessen nie Schmerzen verspürt habe?
Ich kaufe die zuvor akzeptierte Antwort nicht. SIGPIPEwird genau dann generiert, wenn das writefehlschlägt EPIPE, nicht im Voraus. Eine sichere Möglichkeit, dies zu vermeiden, SIGPIPEohne die globalen Signalanordnungen zu ändern, besteht darin, es vorübergehend zu maskieren pthread_sigmask, auszuführen writeund dann sigtimedwait(ohne Zeitüberschreitung) auszuführen , um ein anstehendes SIGPIPESignal zu verbrauchen (das an gesendet wird) den aufrufenden Thread, nicht den Prozess), bevor er erneut entlarvt wird.
Ich glaube, der Grund SIGPIPEdafür ist viel einfacher: Ein vernünftiges Standardverhalten für reine "Filter" -Programme festzulegen, die kontinuierlich Eingaben lesen, irgendwie transformieren und Ausgaben schreiben. Ohne SIGPIPE, sofern diese Programme Schreibfehler nicht explizit behandeln und sofort beenden (was ohnehin nicht das gewünschte Verhalten für alle Schreibfehler ist), werden sie so lange ausgeführt, bis ihnen die Eingabe ausgeht, selbst wenn ihre Ausgabepipe geschlossen wurde. Sicher, Sie können das Verhalten von duplizieren, SIGPIPEindem Sie explizit nach suchen EPIPEund es beenden, aber der gesamte Zweck von SIGPIPEbestand darin, dieses Verhalten standardmäßig zu erreichen, wenn der Programmierer faul ist.
+1. Der Hinweis liegt in der Tatsache, dass SIGPIPE Sie standardmäßig tötet - es ist nicht dazu gedacht, einen Systemaufruf zu unterbrechen, sondern Ihr Programm zu beenden! Wenn Sie das Signal in einem Signalhandler verarbeiten können, können Sie auch den Rückkehrcode von verarbeiten write.
Nicholas Wilson
2
Du hast recht, ich weiß nicht, warum ich das überhaupt akzeptiert habe. Diese Antwort ist sinnvoll, obwohl IMO es seltsam ist, dass z. B. unter Linux diese Faulheit vom Kernel und nicht von der libc erreicht wird.
Shea Levy
5
Es klingt so, als würde diese Antwort im Grunde genommen auf "weil wir keine Ausnahmen hatten" hinauslaufen. Das Ignorieren von Rückkehrcodes in C ist jedoch ein viel größeres Problem als nur write () -Aufrufe. Was macht das Schreiben so besonders, dass es ein eigenes Signal benötigt? Vielleicht sind die reinen Filterprogramme viel häufiger, als ich mir vorstelle.
Arvid
@Arvid SIGPIPE wurde von Unix-Leuten erfunden, um ein Problem zu lösen, das sie in ihrer Umgebung hatten, in der Filterprogramme extrem häufig sind. Wir müssen nur die Boot-Skripte lesen, die das System aufrufen.
Kaz
@SheaLevy Welche Unix-Systeme implementieren SIGPIPE nur in ihrer Bibliothek?
Kaz
23
Weil Ihr Programm möglicherweise auf E / A wartet oder auf andere Weise angehalten wird. Ein SIGPIPE unterbricht Ihr Programm asynchron, beendet den Systemaufruf und kann so sofort verarbeitet werden.
Aktualisieren
Betrachten Sie eine Pipeline A | B | C.
Nur zur Bestimmtheit nehmen wir an, dass B die kanonische Kopierschleife ist:
Bwird beim Aufruf von read (2) blockiert und wartet auf Daten ab dem AZeitpunkt der CBeendigung. Wenn Sie auf den Rückkehrcode von write (2) warten , wann wird B ihn sehen? Die Antwort ist natürlich erst, wenn A mehr Daten schreibt (was eine lange Wartezeit sein kann - was ist, wenn A durch etwas anderes blockiert wird?). Beachten Sie übrigens, dass dies uns auch ein einfacheres und saubereres Programm ermöglicht. Wenn Sie beim Schreiben auf den Fehlercode angewiesen wären, benötigen Sie Folgendes:
Aha, du bist verwirrt über das Verhalten des Schreibens. Sie sehen, wenn der Dateideskriptor mit dem ausstehenden Schreibvorgang geschlossen wird, geschieht das SIGPIPE sofort. Während der Schreibvorgang schließlich -1 zurückgibt , besteht der Sinn des Signals darin, Sie asynchron zu benachrichtigen, dass der Schreibvorgang nicht mehr möglich ist. Dies ist Teil dessen, was die gesamte elegante Co-Routine-Struktur von Pipes unter UNIX zum Funktionieren bringt.
Jetzt könnte ich Sie auf eine ganze Diskussion in einem der verschiedenen UNIX-Systemprogrammierbücher hinweisen, aber es gibt eine bessere Antwort: Sie können dies selbst überprüfen. Schreiben Sie ein einfaches BProgramm [1] - Sie haben bereits den Mut, alles, was Sie brauchen, ist ein mainund einige enthält - und fügen Sie einen Signalhandler für hinzu SIGPIPE. Führen Sie eine Pipeline wie aus
cat | B | more
Fügen Sie in einem anderen Terminalfenster einen Debugger an B hinzu und setzen Sie einen Haltepunkt in den B-Signalhandler.
Töte jetzt mehr und B sollte deinen Signalhandler einbrechen. Untersuche den Stapel. Sie werden feststellen, dass der Lesevorgang noch aussteht. Lassen Sie den Signalhandler fortfahren und zurückkehren und sehen Sie sich das vom Schreiben zurückgegebene Ergebnis an - das dann -1 ist.
[1] Natürlich schreibst du dein B-Programm in C. :-)
Warum sollte B die Kündigung von C mit SIGPIPE früher sehen? B bleibt beim Lesen blockiert, bis etwas in seine STDIN geschrieben wird. An diesem Punkt ruft es write () auf und erst dann wird SIGPIPE ausgelöst / -1 zurückgegeben.
Shea Levy
2
Die Antwort gefällt mir sehr gut: Mit SIGPIPE kann sich der Tod sofort vom Ausgabeende der Pipeline zurück ausbreiten. Ohne dies dauert es bis zu einem Zyklus des Kopierprogramms, bis jedes der N Elemente der Pipe die Pipeline beendet, und die Eingangsseite erzeugt N Linien, die niemals das Ende erreichen.
Yttrill
18
Diese Antwort ist falsch. SIGPIPEwird nicht während des Lesens geliefert, sondern während des write. Sie müssen kein C-Programm schreiben, um es zu testen. Führen Sie es einfach aus cat | headund pkill headin einem separaten Terminal. Sie werden sehen, dass catdas Warten glücklich davon lebt - read()nur wenn Sie etwas eingeben und die Eingabetaste drücken, catstirbt es mit einem kaputten Rohr, genau weil es versucht hat, eine Ausgabe zu schreiben.
user4815162342
5
-1 SIGPIPEkann nicht zugestellt werden, Bsolange Bes blockiert ist, readda SIGPIPEes erst generiert wird, wenn Bversucht wird, das write. Kein Thread kann "auf E / A warten oder anderweitig angehalten" sein, während er gleichzeitig anruft write.
Dan Moulding
3
Können Sie ein vollständiges Programm veröffentlichen, das zeigt SIGPIPE, dass es ausgelöst wird, während es auf einem blockiert ist read? Ich kann dieses Verhalten überhaupt nicht reproduzieren (und bin mir nicht sicher, warum ich das überhaupt akzeptiert habe)
Ein Rohr oder FIFO muss an beiden Enden gleichzeitig offen sein. Wenn Sie aus einer Pipe- oder FIFO-Datei lesen, in die keine Prozesse geschrieben wurden (möglicherweise, weil alle die Datei geschlossen oder beendet haben), gibt der Lesevorgang das Dateiende zurück. Das Schreiben in eine Pipe oder einen FIFO ohne Lesevorgang wird als Fehlerbedingung behandelt. es erzeugt ein SIGPIPE-Signal, und schlägt mit dem Fehlercode EPIPE fehl, wenn das Signal verarbeitet oder blockiert wird.
- Makro: int SIGPIPE
Rohrbruch. Wenn Sie Pipes oder FIFOs verwenden, müssen Sie Ihre Anwendung so gestalten, dass ein Prozess die Pipe zum Lesen öffnet, bevor ein anderer mit dem Schreiben beginnt. Wenn der Lesevorgang nie beginnt oder unerwartet endet, löst das Schreiben in die Pipe oder den FIFO ein SIGPIPE-Signal aus.Wenn SIGPIPE blockiert, behandelt oder ignoriert wird, schlägt der störende Anruf stattdessen mit EPIPE fehl.
Pipes und FIFO-Spezialdateien werden in Pipes und FIFOs ausführlicher behandelt.
Ich denke, es geht darum, die Fehlerbehandlung korrekt zu gestalten, ohne dass bei allem, was in eine Pipe geschrieben wird, viel Code erforderlich ist.
Einige Programme ignorieren den Rückgabewert von write(); ohne SIGPIPEsie würden sie nutzlos alle Ausgaben erzeugen.
Programme, die den Rückgabewert von write()wahrscheinlich prüfen, drucken eine Fehlermeldung, wenn dies fehlschlägt. Dies ist für ein defektes Rohr ungeeignet, da es nicht wirklich ein Fehler für die gesamte Pipeline ist.
Linux 3.2.0-53-generic # 81-Ubuntu SMP Do 22. August 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
gcc Version 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)
Ich habe diesen Code unten geschrieben:
// 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);
}
Sie können sehen, dass in jedem Fall SIGPIPEerst empfangen wird, nachdem mehr als 3 Zeichen durch den Schreibvorgang geschrieben wurden (versucht wurden).
Beweist dies nicht, dass dies SIGPIPEnicht unmittelbar nach Beendigung des Lesevorgangs generiert wird, sondern nach dem Versuch, weitere Daten in eine geschlossene Pipe zu schreiben?
write
.Weil Ihr Programm möglicherweise auf E / A wartet oder auf andere Weise angehalten wird. Ein SIGPIPE unterbricht Ihr Programm asynchron, beendet den Systemaufruf und kann so sofort verarbeitet werden.
Aktualisieren
Betrachten Sie eine Pipeline
A | B | C
.Nur zur Bestimmtheit nehmen wir an, dass B die kanonische Kopierschleife ist:
B
wird beim Aufruf von read (2) blockiert und wartet auf Daten ab demA
Zeitpunkt derC
Beendigung. Wenn Sie auf den Rückkehrcode von write (2) warten , wann wird B ihn sehen? Die Antwort ist natürlich erst, wenn A mehr Daten schreibt (was eine lange Wartezeit sein kann - was ist, wenn A durch etwas anderes blockiert wird?). Beachten Sie übrigens, dass dies uns auch ein einfacheres und saubereres Programm ermöglicht. Wenn Sie beim Schreiben auf den Fehlercode angewiesen wären, benötigen Sie Folgendes:Ein weiteres Update
Aha, du bist verwirrt über das Verhalten des Schreibens. Sie sehen, wenn der Dateideskriptor mit dem ausstehenden Schreibvorgang geschlossen wird, geschieht das SIGPIPE sofort. Während der Schreibvorgang schließlich -1 zurückgibt , besteht der Sinn des Signals darin, Sie asynchron zu benachrichtigen, dass der Schreibvorgang nicht mehr möglich ist. Dies ist Teil dessen, was die gesamte elegante Co-Routine-Struktur von Pipes unter UNIX zum Funktionieren bringt.
Jetzt könnte ich Sie auf eine ganze Diskussion in einem der verschiedenen UNIX-Systemprogrammierbücher hinweisen, aber es gibt eine bessere Antwort: Sie können dies selbst überprüfen. Schreiben Sie ein einfaches
B
Programm [1] - Sie haben bereits den Mut, alles, was Sie brauchen, ist einmain
und einige enthält - und fügen Sie einen Signalhandler für hinzuSIGPIPE
. Führen Sie eine Pipeline wie ausFügen Sie in einem anderen Terminalfenster einen Debugger an B hinzu und setzen Sie einen Haltepunkt in den B-Signalhandler.
Töte jetzt mehr und B sollte deinen Signalhandler einbrechen. Untersuche den Stapel. Sie werden feststellen, dass der Lesevorgang noch aussteht. Lassen Sie den Signalhandler fortfahren und zurückkehren und sehen Sie sich das vom Schreiben zurückgegebene Ergebnis an - das dann -1 ist.
[1] Natürlich schreibst du dein B-Programm in C. :-)
quelle
SIGPIPE
wird nicht während des Lesens geliefert, sondern während deswrite
. Sie müssen kein C-Programm schreiben, um es zu testen. Führen Sie es einfach auscat | head
undpkill head
in einem separaten Terminal. Sie werden sehen, dasscat
das Warten glücklich davon lebt -read()
nur wenn Sie etwas eingeben und die Eingabetaste drücken,cat
stirbt es mit einem kaputten Rohr, genau weil es versucht hat, eine Ausgabe zu schreiben.SIGPIPE
kann nicht zugestellt werden,B
solangeB
es blockiert ist,read
daSIGPIPE
es erst generiert wird, wennB
versucht wird, daswrite
. Kein Thread kann "auf E / A warten oder anderweitig angehalten" sein, während er gleichzeitig anruftwrite
.SIGPIPE
, dass es ausgelöst wird, während es auf einem blockiert istread
? Ich kann dieses Verhalten überhaupt nicht reproduzieren (und bin mir nicht sicher, warum ich das überhaupt akzeptiert habe)https://www.gnu.org/software/libc/manual/html_mono/libc.html
Dieser Link sagt:
Ein Rohr oder FIFO muss an beiden Enden gleichzeitig offen sein. Wenn Sie aus einer Pipe- oder FIFO-Datei lesen, in die keine Prozesse geschrieben wurden (möglicherweise, weil alle die Datei geschlossen oder beendet haben), gibt der Lesevorgang das Dateiende zurück. Das Schreiben in eine Pipe oder einen FIFO ohne Lesevorgang wird als Fehlerbedingung behandelt. es erzeugt ein SIGPIPE-Signal, und schlägt mit dem Fehlercode EPIPE fehl, wenn das Signal verarbeitet oder blockiert wird.
- Makro: int SIGPIPE
Rohrbruch. Wenn Sie Pipes oder FIFOs verwenden, müssen Sie Ihre Anwendung so gestalten, dass ein Prozess die Pipe zum Lesen öffnet, bevor ein anderer mit dem Schreiben beginnt. Wenn der Lesevorgang nie beginnt oder unerwartet endet, löst das Schreiben in die Pipe oder den FIFO ein SIGPIPE-Signal aus.Wenn SIGPIPE blockiert, behandelt oder ignoriert wird, schlägt der störende Anruf stattdessen mit EPIPE fehl.
Pipes und FIFO-Spezialdateien werden in Pipes und FIFOs ausführlicher behandelt.
quelle
Ich denke, es geht darum, die Fehlerbehandlung korrekt zu gestalten, ohne dass bei allem, was in eine Pipe geschrieben wird, viel Code erforderlich ist.
Einige Programme ignorieren den Rückgabewert von
write()
; ohneSIGPIPE
sie würden sie nutzlos alle Ausgaben erzeugen.Programme, die den Rückgabewert von
write()
wahrscheinlich prüfen, drucken eine Fehlermeldung, wenn dies fehlschlägt. Dies ist für ein defektes Rohr ungeeignet, da es nicht wirklich ein Fehler für die gesamte Pipeline ist.quelle
Maschineninfo:
Linux 3.2.0-53-generic # 81-Ubuntu SMP Do 22. August 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
gcc Version 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)
Ich habe diesen Code unten geschrieben:
Ausgabe:
Sie können sehen, dass in jedem Fall
SIGPIPE
erst empfangen wird, nachdem mehr als 3 Zeichen durch den Schreibvorgang geschrieben wurden (versucht wurden).Beweist dies nicht, dass dies
SIGPIPE
nicht unmittelbar nach Beendigung des Lesevorgangs generiert wird, sondern nach dem Versuch, weitere Daten in eine geschlossene Pipe zu schreiben?quelle