Pourquoi SIGPIPE existe-t-il?

93

D'après ce que je comprends, SIGPIPEcela ne peut se produire que comme le résultat de a write(), qui peut (et renvoie) -1 et mis errnoà EPIPE... Alors pourquoi avons-nous la surcharge supplémentaire d'un signal? Chaque fois que je travaille avec des tuyaux, j'ignore SIGPIPEet je n'ai jamais ressenti de douleur en conséquence, est-ce que je rate quelque chose?

Levy au karité
la source

Réponses:

112

Je n'achète pas la réponse précédemment acceptée. SIGPIPEest généré exactement quand le writeéchoue avec EPIPE, pas avant - en fait, un moyen sûr d'éviter SIGPIPEsans changer les dispositions globales du signal est de le masquer temporairement avec pthread_sigmask, d'effectuer le write, puis d'effectuer sigtimedwait(avec zéro timeout) pour consommer tout en attenteSIGPIPE signal en (qui est envoyé à le thread appelant, pas le processus) avant de le démasquer à nouveau.

Je crois que la raison SIGPIPEexiste est beaucoup plus simple: établir un comportement par défaut sain pour les programmes de "filtrage" purs qui lisent continuellement l'entrée, la transforment d'une manière ou d'une autre et écrivent la sortie. Sans SIGPIPE, à moins que ces programmes ne gèrent explicitement les erreurs d'écriture et se terminent immédiatement (ce qui pourrait ne pas être le comportement souhaité pour toutes les erreurs d'écriture, de toute façon), ils continueront de fonctionner jusqu'à ce qu'ils soient à court d'entrée même si leur tube de sortie a été fermé. Bien sûr, vous pouvez dupliquer le comportement de SIGPIPEen vérifiant explicitement EPIPEet en quittant, mais le but principal de SIGPIPEétait d'obtenir ce comportement par défaut lorsque le programmeur est paresseux.

R .. GitHub STOP AIDING ICE
la source
15
+1. L'indice réside dans le fait que SIGPIPE vous tue par défaut - il n'est pas conçu pour interrompre un appel système, il est conçu pour terminer votre programme! Si vous êtes capable de gérer le signal dans un gestionnaire de signaux, vous pouvez tout aussi bien gérer le code retour de write.
Nicholas Wilson
2
Vous avez raison, je ne sais pas pourquoi j'ai accepté cela en premier lieu. Cette réponse a du sens, même si IMO, il est étrange que, par exemple sous Linux, cette paresse soit obtenue par le noyau et non par la libc.
Shea Levy
5
il semble que cette réponse se résume essentiellement à: "parce que nous n'avons pas eu d'exceptions". Cependant, les gens ignorant les codes de retour en C sont un problème beaucoup plus large que les simples appels write (). Qu'est-ce qui rend l'écriture si spéciale qu'elle a besoin de son propre signal? peut-être que les programmes de filtres purs sont beaucoup plus courants que j'imagine.
Arvid
@Arvid SIGPIPE a été inventé par les gens d'Unix, pour résoudre un problème qu'ils rencontraient dans leur environnement dans lequel les programmes de filtrage sont extrêmement courants, il suffit de lire les scripts de démarrage qui mettent en place le système.
Kaz
@SheaLevy Quels systèmes Unix implémentent SIGPIPE uniquement dans leur libc?
Kaz
23

Parce que votre programme peut être en attente d'E / S ou suspendu. Un SIGPIPE interrompt votre programme de manière asynchrone, mettant fin à l'appel système, et peut donc être traité immédiatement.

Mettre à jour

Prenons un pipeline A | B | C.

Juste pour être précis, nous supposerons que B est la boucle de copie canonique:

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

Best bloqué sur l' appel read (2) en attente de données à partir de Aquand se Ctermine. Si vous attendez le code retour de write (2) , quand B le verra-t-il? La réponse, bien sûr, n'est pas tant que A n'a pas écrit plus de données (ce qui pourrait être une longue attente - et si A était bloqué par autre chose?). Notez, en passant, que cela nous permet également un programme plus simple et plus propre. Si vous dépendiez du code d'erreur de l'écriture, vous auriez besoin de quelque chose comme:

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

Une autre mise à jour

Aha, vous êtes confus sur le comportement de l'écriture. Vous voyez, lorsque le descripteur de fichier avec l'écriture en attente est fermé, le SIGPIPE se produit alors. Alors que l'écriture renverra finalement -1 , le but du signal est de vous avertir de manière asynchrone que l'écriture n'est plus possible. Cela fait partie de ce qui fait que toute la structure élégante de co-routine des tubes fonctionne sous UNIX.

Maintenant, je pourrais vous indiquer toute une discussion dans l'un des nombreux livres de programmation système UNIX, mais il y a une meilleure réponse: vous pouvez le vérifier vous-même. Écrivez un Bprogramme simple [1] - vous avez déjà le courage, tout ce dont vous avez besoin est un mainet quelques inclus - et ajoutez un gestionnaire de signal pour SIGPIPE. Exécutez un pipeline comme

cat | B | more

et dans une autre fenêtre de terminal, attachez un débogueur à B et placez un point d'arrêt dans le gestionnaire de signal B.

Maintenant, tuez le plus et B devrait casser votre gestionnaire de signaux. examinez la pile. Vous constaterez que la lecture est toujours en attente. laissez le gestionnaire de signaux continuer et revenir, et regardez le résultat renvoyé par écriture - qui sera alors -1.

[1] Naturellement, vous allez écrire votre programme B en C. :-)

