Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2557 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 8:

Средства межпроцессного взаимодействия

Аннотация: Рассматриваются средства локального межпроцессного взаимодействия - каналы, сигналы, очереди сообщений, семафоры, разделяемые сегменты памяти.
Ключевые слова: локальное межпроцессное взаимодействие, межпроцессное взаимодействие, канал, mkfifo, файловый дескриптор, программа, буферизованный ввод/вывод, обмен данными, запись, признак конца файла, ПО, дескриптор, функция, поток, стандартный ввод, стандартный вывод, сигнал, событие, исключительная ситуация, таймер, пользователь, генерация сигнала, доставка сигнала, маска сигналов, игнорирование сигнала, реакция, список, повторная входимость, АСБ-функция, SIG, значение, идентификатор процесса, посылка сигнала, идентификатор, действующий идентификатор, пдп-идентификатор, опция, создание файла, завершение процесса, арифметическая операция, команда, сигнал прерывания, таймер профилирования, системный вызов, сокет, getpid, функция обработки сигнала, способ обработки сигнала, приложение, набор сигналов, константы, срабатывание таймера, операции, очереди сообщений, представление, деление, переполнение, вещественная арифметика, индекс, адрес, объект, аргумент, поле, контекст процесса, специальная встроенная команда, trap, перенаправление вывода, файл, маска, блокировка сигнала, ждущий сигнал, прием сигнала, пара функций, критический интервал, вывод, режим ожидания, X/Open-расширение системного интерфейса, семафор, разделяемый сегмент памяти, набор семафоров, идентификатор средства межпроцессного взаимодействия, ключ, маршрутное имя, структура данных, SEM, управление доступом, информация, байт, отправка сообщения, активное средство межпроцессного взаимодействия, очередь, операции с блокировкой, операции без блокировки, права, идентификатор очереди сообщения, управление очередью сообщений, прием сообщения, Дополнение, создание очереди сообщения, тело сообщения, размер сообщения, длина, указатель, цикл работы, родительский процесс, корректность, увеличение значения семафора, уменьшение значения семафора, ожидание обнуления значения семафора, управляющие, идентификатор набора семафоров, значение семафора, доступ, операционная система, вызов функции, массив, задача об обедающих философах, место, конечные, выход, статистика, память, пространство, виртуальная память, сегмент, создание разделяемого сегмента, права доступа, управляющие действия над разделяемыми сегментами, идентификатор разделяемого сегмента, присоединение разделяемого сегмента

Каналы

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

К числу наиболее простых и в то же время самых употребительных средств межпроцессного взаимодействия принадлежат каналы, представляемые файлами соответствующего типа. Стандарт 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. Пример создания и использования канала для ввода данных.
Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?