Почему существует SIGPIPE? - PullRequest
       2

Почему существует SIGPIPE?

86 голосов
/ 03 декабря 2011

Насколько я понимаю, SIGPIPE может произойти только в результате write(), который может (и делает) вернуть -1 и установить errno в EPIPE ... Так почему же у нас есть дополнительныенакладные расходы на сигнал?Каждый раз, когда я работаю с трубами, я игнорирую SIGPIPE и никогда не чувствую никакой боли в результате, я что-то упускаю?

Ответы [ 5 ]

104 голосов
/ 18 февраля 2012

Я не покупаю ранее принятый ответ. SIGPIPE генерируется именно тогда, когда write завершается неудачно с EPIPE, не заранее - фактически один из безопасных способов избежать SIGPIPE без изменения глобального расположения сигналов - это временно замаскировать его с помощью pthread_sigmask, выполнить write затем выполните sigtimedwait (с нулевым тайм-аутом), чтобы получить любой ожидающий сигнал SIGPIPE (который отправляется вызывающему потоку, а не процессу), прежде чем снова его демаскировать.

Я полагаю, что причина существования SIGPIPE гораздо проще: установить нормальное поведение по умолчанию для чистых программ-фильтров, которые непрерывно читают ввод, каким-то образом преобразуют его и записывают вывод. Без SIGPIPE, если только эти программы явно не обрабатывают ошибки записи и сразу же завершают работу (что может быть нежелательным поведением для всех ошибок записи, во всяком случае), они будут продолжать работать до тех пор, пока не закончится ввод, даже если их выходной канал был закрыт. Конечно, вы можете продублировать поведение SIGPIPE, явно проверив наличие EPIPE и выйдя, но основная цель SIGPIPE состояла в том, чтобы добиться такого поведения по умолчанию, когда программист ленив.

25 голосов
/ 03 декабря 2011

Поскольку ваша программа может ожидать ввода-вывода или иным образом приостановлена. SIGPIPE прерывает вашу программу асинхронно, завершая системный вызов, и поэтому может быть обработан немедленно.

Обновление

Рассмотрим конвейер A | B | C.

Просто для определенности предположим, что B - это канонический цикл копирования:

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

B блокируется при вызове read (2) , ожидающем данных от A, когда C завершается. Если вы ждете код возврата от write (2) , когда B его увидит? Ответ, конечно, не до тех пор, пока A не запишет больше данных (что может быть долгим ожиданием - что если A заблокирован чем-то другим?). Заметьте, кстати, что это также позволяет нам более простую и понятную программу. Если вы зависели от кода ошибки записи, вам понадобится что-то вроде:

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

Еще одно обновление

Ага, вы запутались в поведении записи. Видите ли, когда файловый дескриптор с ожидающей записью закрывается, SIGPIPE происходит именно тогда. В то время как запись вернет -1 , в конечном итоге , весь смысл сигнала в том, чтобы асинхронно уведомить вас, что запись больше невозможна. Это часть того, что заставляет всю элегантную параллельную структуру труб работать в UNIX.

Теперь я могу указать вам на целое обсуждение в любой из нескольких книг по системному программированию для UNIX, но есть лучший ответ: вы можете проверить это самостоятельно. Напишите простую B программу [1] - у вас уже есть смелость, все, что вам нужно - это main и некоторые из них - и добавьте обработчик сигнала для SIGPIPE. Запустите конвейер как

cat | B | more

и в другом окне терминала присоедините отладчик к B и установите точку останова в обработчике сигнала B.

Теперь убейте больше , и B должен сломаться в вашем обработчике сигналов. осмотрите стек. Вы обнаружите, что read все еще ожидает рассмотрения. позвольте обработчику сигнала продолжить и вернуться и посмотреть на результат, возвращаемый write - который будет , затем будет -1.

[1] Естественно, вы напишите свою программу на B на C.: -)

7 голосов
/ 24 сентября 2013

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

Эта ссылка говорит:

Труба или FIFO должны быть открыты на обоих концах одновременно. Если вы читаете из канала или файла FIFO, у которого нет процессов, записывающих в него (возможно, потому что все они закрыли файл или вышли из него), чтение возвращает конец файла. Запись в канал или FIFO, который не имеет процесса чтения, рассматривается как условие ошибки; он генерирует сигнал SIGPIPE и завершается ошибкой с кодом ошибки EPIPE, если сигнал обрабатывается или блокируется.

- Макрос: int SIGPIPE

Разбитая труба. Если вы используете каналы или FIFO, вы должны спроектировать свое приложение так, чтобы один процесс открыл канал для чтения, прежде чем другой начнет писать. Если процесс чтения никогда не начинается или неожиданно завершается, запись в канал или FIFO вызывает сигнал SIGPIPE. Если SIGPIPE заблокирован, обработан или проигнорирован, вызывающий сбой вызов завершается с помощью EPIPE.

Трубы и специальные файлы FIFO более подробно обсуждаются в разделе Трубы и FIFO.

5 голосов
/ 04 декабря 2011

Я думаю, что это нужно для правильной обработки ошибок, не требуя большого количества кода во всем, что пишет в канал.

Некоторые программы игнорируют возвращаемое значение write();без SIGPIPE они будут бесполезно генерировать все выходные данные.

Программы, проверяющие возвращаемое значение write(), скорее всего, напечатают сообщение об ошибке, если оно не выполнится;это неуместно для сломанной трубы, поскольку это не является ошибкой для всего конвейера.

2 голосов
/ 25 сентября 2013

Информация о машине:

Linux 3.2.0-53-generic # 81-Ubuntu SMP, четверг, 22 августа 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc версия 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Я написал этот код ниже:

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

}

Вывод:

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

Вы можете видеть, что в каждом случае SIGPIPE принимается только после того, как в процессе записи было написано более 3 символов.

Разве это не доказывает, что SIGPIPE генерируется не сразу после завершения процесса чтения, а после попытки записи дополнительных данных в закрытый канал?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...