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

Стоит отметить, что прерывание операции write() сигналом SIGPIPE обрабатывается особым образом. Обычно, когда обработчик сигнала прерывает запись (или другой «медленный» вызов), операция либо автоматически перезапускается, либо завершается ошибкой EINTR — это зависит от того, был ли обработчик установлен с помощью флага SA_RESTART для вызова sigaction() (см. раздел 21.5). Сигнал SIGPIPE ведет себя иначе, поскольку автоматический перезапуск записи или оповещение о ее прерывании обработчиком (предполагается, что тогда операция write() может быть перезапущена вручную) не имеет никакого смысла. Ни в том ни в другом случае последующий вызов write() не сможет завершиться успешно, так как канал останется поврежденным.

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

Еще одна причина для закрытия неиспользуемых дескрипторов состоит в том, что канал можно уничтожить и освободить его ресурсы только после того, как будут закрыты все дескрипторы во всех процессах, ссылающихся на данный канал. В этот момент любые данные, которые в нем еще оставались, теряются.

Пример программы

Программа, представленная в листинге 44.2, демонстрирует применение канала для взаимодействия родительского и дочернего процессов. Данный пример иллюстрирует ранее озвученный нами факт: каналы являются потоками байтов. Для этого родитель выполняет запись за одну операцию, а потомок считывает данные небольшими блоками.

Главная программа открывает канал с помощью вызова pipe() и вызывает fork(), чтобы создать дочерний процесс . После этого родитель закрывает свой файловый дескриптор для чтения из канала и записывает в другой конец канала строку , переданную программе в виде аргумента командной строки. Затем родитель закрывает считывающий конец канала и делает вызов wait(), чтобы дождаться завершения дочернего процесса . После закрытия своего дескриптора для записывающего конца канала потомок входит в цикл, в котором считывает из канала блоки данных (размером до BUF_SIZE байт) и записывает их в стандартный вывод. Обнаружив символ конца файла , потомок выходит из цикла , выводит в конце символ новой строки, закрывает свой дескриптор для чтения из канала и завершается.

Вот какой результат можно получить, если запустить программу из листинга 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]);

if (pipe(pfd) == -1) /* Создаем канал */

errExit("pipe");

switch (fork()) {

case -1:

errExit("fork");

case 0: /* Потомок читает из канала */

 if (close(pfd[1]) == -1) /* Записывающий конец не используется */

errExit("close — child");

for (;;) { /* Считываем данные из канала и направляем их в стандартный вывод */

 numRead = read(pfd[0], buf, BUF_SIZE);

if (numRead == -1)

errExit("read");

 if (numRead == 0)

break; /* Конец файла */

 if (write(STDOUT_FILENO, buf, numRead)!= numRead)

fatal("child — partial/failed write");

}

 write(STDOUT_FILENO, "\n", 1);

if (close(pfd[0]) == -1)

errExit("close");

_exit(EXIT_SUCCESS);

default: /* Родитель записывает в канал */

 if (close(pfd[0]) == -1) /* Считывающий конец не используется */

errExit("close — parent");

 if (write(pfd[1], argv[1], strlen(argv[1]))!= strlen(argv[1]))

fatal("parent — partial/failed write");

 if (close(pfd[1]) == -1) /* Потомок увидит символ завершения файла */

errExit("close");

 wait(NULL); /* Ждем завершения потомка */

exit(EXIT_SUCCESS);

}

}

pipes/simple_pipe.c

44.3. Каналы как средство синхронизации процессов
Перейти на страницу:

Похожие книги