Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Обновление

Рассмотрим трубопровод A | B | C.

Для определенности будем считать, что B - канонический цикл копирования:

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

B блокируется при вызове чтения (2), ожидающем данные из A, когда C завершается. Если вы ждете кода возврата от записи (2), когда он увидит его? Ответ, конечно же, не до тех пор, пока 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 должен сломаться в вашем обработчике сигналов. проверьте стек. Вы обнаружите, что чтение еще не принято. пусть обработчик сигнала продолжит и вернется, и посмотрим на результат, возвращаемый write, который будет равен -1.

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

Ответ 3

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

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

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

Ответ 4

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

В этой ссылке говорится:

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

- Макро: int SIGPIPE

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

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

Ответ 5

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

Linux 3.2.0-53-generiС# 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++;

    }

}

====================   // Читает только 3 символа из stdin   //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 не создается сразу после завершения процесса чтения, но после попытки записать еще несколько данных в закрытый канал?