Charlie Martin
la source
3
Pourquoi B verrait-il la résiliation de C plus tôt avec SIGPIPE? B restera bloqué sur la lecture jusqu'à ce que quelque chose soit écrit dans son STDIN, à quel point il appellera le write () et alors seulement SIGPIPE sera levé / -1 sera retourné.
Shea Levy
2
J'aime vraiment la réponse: SIGPIPE permet à la mort de se propager instantanément à partir de l'extrémité de sortie du pipeline. Sans cela, il faut jusqu'à un cycle du programme de copie pour chacun des N éléments du tuyau pour tuer le pipeline et amène le côté entrée à générer N lignes qui n'atteignent jamais la fin.
Yttrill
18
Cette réponse est incorrecte. SIGPIPEn'est pas livré pendant la lecture, mais pendant le write. Vous n'avez pas besoin d'écrire un programme C pour le tester, exécutez simplement cat | headet pkill headdans un terminal séparé. Vous verrez que catvivre heureux en attendant dans son - read()seulement lorsque vous tapez quelque chose et appuyez sur Entrée catmeurt avec un tuyau cassé, exactement parce qu'il a essayé d'écrire la sortie.
user4815162342
5
-1 SIGPIPEne peut pas être livré à Bwhile Best bloqué readcar il SIGPIPEn'est généré que lorsque le Bfichier write. Aucun thread ne peut "être en attente d'E / S ou suspendu d'une autre manière" tout en appelant writeen même temps.
Dan Molding
3
Pouvez-vous publier un programme complet qui montre SIGPIPEêtre soulevé alors qu'il est bloqué sur un read? Je ne peux pas du tout reproduire ce comportement (et je ne sais pas vraiment pourquoi j'ai accepté cela en premier lieu)
Shea Levy
7

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

Ce lien dit:

Un tuyau ou FIFO doit être ouvert aux deux extrémités simultanément. Si vous lisez à partir d'un tube ou d'un fichier FIFO sur lequel aucun processus n'écrit (peut-être parce qu'ils ont tous fermé le fichier, ou quitté), la lecture renvoie la fin du fichier. L'écriture dans un tube ou un FIFO qui n'a pas de processus de lecture est traitée comme une condition d'erreur; il génère un signal SIGPIPE, et échoue avec le code d'erreur EPIPE si le signal est traité ou bloqué.

- Macro: int SIGPIPE

Tuyau cassé. Si vous utilisez des tubes ou des FIFO, vous devez concevoir votre application de manière à ce qu'un processus ouvre le tube en lecture avant qu'un autre commence à écrire. Si le processus de lecture ne démarre jamais ou se termine de manière inattendue, l' écriture dans le tube ou FIFO déclenche un signal SIGPIPE. Si SIGPIPE est bloqué, traité ou ignoré, l'appel incriminé échoue avec EPIPE à la place.

Les tuyaux et les fichiers spéciaux FIFO sont décrits plus en détail dans Pipes et FIFO.

Ankur Agarwal
la source
5

Je pense que c'est pour que la gestion des erreurs soit correcte sans avoir besoin de beaucoup de code dans tout ce qui écrit dans un tube.

Certains programmes ignorent la valeur de retour de write(); sans SIGPIPEelles, toutes les sorties seraient inutilement générées.

Les programmes qui vérifient la valeur de retour de write()probablement impriment un message d'erreur en cas d'échec; ceci est inapproprié pour un tuyau cassé car ce n'est pas vraiment une erreur pour l'ensemble du pipeline.

jilles
la source
2

Informations sur la machine:

Linux 3.2.0-53-generic # 81-Ubuntu SMP jeu.22 août 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc version 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

J'ai écrit ce code ci-dessous:

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

}

Production:

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

Vous pouvez voir que dans chaque instance SIGPIPEn'est reçu qu'après que plus de 3 caractères ont été (essayés d'être) écrits par le processus d'écriture.

Cela ne prouve-t-il pas que ce SIGPIPEn'est pas généré immédiatement après la fin du processus de lecture, mais après une tentative d'écriture de plus de données dans un tube fermé?

Ankur Agarwal
la source