Почему существует SIGPIPE?
Из моего понимания, SIGPIPE
может произойти только в результате write()
, который может (и делает) вернуть -1 и установить errno
в EPIPE
... Так почему у нас есть дополнительные издержки сигнала? Каждый раз, когда я работаю с трубами, я игнорирую SIGPIPE
и никогда не чувствовал никакой боли в результате, я что-то упускаю?
5 ответов
Я не покупаю ранее принятый ответ. SIGPIPE
генерируется именно тогда, когда write
не удается с EPIPE
, не заранее - на самом деле один безопасный способ избежать SIGPIPE
без изменения общего расположения сигнала временно скрыть pthread_sigmask
выполнить write
затем выполните sigtimedwait
(с нулевым тайм-аутом), чтобы использовать любой ожидающий SIGPIPE
сигнал (который отправляется вызывающему потоку, а не процессу) перед повторной его маскировкой.
Я считаю причину SIGPIPE
Существовать гораздо проще: установить нормальное поведение по умолчанию для чистых программ-фильтров, которые непрерывно читают ввод, каким-то образом преобразуют его и записывают вывод. Без SIGPIPE
до тех пор, пока эти программы явно не обработают ошибки записи и сразу не завершат работу (что может быть нежелательным поведением для всех ошибок записи, во всяком случае), они будут продолжать работать до тех пор, пока не закончится ввод, даже если их выходной канал был закрыт. Конечно, вы можете дублировать поведение SIGPIPE
явно проверяя EPIPE
и выход, но вся цель SIGPIPE
было достигнуть этого поведения по умолчанию, когда программист ленив.
Потому что ваша программа может ожидать ввода-вывода или иным образом приостановлена. 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 должен сломаться в твоем обработчике сигналов. осмотрите стек. Вы обнаружите, что чтение еще не завершено. позвольте обработчику сигнала продолжить и вернуться и посмотреть на результат, возвращаемый записью - который тогда будет равен -1.
[1] Естественно, ты напишешь свою B-программу на C.:-)
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.
Я думаю, что это для того, чтобы правильно обрабатывать ошибки, не требуя много кода во всем, что пишет в канал.
Некоторые программы игнорируют возвращаемое значение write()
; без SIGPIPE
они будут бесполезно генерировать всю продукцию.
Программы, которые проверяют возвращаемое значение write()
скорее всего, напечатайте сообщение об ошибке, если оно не получится это неуместно для сломанной трубы, поскольку это не совсем ошибка для всего конвейера.
Информация о машине:
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
генерируется не сразу после завершения процесса чтения, а после попытки записи дополнительных данных в закрытый канал?