Por que existe o SIGPIPE?


92

Do meu entendimento, SIGPIPEsó pode ocorrer como resultado de a write(), que pode (e retorna) -1 e definido errnocomo EPIPE... Então, por que temos a sobrecarga extra de um sinal? Sempre que trabalho com canos, ignoro SIGPIPEe nunca senti nenhuma dor como resultado, estou perdendo alguma coisa?

Respostas:


111

Eu não compro a resposta aceita anteriormente. SIGPIPEé gerado exatamente quando o writefalha com EPIPE, não de antemão - na verdade, uma maneira segura de evitar SIGPIPEsem alterar as disposições globais do sinal é mascará-lo temporariamente com pthread_sigmask, executar o writee executar sigtimedwait(com tempo limite zero) para consumir qualquer SIGPIPEsinal pendente (que é enviado para o thread de chamada, não o processo) antes de desmascará-lo novamente.

Acredito que a razão de SIGPIPEexistir é muito mais simples: estabelecer um comportamento padrão lógico para programas de "filtro" puro que continuamente leem a entrada, a transformam de alguma forma e gravam a saída. Sem SIGPIPE, a menos que esses programas tratem explicitamente dos erros de gravação e saiam imediatamente (o que pode não ser o comportamento desejado para todos os erros de gravação, de qualquer maneira), eles continuarão executando até que fiquem sem entrada, mesmo que seu canal de saída tenha sido fechado. Claro que você pode duplicar o comportamento de SIGPIPEverificando EPIPEe saindo explicitamente , mas o objetivo inteiro SIGPIPEera atingir esse comportamento por padrão quando o programador é preguiçoso.


15
+1. A pista está no fato de que o SIGPIPE mata você por padrão - ele não foi projetado para interromper uma chamada de sistema, mas para encerrar seu programa! Se você for capaz de manipular o sinal em um manipulador de sinal, também poderá manipular o código de retorno de write.
Nicholas Wilson

2
Você está certo, não sei por que aceitei isso em primeiro lugar. Essa resposta faz sentido, embora seja estranho que, por exemplo, no Linux, essa preguiça seja alcançada pelo kernel e não pela libc.
Shea Levy

5
parece que essa resposta basicamente se resume a: "porque não tínhamos exceções". No entanto, pessoas ignorando códigos de retorno em C é um problema muito mais amplo do que apenas chamadas write (). O que torna a gravação tão especial que precisa de seu próprio sinal? talvez os programas de filtro puro sejam muito mais comuns do que imagino.
Arvid

@Arvid SIGPIPE foi inventado pelo pessoal do Unix, para resolver um problema que eles estavam tendo em seu ambiente no qual programas de filtro são extremamente comuns. Tudo o que temos que fazer é ler os scripts de boot que inicializam o sistema.
Kaz

@SheaLevy Quais sistemas Unix implementam SIGPIPE puramente em sua libc?
Kaz

23

Porque seu programa pode estar aguardando E / S ou suspenso. Um SIGPIPE interrompe seu programa de forma assíncrona, encerrando a chamada do sistema e, portanto, pode ser tratado imediatamente.

Atualizar

Considere um pipeline A | B | C.

Apenas para definição, vamos supor que B é o loop de cópia canônico:

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

Bé bloqueado na chamada read (2) em espera por dados Aquando Ctermina. Se você esperar pelo código de retorno de write (2) , quando B o verá? A resposta, é claro, não é até que A grave mais dados (o que pode ser uma longa espera - e se A for bloqueado por outra coisa?). Observe, a propósito, que isso também nos permite um programa mais simples e mais limpo. Se dependesse do código de erro de gravação, você precisaria de algo como:

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

Outra atualização

Aha, você está confuso sobre o comportamento da escrita. Veja, quando o descritor de arquivo com a gravação pendente é fechado, o SIGPIPE acontece imediatamente. Embora a gravação retorne -1 eventualmente , o objetivo do sinal é notificá-lo de forma assíncrona de que a gravação não é mais possível. Isso é parte do que faz com que toda a elegante estrutura co-rotineira de tubos funcione no UNIX.

