Perché esiste SIGPIPE?


93

Dalla mia comprensione, SIGPIPEpuò verificarsi solo come risultato di a write(), che può (e fa) restituire -1 e impostato errnosu EPIPE... Allora perché abbiamo l'overhead extra di un segnale? Ogni volta che lavoro con le pipe ignoro SIGPIPEe non ho mai sentito alcun dolore di conseguenza, mi manca qualcosa?

Risposte:


112

Non compro la risposta precedentemente accettata. SIGPIPEviene generato esattamente quando writefallisce con EPIPE, non in anticipo - in effetti un modo sicuro per evitare SIGPIPEsenza cambiare le disposizioni del segnale globale è mascherarlo temporaneamente pthread_sigmask, eseguire il write, quindi eseguire sigtimedwait(con timeout zero) per consumare qualsiasi SIGPIPEsegnale in sospeso (che viene inviato a il thread chiamante, non il processo) prima di smascherarlo di nuovo.

Credo che la ragione SIGPIPEesista sia molto più semplice: stabilire un comportamento di default sano per programmi puri di "filtro" che leggono continuamente l'input, lo trasformano in qualche modo e scrivono l'output. Senza SIGPIPE, a meno che questi programmi non gestiscano esplicitamente gli errori di scrittura e terminino immediatamente (che potrebbe non essere il comportamento desiderato per tutti gli errori di scrittura, comunque), continueranno a funzionare finché non esauriscono l'input anche se la loro pipe di output è stata chiusa. Sicuramente puoi duplicare il comportamento di SIGPIPEcontrollando esplicitamente EPIPEe uscendo, ma lo scopo di SIGPIPEera quello di ottenere questo comportamento di default quando il programmatore è pigro.


15
+1. L'indizio sta nel fatto che SIGPIPE ti uccide per impostazione predefinita: non è progettato per interrompere una chiamata di sistema, è progettato per terminare il tuo programma! Se sei in grado di gestire il segnale in un gestore di segnali, potresti anche gestire il codice di ritorno di write.
Nicholas Wilson

2
Hai ragione, non so perché l'ho accettato in primo luogo. Questa risposta ha senso, anche se IMO è strano che ad esempio su Linux questa pigrizia sia ottenuta dal kernel e non dalla libc.
Shea Levy

5
suona come questa risposta fondamentalmente si riduce a: "perché non abbiamo eccezioni". Tuttavia, le persone che ignorano i codici di ritorno in C sono un problema molto più ampio delle semplici chiamate write (). Cosa rende la scrittura così speciale da aver bisogno del proprio segnale? forse i programmi di filtro puro sono molto più comuni di quanto immagino.
Arvid

@Arvid SIGPIPE è stato inventato da persone Unix, per risolvere un problema che stavano riscontrando nel loro ambiente in cui i programmi di filtro sono estremamente comuni, tutto ciò che dobbiamo fare è leggere gli script di avvio che attivano il sistema.
Kaz

@SheaLevy Quali sistemi Unix implementano SIGPIPE esclusivamente nella loro libc?
Kaz

23

Perché il tuo programma potrebbe essere in attesa di I / O o altrimenti sospeso. Un SIGPIPE interrompe il programma in modo asincrono, terminando la chiamata di sistema e quindi può essere gestito immediatamente.

Aggiornare

Considera una pipeline A | B | C.

Solo per chiarezza, assumeremo che B sia il ciclo di copia canonico:

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

Bè bloccato sulla chiamata di lettura (2) in attesa di dati da Aquando Ctermina. Se aspetti il ​​codice di ritorno da write (2) , quando lo vedrà B? La risposta, ovviamente, non è finché A non scrive più dati (il che potrebbe richiedere una lunga attesa - cosa succede se A è bloccato da qualcos'altro?). Si noti, a proposito, che questo ci consente anche un programma più semplice e pulito. Se dipendevi dal codice di errore della scrittura, avresti bisogno di qualcosa come:

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

Un altro aggiornamento

Aha, sei confuso sul comportamento di chi scrive. Vedete, quando il descrittore di file con la scrittura in sospeso viene chiuso, il SIGPIPE si verifica in quel momento. Mentre la scrittura alla fine restituirà -1 , il punto centrale del segnale è notificarti in modo asincrono che la scrittura non è più possibile. Questo fa parte di ciò che fa funzionare l'intera elegante struttura co-routine delle pipe in UNIX.

Ora, potrei indicarti un'intera discussione in uno qualsiasi dei numerosi libri di programmazione del sistema UNIX, ma c'è una risposta migliore: puoi verificarlo tu stesso. Scrivi un semplice Bprogramma [1] - hai già il coraggio, tutto ciò di cui hai bisogno è un maine alcuni include - e aggiungi un gestore di segnale per SIGPIPE. Esegui una pipeline come

cat | B | more

e in un'altra finestra di terminale, collega un debugger a B e metti un punto di interruzione all'interno del gestore del segnale B.

Ora, uccidi di più e B dovrebbe rompere il tuo gestore di segnali. esaminare la pila. Scoprirai che la lettura è ancora in sospeso. lascia che il gestore del segnale proceda e ritorni, e guarda il risultato restituito da write - che sarà quindi -1.

[1] Naturalmente, scriverai il tuo programma B in C. :-)


