¿Por qué existe SIGPIPE?

93

Según tengo entendido, SIGPIPEsolo puede ocurrir como resultado de a write(), que puede (y lo hace) devolver -1 y establecerse errnoen EPIPE... Entonces, ¿por qué tenemos la sobrecarga adicional de una señal? Cada vez que trabajo con tuberías ignoro SIGPIPEy nunca he sentido ningún dolor como resultado, ¿me estoy perdiendo algo?

Shea Levy
fuente

Respuestas:

112

No compro la respuesta previamente aceptada. SIGPIPEse genera exactamente cuando writefalla con EPIPE, no de antemano; de hecho, una forma segura de evitar SIGPIPEsin cambiar las disposiciones globales de la señal es enmascararla temporalmente con pthread_sigmask, realizar el write, luego realizar sigtimedwait(con tiempo de espera cero) para consumir cualquier SIGPIPEseñal pendiente (que se envía a el hilo de llamada, no el proceso) antes de desenmascararlo nuevamente.

Creo que la razón por la que SIGPIPEexiste es mucho más simple: establecer un comportamiento sano por defecto para programas puros de "filtro" que continuamente leen la entrada, la transforman de alguna manera y escriben la salida. Sin ellos SIGPIPE, a menos que estos programas manejen explícitamente los errores de escritura y salgan inmediatamente (que podría no ser el comportamiento deseado para todos los errores de escritura, de todos modos), continuarán ejecutándose hasta que se queden sin entrada incluso si su tubería de salida se ha cerrado. Seguro que puede duplicar el comportamiento de SIGPIPEcomprobando explícitamente EPIPEy saliendo, pero el propósito principal SIGPIPEera lograr este comportamiento de forma predeterminada cuando el programador es vago.

R .. GitHub DEJA DE AYUDAR A ICE
fuente
15
+1. La pista está en el hecho de que SIGPIPE lo mata de manera predeterminada: no está diseñado para interrumpir una llamada al sistema, ¡está diseñado para terminar su programa! Si puede manejar la señal en un controlador de señales, también podría manejar el código de retorno de write.
Nicholas Wilson
2
Tienes razón, no sé por qué acepté eso en primer lugar. Esta respuesta tiene sentido, aunque en mi opinión es extraño que, por ejemplo, en Linux esta pereza se logre el kernel y no la libc.
Shea Levy
5
parece que esta respuesta básicamente se reduce a: "porque no tuvimos excepciones". Sin embargo, la gente que ignora los códigos de retorno en C es un problema mucho más amplio que las llamadas write (). ¿Qué hace que la escritura sea tan especial que necesita su propia señal? Quizás los programas de filtro puro son mucho más comunes de lo que imagino.
Arvid
@Arvid SIGPIPE fue inventado por gente de Unix, para resolver un problema que estaban teniendo en su entorno en el que los programas de filtrado son extremadamente comunes. Todo lo que tenemos que hacer es leer los scripts de arranque que hacen aparecer el sistema.
Kaz
@SheaLevy ¿Qué sistemas Unix implementan SIGPIPE únicamente en su libc?
Kaz
23

Porque su programa puede estar esperando E / S o suspendido. Un SIGPIPE interrumpe su programa de forma asincrónica, terminando la llamada al sistema y, por lo tanto, puede manejarse de inmediato.

Actualizar

Considere una tubería A | B | C.

Para ser precisos, asumiremos que B es el ciclo de copia canónico:

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

Bestá bloqueado en la llamada read (2) en espera de datos desde el Amomento en que Cfinaliza. Si espera el código de retorno de write (2) , ¿cuándo lo verá B? La respuesta, por supuesto, no es hasta que A escriba más datos (lo que podría ser una larga espera, ¿qué pasa si A está bloqueado por otra cosa?). Observe, por cierto, que esto también nos permite un programa más simple y limpio. Si dependía del código de error de escritura, necesitaría algo como:

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

Otra actualización

Ajá, estás confundido sobre el comportamiento de la escritura. Verá, cuando el descriptor de archivo con la escritura pendiente se cierra, el SIGPIPE ocurre en ese momento. Si bien la escritura devolverá -1 eventualmente , el objetivo de la señal es notificarle de forma asincrónica que la escritura ya no es posible. Esto es parte de lo que hace que toda la elegante estructura de co-rutina de las tuberías funcione en UNIX.

