Вопрос или проблема
Сейчас, если я пишу в именованный канал и затем закрываю читателя, писатель получает SIGPIPE и затем завершается. Например,
$ mkfifo pipe
$ cat pipe & # чтение из канала в фоне
$ cat > pipe # запись в канал
line1
line2
...
Если я затем останавливаю процесс читателя, процесс писателя завершается из-за SIGPIPE, который читатель послал при остановке.
Я не хочу, чтобы это происходило. Если писатель пытается записать в канал до появления читателя, писатель блокируется и ждет появления читателя. Я хочу такое поведение. Если читатель закрывает канал, писатель должен блокироваться и ждать нового читателя.
Аналогично, если я останавливаю процесс писателя, процесс читателя закрывается, так как видит EOF (или точнее, пустое чтение). Я бы также хотел, чтобы он блокировался, пока не появится новый писатель.
Есть ли у cat
флаг для этого? Если нет, это должно быть довольно простая программа на C.
man 2 pipe
говорит:
Если все файловые дескрипторы, относящиеся к концу записи канала, были закрыты, то попытка чтения (read(2)) из канала увидит конец файла (read(2) вернет 0). Если все файловые дескрипторы, относящиеся к концу чтения канала, были закрыты, то запись (write(2)) вызовет генерацию сигнала SIGPIPE для вызывающего процесса. Если вызывающий процесс игнорирует этот сигнал, то write(2) завершится с ошибкой EPIPE.
Почему бы вам не игнорировать SIGPIPE
и просто не переоткрыть соединение, когда получаете EOF (со стороны читателя) или EPIPE
(со стороны писателя)?
Вам в любом случае понадобится программа на C. Со стороны записи:
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
int main(int argc, char **argv) {
signal(SIGPIPE, SIG_IGN); // Игнорировать SIGPIPE, чтобы предотвратить завершение
int i = 0;
while (1) {
printf("Открытие %s\n", argv[1]);
FILE *fd = fopen(argv[1], "w");
if (fd == NULL) {
perror (argv[1]);
exit(errno);
}
while(1) {
if (-1 == fprintf(fd, "%d\n", i++)) {
if (errno == EPIPE) {
fclose(fd);
break;
}
}
fflush(fd);
sleep(1);
}
}
return 0;
}
Для программы чтения:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char **argv) {
int i = 0;
char s[100];
while (1) {
printf("Открытие %s\n", argv[1]);
FILE *fd = fopen(argv[1], "r");
if (fd == NULL) {
perror (argv[1]);
exit(errno);
}
while(1) {
if (NULL == fgets(s, sizeof s, fd)) {
fclose(fd);
break;
}
fputs(s, stdout);
}
}
return 0;
}
Результат, когда вы завершаете процесс записи, или завершаете процесс чтения, пока процесс записи все еще идет:
16:15:30:~/src$ ./readpipe /tmp/myfifo
16:15:50
Открытие /tmp/myfifo
0
1
2
3
4
Открытие /tmp/myfifo
0
1
2
3
4
5
Открытие /tmp/myfifo
0
1
2
3
4
^C
16:16:09:~/src$ ./readpipe /tmp/myfifo
16:16:10
Открытие /tmp/myfifo
6
7
8
9
^C
16:16:13:~/src$ ./readpipe /tmp/myfifo
16:16:14
Открытие /tmp/myfifo
11
12
13
14
^C
16:16:19:~/src$
16:15:46:~/src$ ./writepipe /tmp/myfifo
16:15:53
Открытие /tmp/myfifo
^C
16:15:57:~/src$ ./writepipe /tmp/myfifo
16:15:58
Открытие /tmp/myfifo
^C
16:16:03:~/src$ ./writepipe /tmp/myfifo
16:16:04
Открытие /tmp/myfifo
^C
Ответ или решение
Чтобы управлять блокировкой записей в именованный канал (FIFO), когда один из процессов (читатель или писатель) закрывается, и избежать завершения работы процессов из-за сигнала SIGPIPE, необходимо разработать специфическое решение на уровне программирования. Это требует понимания работы сигналов в Unix и управления вводом-выводом. Давайте подробно рассмотрим, как можно добиться такого поведения.
Теория
Именованный канал (FIFO) представляет собой межпроцессное средство передачи данных, позволяющее передавать данные между связанными процессами. Однако работа с ним имеет определенные ограничения:
- SIGPIPE для писателя – Когда читатель закрывает конец канала, писатель, пытающийся записать данные, получает сигнал SIGPIPE и может завершиться, если этот сигнал не обработан.
- EOF для читателя – Когда писатель закрывает конец канала, читатель, пытающийся прочитать данные, получает конец файла (EOF), что может привести к завершению чтения.
В нашей задаче необходимо настроить процессы так, чтобы они блокировались в ожидании появления нового процесса, вместо завершения при закрытии противоположного конца канала.
Пример
Рассмотрим пример кода, который демонстрирует необходимое поведение:
Программа для записи
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
// Главная функция для программы записи
int main(int argc, char **argv) {
// Игнорирование сигнала SIGPIPE для предотвращения завершения работы
signal(SIGPIPE, SIG_IGN);
int i = 0;
while (1) {
printf("Открытие %s для записи\n", argv[1]);
FILE *fd = fopen(argv[1], "w");
if (fd == NULL) {
perror(argv[1]);
exit(errno);
}
while(1) {
if (fprintf(fd, "%d\n", i++) == -1) {
if (errno == EPIPE) {
fclose(fd);
break;
}
}
fflush(fd);
sleep(1);
}
}
return 0;
}
Программа для чтения
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char **argv) {
char s[100];
while (1) {
printf("Открытие %s для чтения\n", argv[1]);
FILE *fd = fopen(argv[1], "r");
if (fd == NULL) {
perror(argv[1]);
exit(errno);
}
while(1) {
if (fgets(s, sizeof s, fd) == NULL) {
fclose(fd);
break;
}
fputs(s, stdout);
}
}
return 0;
}
Применение
-
Игнорирование SIGPIPE: Сигнал SIGPIPE генерируется, когда нет ни одного открытого приемника на другом конце канала. Устанавливая обработчик signal(SIGPIPE, SIG_IGN), мы предотвращаем завершение процесса записи.
-
Переподключение: Когда одна из сторон закрывается, другая сторона закрывает свой файловый дескриптор и возвращается в режим ожидания, пытаясь переподключиться. Это позволяет процессам вновь начинать обмен данными, как только другая сторона стала доступной.
-
Блокировка: В ситуации, когда одна сторона канала временно отсутствует, программа просто закрывает текущее соединение и ожидает повторного открытия канала, имитируя желаемое поведение "блокировки", пока не появится другой процесс.
Таким образом, мы определили метод, позволяющий программно реализовать блокировку операций записи и чтения в случае закрытия одной из сторон в именованных каналах. Этот подход требует написания собственных программ вероятнее всего на С или другом системном языке, что позволяет полностью управлять поведением при межпроцессном взаимодействии.