Agora, eu poderia apontar uma discussão inteira em qualquer um dos vários livros de programação de sistemas UNIX, mas há uma resposta melhor: você pode verificar isso sozinho. Escreva um Bprograma simples [1] - você já tem coragem, tudo que você precisa é um maine alguns inclusos - e adicione um manipulador de sinal para SIGPIPE. Execute um pipeline como

cat | B | more

e em outra janela de terminal, conecte um depurador a B e coloque um ponto de interrupção dentro do manipulador de sinal B.

Agora, mate o mais e B deve interromper seu manipulador de sinal. examine a pilha. Você verá que a leitura ainda está pendente. deixe o sinal manipulador de prosseguir e retorno, e olhar para o resultado retornado por write - que irá então ser -1.

[1] Naturalmente, você escreverá seu programa B em C. :-)


3
Por que B veria o término de C mais cedo com o SIGPIPE? B permanecerá bloqueado na leitura até que algo seja escrito em seu STDIN, momento em que chamará write () e somente então SIGPIPE será gerado / -1 será retornado.
Shea Levy

2
Gosto muito da resposta: o SIGPIPE permite que a morte se propague de volta da extremidade de saída do pipeline instantaneamente. Sem isso, leva até um ciclo do programa de cópia para cada um dos N elementos do tubo para interromper o pipeline e faz com que o lado da entrada gere N linhas que nunca chegam ao fim.
Yttrill

18
Esta resposta está incorreta. nãoSIGPIPE é entregue durante a leitura, mas durante a . Você não precisa escrever um programa C para testá-lo, apenas execute e em um terminal separado. Você verá que felizmente vive esperando em seu - somente quando você digita algo e pressiona enter, morre com um tubo quebrado, exatamente porque tentou escrever a saída. writecat | headpkill headcatread()cat
user4815162342

5
-1 SIGPIPEnão pode ser entregue Benquanto Bestá bloqueado readporque SIGPIPEnão é gerado até que as Btentativas de write. Nenhum encadeamento pode "estar esperando por E / S ou de outra forma suspenso" enquanto chama writeao mesmo tempo.
Dan Molding

3
Você pode postar um programa completo que mostra SIGPIPEsendo gerado enquanto bloqueado em um read? Não consigo reproduzir esse comportamento (e não tenho certeza de por que aceitei isso em primeiro lugar)
Shea Levy

7

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

Este link diz:

Um tubo ou FIFO deve ser aberto em ambas as extremidades simultaneamente. Se você ler de um pipe ou arquivo FIFO que não possui nenhum processo gravando nele (talvez porque todos tenham fechado o arquivo ou saído), a leitura retorna o fim do arquivo. A gravação em um pipe ou FIFO que não tem um processo de leitura é tratada como uma condição de erro; ele gera um sinal SIGPIPE e falha com o código de erro EPIPE se o sinal for manipulado ou bloqueado.

- Macro: int SIGPIPE

Cano quebrado. Se você usa canais ou FIFOs, deve projetar seu aplicativo de forma que um processo abra o canal para leitura antes que outro comece a gravar. Se o processo de leitura nunca começa ou termina inesperadamente, escrever no pipe ou FIFO gera um sinal SIGPIPE. Se SIGPIPE for bloqueado, tratado ou ignorado, a chamada incorreta falhará com EPIPE.

Pipes e arquivos especiais FIFO são discutidos com mais detalhes em Pipes e FIFOs.


5

Acho que é para obter o tratamento de erros correto sem exigir muito código em tudo escrito em um pipe.

Alguns programas ignoram o valor de retorno de write(); sem SIGPIPEeles iriam gerar inutilmente toda a saída.

Os programas que verificam o valor de retorno write()provável imprimem uma mensagem de erro se falhar; isso é impróprio para um tubo quebrado, pois não é realmente um erro para todo o pipeline.


2

Informação da máquina:

Linux 3.2.0-53-generic # 81-Ubuntu SMP Qui. 22 de agosto 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc versão 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Escrevi este código abaixo:

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

}

Resultado:

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

Você pode ver que em todas as instâncias SIGPIPEsó é recebido depois que mais de 3 caracteres são (tentam ser) escritos pelo processo de escrita.

Isso não prova que SIGPIPEnão é gerado imediatamente após o término do processo de leitura, mas após uma tentativa de gravar mais alguns dados em um tubo fechado?

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.