Ahora, podría señalarle una discusión completa en cualquiera de los varios libros de programación del sistema UNIX, pero hay una mejor respuesta: puede verificarlo usted mismo. Escriba un Bprograma simple [1] - ya tiene las agallas, todo lo que necesita es un mainy algunos incluyen - y agregue un controlador de señal para SIGPIPE. Ejecutar una tubería como

cat | B | more

y en otra ventana de terminal, adjunte un depurador a B y coloque un punto de interrupción dentro del manejador de señal B.

Ahora, mata más y B debería entrar en tu manejador de señales. examine la pila. Verá que la lectura aún está pendiente. deje que el manejador de señales proceda y regrese, y observe el resultado devuelto por write , que luego será -1.

[1] Naturalmente, escribirás tu programa B en C. :-)

Charlie martin
fuente
3
¿Por qué B vería la terminación de C antes con SIGPIPE? B permanecerá bloqueado en la lectura hasta que se escriba algo en su STDIN, momento en el que llamará a write () y solo entonces se generará SIGPIPE / -1.
Shea Levy
2
Realmente me gusta la respuesta: SIGPIPE permite que la muerte se propague desde el extremo de salida de la tubería al instante. Sin esto, se necesita hasta un ciclo del programa de copia para que cada uno de los N elementos de la tubería elimine la tubería y hace que el lado de entrada genere N líneas que nunca llegan al final.
Yttrill
18
Esta respuesta es incorrecta. SIGPIPEse no entregados durante la lectura, pero durante el write. No necesita escribir un programa en C para probarlo, simplemente ejecútelo cat | heady pkill headen una terminal separada. Verá que catfelizmente vive esperando en su, solo read()cuando escribe algo y presiona enter, catmuere con una tubería rota, exactamente porque intentó escribir la salida.
user4815162342
5
-1 SIGPIPEno se puede entregar Bmientras Bestá bloqueado readporque SIGPIPEno se genera hasta que se Bintenta el write. Ningún subproceso puede "estar esperando E / S o suspendido de otra manera" mientras se llama writeal mismo tiempo.
Dan Moulding
3
¿Puede publicar un programa completo que muestre que se SIGPIPEestá levantando mientras está bloqueado en un read? No puedo reproducir ese comportamiento en absoluto (y en realidad no estoy seguro de por qué acepté esto en primer lugar)
Shea Levy
7

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

Este enlace dice:

Un tubo o FIFO debe estar abierto en ambos extremos simultáneamente. Si lee de un archivo de tubería o FIFO que no tiene ningún proceso que escriba en él (quizás porque todos han cerrado el archivo o salido), la lectura devuelve el final del archivo. Escribir en una tubería o FIFO que no tiene un proceso de lectura se trata como una condición de error; genera una señal SIGPIPE y falla con el código de error EPIPE si la señal se maneja o bloquea.

- Macro: int SIGPIPE

Tuberia rota. Si usa tuberías o FIFO, debe diseñar su aplicación para que un proceso abra la tubería para lectura antes de que otro comience a escribir. Si el proceso de lectura nunca comienza o termina inesperadamente, al escribir en la tubería o FIFO se genera una señal SIGPIPE. Si SIGPIPE se bloquea, maneja o ignora, la llamada infractora falla con EPIPE en su lugar.

Los archivos especiales Pipes y FIFO se tratan con más detalle en Pipes y FIFO.

Ankur Agarwal
fuente
5

Creo que es para conseguir que el manejo de errores sea correcto sin requerir mucho código en todo lo que se escribe en una tubería.

Algunos programas ignoran el valor de retorno de write(); sin SIGPIPEellos generarían inútilmente toda la producción.

Los programas que comprueban el valor de retorno write()probablemente impriman un mensaje de error si falla; esto es inapropiado para una tubería rota, ya que en realidad no es un error para toda la tubería.

Jilles
fuente
2

Información de la máquina:

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

gcc versión 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Escribí este código a continuación:

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

}

Salida:

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

Puede ver que en todos los casos SIGPIPEsolo se recibe después de que el proceso de escritura (se intenta escribir) más de 3 caracteres.

¿No prueba esto que SIGPIPEno se genera inmediatamente después de que termina el proceso de lectura, sino después de un intento de escribir algunos datos más en una tubería cerrada?

Ankur Agarwal
fuente