3
Perché B vede la cessazione di C prima con SIGPIPE? B rimarrà bloccato durante la lettura fino a quando qualcosa non verrà scritto nel suo STDIN, a quel punto chiamerà write () e solo allora verrà sollevato SIGPIPE / -1 verrà restituito.
Shea Levy

2
Mi piace molto la risposta: SIGPIPE lascia che la morte si propaghi all'istante dall'estremità di output della pipeline. Senza questo, ci vuole fino a un ciclo del programma di copia per ciascuno degli N elementi della pipe per terminare la pipeline e fa sì che il lato di input generi N linee che non raggiungono mai la fine.
Yttrill

18
Questa risposta non è corretta. nonSIGPIPE viene consegnato durante la lettura, ma durante il . Non è necessario scrivere un programma C per testarlo, basta eseguirlo e in un terminale separato. Vedrai che vive felicemente nell'attesa nel suo — solo quando digiti qualcosa e premi Invio muore con un tubo rotto, esattamente perché ha provato a scrivere l'output. writecat | headpkill headcatread()cat
user4815162342

5
-1 SIGPIPEnon può essere consegnato a Bmentre Bè bloccato readperché SIGPIPEnon viene generato fino a quando non si Btenta il file write. Nessun thread può "essere in attesa di I / O o sospeso in altro modo" durante la chiamata writeallo stesso tempo.
Dan Molding

3
Puoi pubblicare un programma completo che mostra di SIGPIPEessere sollevato mentre è bloccato su un read? Non riesco affatto a riprodurre quel comportamento (e in realtà non sono sicuro del motivo per cui l'ho accettato in primo luogo)
Shea Levy

7

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

Questo collegamento dice:

Un tubo o FIFO deve essere aperto ad entrambe le estremità contemporaneamente. Se leggi da un file pipe o FIFO che non ha alcun processo in scrittura (forse perché hanno tutti chiuso il file o sono usciti), la lettura restituisce end-of-file. La scrittura su una pipe o FIFO che non dispone di un processo di lettura viene trattata come una condizione di errore; genera un segnale SIGPIPE e fallisce con il codice di errore EPIPE se il segnale è gestito o bloccato.

- Macro: int SIGPIPE

Tubo rotto. Se usi pipe o FIFO, devi progettare la tua applicazione in modo che un processo apra la pipe per la lettura prima che un altro inizi a scrivere. Se il processo di lettura non inizia o termina in modo imprevisto, la scrittura nella pipe o FIFO genera un segnale SIGPIPE. Se SIGPIPE viene bloccato, gestito o ignorato, la chiamata offensiva fallisce invece con EPIPE.

Pipes e file speciali FIFO sono discussi più dettagliatamente in Pipes e FIFO.


5

Penso che sia per ottenere la corretta gestione degli errori senza richiedere molto codice in tutto ciò che scrive su una pipe.

Alcuni programmi ignorano il valore di ritorno di write(); senza SIGPIPEgenererebbero inutilmente tutto l'output.

I programmi che controllano il valore di ritorno o write()probabilmente stampano un messaggio di errore se fallisce; questo non è appropriato per un tubo rotto in quanto non è realmente un errore per l'intera pipeline.


2

Informazioni sulla macchina:

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 versione 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Ho scritto questo codice di seguito:

// 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);

}

Produzione:

$ ./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

Puoi vedere che in ogni istanza SIGPIPEviene ricevuto solo dopo che più di 3 caratteri sono stati (tentati di essere) scritti dal processo di scrittura.

Questo non prova che SIGPIPEnon viene generato immediatamente dopo il termine del processo di lettura, ma dopo un tentativo di scrivere altri dati su una pipe chiusa?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.