Записывающий процесс закрывает считывающий дескриптор канала по другой причине. Если попытаться записать в канал, на другом конце которого нет открытого дескриптора, ядро пошлет сигнал SIGPIPE. По умолчанию это приводит к завершению процесса, хотя он может его перехватить или проигнорировать — в таком случае запись в канал завершится ошибкой EPIPE (канал поврежден). Получение сигнала SIGPIPE или ошибки EPIPE позволяет узнать состояние канала, поэтому неиспользуемый считывающий дескриптор следует закрывать.
Стоит отметить, что прерывание операции write() сигналом SIGPIPE обрабатывается особым образом. Обычно, когда обработчик сигнала прерывает запись (или другой «медленный» вызов), операция либо автоматически перезапускается, либо завершается ошибкой EINTR — это зависит от того, был ли обработчик установлен с помощью флага SA_RESTART для вызова sigaction() (см. раздел 21.5). Сигнал SIGPIPE ведет себя иначе, поскольку автоматический перезапуск записи или оповещение о ее прерывании обработчиком (предполагается, что тогда операция write() может быть перезапущена вручную) не имеет никакого смысла. Ни в том ни в другом случае последующий вызов write() не сможет завершиться успешно, так как канал останется поврежденным.
Если записывающий процесс не закроет считывающий конец канала, то сможет продолжать запись даже после того, как другой процесс закроет свой считывающий дескриптор. В какой-то момент записывающий процесс заполнит канал, и следующая попытка записи будет навсегда заблокирована.
Еще одна причина для закрытия неиспользуемых дескрипторов состоит в том, что канал можно уничтожить и освободить его ресурсы только после того, как будут закрыты все дескрипторы во всех процессах, ссылающихся на данный канал. В этот момент любые данные, которые в нем еще оставались, теряются.
Программа, представленная в листинге 44.2, демонстрирует применение канала для взаимодействия родительского и дочернего процессов. Данный пример иллюстрирует ранее озвученный нами факт: каналы являются потоками байтов. Для этого родитель выполняет запись за одну операцию, а потомок считывает данные небольшими блоками.
Главная программа открывает канал с помощью вызова pipe()
Вот какой результат можно получить, если запустить программу из листинга 44.2:
$ ./simple_pipe 'It was a bright cold day in April, '\
'and the clocks were striking thirteen.'
It was a bright cold day in April, and the clocks were striking thirteen.
Листинг 44.2. Использование канала для взаимодействия родительского и дочернего процессов
pipes/simple_pipe.c
#include
#include "tlpi_hdr.h"
#define BUF_SIZE 10
int
main(int argc, char *argv[])
{
int pfd[2]; /* Файловые дескрипторы канала */
char buf[BUF_SIZE];
ssize_t numRead;
if (argc!= 2 || strcmp(argv[1], " — help") == 0)
usageErr("%s string\n", argv[0]);
errExit("pipe");
case -1:
errExit("fork");
case 0: /* Потомок читает из канала */
errExit("close — child");
for (;;) { /* Считываем данные из канала и направляем их в стандартный вывод */
if (numRead == -1)
errExit("read");
break; /* Конец файла */
fatal("child — partial/failed write");
}
if (close(pfd[0]) == -1)
errExit("close");
_exit(EXIT_SUCCESS);
default: /* Родитель записывает в канал */
errExit("close — parent");
fatal("parent — partial/failed write");
errExit("close");
exit(EXIT_SUCCESS);
}
}
pipes/simple_pipe.c