В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Средства межпроцессного взаимодействия
Каналы
Средства локального межпроцессного взаимодействия реализуют высокопроизводительную, детерминированную передачу данных между процессами в пределах одной системы.
К числу наиболее простых и в то же время самых употребительных средств межпроцессного взаимодействия принадлежат каналы, представляемые файлами соответствующего типа. Стандарт POSIX-2001 различает именованные и безымянные каналы. Напомним, что первые создаются функцией mkfifo() и одноименной служебной программой, а вторые - функцией pipe(). Именованным каналам соответствуют элементы файловой системы, ко вторым можно обращаться только посредством файловых дескрипторов. В остальном эти разновидности каналов эквивалентны.
Взаимодействие между процессами через канал может быть установлено следующим образом: один из процессов создает канал и передает другому соответствующий открытый файловый дескриптор. После этого процессы обмениваются данными через канал при помощи функций read() и write(). Примером подобного взаимодействия служит программа, показанная в листинге 8.1.
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/wait.h> /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через канал. */ /* Используются функции ввода/вывода нижнего уровня */ #define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: " int main (void) { int fd [2]; char buf [1]; int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */ /* перед отображением очередной строки */ /* Создадим безымянный канал */ if (pipe (fd) < 0) { perror ("PIPE"); exit (1); } switch (fork ()) { case -1: perror ("FORK"); exit (2); case 0: /* Чтение из канала и выдачу на стандартный вывод */ /* реализуем в порожденном процессе. */ /* Необходимо закрыть дескриптор, предназначенный */ /* для записи в канал, иначе чтение не завершится */ /* по концу файла */ close (fd [1]); while (read (fd [0], buf, 1) == 1) { if (write (1, buf, 1) != 1) { perror ("WRITE TO STDOUT"); break; } } exit (0); } /* Чтение со стандартного ввода и запись в канал */ /* возложим на родительский процесс. */ /* Из соображений симметрии закроем дескриптор, */ /* предназначенный для чтения из канала */ close (fd [0]); if (write (fd [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) != sizeof (MY_PROMPT) - 1) { perror ("WRITE TO PIPE-1"); } while (read (0, buf, 1) == 1) { /* Перед отображением очередной строки */ /* нужно выдать сообщение MY_MSG */ if (new_line) { if (write (fd [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) { perror ("WRITE TO PIPE-2"); break; } } if (write (fd [1], buf, 1) != 1) { perror ("WRITE TO PIPE-3"); break; } new_line = (buf [0] == '\n'); } close (fd [1]); (void) wait (NULL); return (0); }Листинг 8.1. Пример взаимодействия между процессами через канал с помощью функций ввода/вывода нижнего уровня.
Решение той же задачи, но с использованием функций буферизованного ввода/вывода, показано в листинге 8.2.
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <limits.h> #include <sys/wait.h> #include <assert.h> /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через канал. */ /* Используются функции буферизованного ввода/вывода */ int main (void) { int fd [2]; FILE *fp [2]; char line [LINE_MAX]; /* Создадим безымянный канал */ if (pipe (fd) < 0) { perror ("PIPE"); exit (1); } /* Сформируем потоки по файловым дескрипторам канала */ assert ((fp [0] = fdopen (fd [0], "r")) != NULL); assert ((fp [1] = fdopen (fd [1], "w")) != NULL); /* Отменим буферизацию вывода */ setbuf (stdout, NULL); setbuf (fp [1], NULL); switch (fork ()) { case -1: perror ("FORK"); exit (2); case 0: /* Чтение из канала и выдачу на стандартный вывод */ /* реализуем в порожденном процессе. */ /* Необходимо закрыть поток, предназначенный для */ /* записи в канал, иначе чтение не завершится */ /* по концу файла */ fclose (fp [1]); while (fgets (line, sizeof (line), fp [0]) != NULL) { if (fputs (line, stdout) == EOF) { break; } } exit (0); } /* Чтение со стандартного ввода и запись в канал */ /* возложим на родительский процесс. */ /* Из соображений симметрии закроем поток, */ /* предназначенный для чтения из канала */ fclose (fp [0]); fputs ("Вводите строки\n", fp [1]); while (fgets (line, sizeof (line), stdin) != NULL) { if ((fputs ("Вы ввели: ", fp [1]) == EOF) || (fputs (line, fp [1]) == EOF)) { break; } } fclose (fp [1]); (void) wait (NULL); return (0); }Листинг 8.2. Пример взаимодействия между процессами через канал с помощью функций буферизованного ввода/вывода.
Если не указано противное, обмен данными через канал происходит в синхронном режиме: процесс, пытающийся читать из пустого канала, открытого кем-либо на запись, приостанавливается до тех пор, пока данные не будут в него записаны; с другой стороны, запись в полный канал задерживается до освобождения необходимого для записи места. Чтобы отменить подобный режим взаимодействия, надо связать с дескрипторами канала флаг статуса O_NONBLOCK (это может быть сделано при помощи функции fcntl() ). В таком случае чтение или запись, которые невозможно выполнить немедленно, завершаются неудачей.
Подчеркнем, что при попытке чтения из пустого канала результат равен 0 (как признак конца файла), только если канал не открыт кем-либо на запись. Под "кем-либо" понимается и сам читающий процесс; по этой причине в приведенной выше программе потребовалось закрыть все экземпляры файлового дескриптора fd [1], возвращенного функцией pipe() как дескриптор для записи в канал.
Функция popen(), описанная выше, при рассмотрении командного интерпретатора, является более высокоуровневой по сравнению с pipe(). Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение в его рамках заданной команды, организует канал между вызывающим и порожденным процессами и формирует необходимые потоки для этого канала. Если при обращении к popen() задан режим " w ", то стандартный ввод команды, выполняющейся в рамках порожденного процесса, перенаправляется на конец канала, предназначенный для чтения; если задан режим " r ", то в канал перенаправляется стандартный вывод.
После вызова popen() процесс может писать в канал или читать из него посредством функций буферизованного ввода/вывода, используя сформированный поток. Канал остается открытым до момента вызова функции pclose() (см. листинг 8.3).
#include <stdio.h> int pclose (FILE *stream);Листинг 8.3. Описание функции pclose().
Функция pclose() не только закрывает поток, сформированный popen(), но и дожидается завершения порожденного процесса, возвращая его статус.
Типичное применение popen() - организация канала для выдачи динамически порождаемых данных на устройство печати командой lp (см. листинг 8.4).
#include <stdio.h> /* Программа печатает несколько первых строк треугольника Паскаля */ #define T_SIZE 16 int main (void) { FILE *outptr; long tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */ int i, j; /* Инициализируем массив, чтобы далее все элементы */ /* можно было считать и выводить единообразно */ tp [0] = 1; for (i = 1; i < T_SIZE; i++) { tp [i] = 0; } /* Создадим канал с командой */ if ((outptr = popen ("lp", "w")) == NULL) { perror ("POPEN"); return (-1); } (void) fprintf (outptr, "\nТреугольник Паскаля:\n"); for (i = 0; i < T_SIZE; i++) { /* Элементы очередной строки нужно считать от конца к началу */ /* Элемент tp [0] пересчитывать не нужно */ for (j = i; j > 0; j--) { tp [j] += tp [j - 1]; } /* Вывод строки треугольника в канал */ for (j = 0; j <= i; j++) { (void) fprintf (outptr, " %ld", tp [j]); } (void) fprintf (outptr, "\n"); } return (pclose (outptr)); }Листинг 8.4. Пример создания и использования канала для вывода данных.
Сходным образом можно организовать канал для чтения результатов выполнения команды (см. листинг 8.5).
#include <stdio.h> #include <limits.h> #include <assert.h> #define MY_CMD "ls -l *.c" int main (void) { FILE *inptr; char line [LINE_MAX]; assert ((inptr = popen (MY_CMD, "r")) != NULL); while (fgets (line, sizeof (line), inptr) != NULL) { fputs (line, stdout); } return (pclose (inptr)); }Листинг 8.5. Пример создания и использования канала для ввода данных.