|
В настоящее время актуальный стандарт - это 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.
Пример создания и использования канала для ввода данных.