Опубликован: 06.12.2004 | Доступ: свободный | Студентов: 1096 / 126 | Оценка: 4.76 / 4.29 | Длительность: 20:58:00
ISBN: 978-5-9556-0021-5
Лекция 4:

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

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >

Разделяемые сегменты памяти

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

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

Минимальный мобильный программный интерфейс к объектам в разделяемой памяти включает функции открытия (возможно, с созданием) подобного объекта и получения его дескриптора, а также удаления ранее созданного объекта.

Стандартный программный интерфейс состоит из двух функций: shm_open() и shm_unlink() (см. листинг 4.19).

#include <sys/mman.h>

int shm_open (const char *name, 
    int oflag, mode_t mode);

int shm_unlink (const char *name);
Листинг 4.20. Описание функций shm_open() и shm_unlink().

При открытии с помощью функции shm_open() возвращается файловый дескриптор. Имя (аргумент name ) трактуется стандартным для рассматриваемых средств межпроцессного взаимодействия образом. Посредством аргумента oflag могут указываться флаги O_RDONLY, O_RDWR, O_CREAT, O_EXCL и/или O_TRUNC. Если объект создается, то режим доступа к нему формируется в соответствии со значением аргумента mode и маской создания файлов процесса.

После создания объект в разделяемой памяти существует, пока не будет удален функцией shm_unlink(). Он сохраняет свое состояние после закрытия всех ссылающихся на него дескрипторов, однако эффект от перезагрузки системы стандарт POSIX-2001 не специфицирует.

Представляется естественным, что способ доступа к объектам определяется типом дескриптора, возвращаемого при их открытии. Если это адрес, то доступ сводится к операциям чтения/записи из/в память. Если это файловый дескриптор, то для доступа должны использоваться функции файлового ввода/вывода - read(), write() и т.п. Подобное естественное применение объектов в разделяемой памяти иллюстрируется двухпроцессной программой, копирующей строки со стандартного ввода на стандартный вывод (см. листинги 4.21, 4.22, 4.23). Предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством execl(), - "g_r_shm".

#ifndef g_SHM
#define g_SHM

/* Имя объекта в разделяемой памяти */
#define O_SHM_NAME 	"/g_o.shm"

/* Используемый номер сигнала 
   реального времени */
#define SIG_SHM   SIGRTMIN

/* Используемые значения сигнала 
   реального времени */
#define SIGVAL_LINE	0
#define SIGVAL_EOF 	EOF

#endif
Листинг 4.21. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод.
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа, состоящая из двух процессов, копирует  */
/* строки со стандартного ввода на стандартный вывод,*/
/* "прокачивая" их через разделяемый сегмент памяти. */
/* Для синхронизации доступа к разделяемому сегменту */
/* используются сигналы реального времени            */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/wait.h>
#include <assert.h>

#include "g_shm.h"

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Создание разделяемого сегмента памяти,            */
/* чтение со стандартного ввода и запись строк в сегмент*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
	int fd_shm; /* Дескриптор объекта в разделяемой памяти*/
	FILE *fp; /* Поток для записи в объект */
	char line [LINE_MAX]; /* Буфер для копируемых строк */
	struct sigaction sact; /* Структура для обработки сигналов */
	union sigval sg_val;/* Значение сигнала */
	int sg_no; /* Номер принятого сигнала */
	pid_t cpid;/* Идентификатор порожденного процесса */
	/* Создадим разделяемый сегмент памяти */
	if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 
			0777)) < 0) {
		perror ("SHM_CREAT");
		return (1);
	}

	/* Сформируем поток данных по файловому дескриптору */
	/* объекта в разделяемой памяти */
	assert ((fp = fdopen (fd_shm, "w")) != NULL);

	/* Отменим буферизацию вывода */
	setbuf (fp, NULL);

	/* Сформируем маску сигналов (блокируем SIG_SHM) */
	(void) sigemptyset (&sact.sa_mask);
	(void) sigaddset (&sact.sa_mask, SIG_SHM);
	(void) sigprocmask (SIG_BLOCK, &sact.sa_mask, 
		(sigset_t *) NULL);

	/* Установим для сигнала SIG_SHM флаг SA_SIGINFO */
	sact.sa_flags = SA_SIGINFO;
	sact.sa_sigaction = (void (*) (int, siginfo_t *, 
		void *)) SIG_DFL;
	(void) sigaction (SIG_SHM, &sact, 
		(struct sigaction *) NULL);


	/* Подготовительная работа закончена */

	switch (cpid = fork ()) {
		case -1:
			perror ("FORK");
			return (2);
		case 0:
			/* Чтение из объекта и выдачу на стандартный */
			/* вывод реализуем в порожденном процессе */
			if (execl ("./g_r_shm", "g_r_shm", 
				(char *) NULL) < 0) {
				perror ("EXECL");
				return (3);
			}
	}

	/* Чтение со стандартного ввода и запись в объект */
	/* возложим на родительский процесс.*/
	/* В начальный момент объект в разделяемой памяти*/
	/* доступен для записи */
	assert (fseek (fp, 0, SEEK_SET) == 0);
	fputs ("Вводите строки\n", fp);
	/* Сообщим порожденному процессу,*/
	/* что объект в разделяемой памяти заполнен*/
	sg_val.sival_int = SIGVAL_LINE;
	assert (sigqueue (cpid, SIG_SHM, sg_val) == 0);

	while (fgets (line, sizeof (line), stdin) != NULL) {
		assert (fseek (fp, 0, SEEK_SET) == 0);
		/* Дождемся, когда в объект можно будет писать */
		if ((sigwait (&sact.sa_mask, &sg_no) != 0) || 
				(sg_no != SIG_SHM)) {
			return (4);
		}
		assert (fputs ("Вы ввели: ", fp) != EOF);
		assert (fputs (line, fp) != EOF);
		assert (sigqueue (cpid, SIG_SHM, sg_val) == 0);
	}
	/* Сообщим о конце файла */
	sg_val.sival_int = SIGVAL_EOF;
	assert (sigqueue (cpid, SIG_SHM, sg_val) == 0);

	fclose (fp);

	(void) wait (NULL);

	if (shm_unlink (O_SHM_NAME) != 0) {
		perror ("SHM_UNLINK");
		return (7);
	}

	return (0);
}
Листинг 4.22. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Процесс читает строки из объекта в разделяемой памяти */
/* и копирует их на стандартный вывод                */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <assert.h>

#include "g_shm.h"

/* * * * * * * * * * * * * * * * * * * * */
/* Открытие разделяемого сегмента памяти,*/
/* чтение из сегмента и выдача строк     */
/* на стандартный вывод                  */
/* * * * * * * * * * * * * * * * * * * * */
int main (void) {
	int fd_shm; /* Дескриптор объекта */
		/* в разделяемой памяти */
	FILE *fp; /* Поток для чтения из объекта */
	char line [LINE_MAX];/* Буфер для копируемых строк */
	sigset_t smask; /* Маска ожидаемых сигналов */
	siginfo_t sinfo; /* Структура для получения */
		/* данных о сигнале */
	pid_t ppid; /* Идентификатор родительского */
		/* процесса */

	/* Откроем разделяемый сегмент памяти */
	if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 
			0777)) < 0) {
		perror ("SHM_OPEN");
		return (1);
	}

	/* Сформируем поток по файловому дескриптору объекта */
	/* в разделяемой памяти */
	assert ((fp = fdopen (fd_shm, "r")) != NULL);

	/* Отменим буферизацию ввода */
	setbuf (fp, NULL);

	/* Запомним идентификатор родительского процесса */
	ppid = getppid ();

	/* Сформируем маску ожидаемых сигналов (SIG_SHM) */
	(void) sigemptyset (&smask);
	(void) sigaddset (&smask, SIG_SHM);

	/* Подготовительная работа закончена */

	while ((fseek (fp, 0, SEEK_SET) == 0) &&
			/* Дождемся, когда из объекта можно будет читать */
		(sigwaitinfo (&smask, &sinfo) == SIG_SHM) &&
			/* И прочитаем строку, а не конец файла */
			(sinfo.si_value.sival_int == SIGVAL_LINE)) {
		(void) fgets (line, sizeof (line), fp);
		/* Сообщим родительскому процессу, */
		/* что данные из объекта извлечены */
		assert (kill (ppid, SIG_SHM) == 0);
		/* Выдадим, наконец, строку на стандартный вывод */
		assert (fputs (line, stdout) != EOF);
	}

	fclose (fp);

	return 0;
}
Листинг 4.23. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.

Объект в разделяемой памяти используется в приведенной программе как буфер на один элемент (одну строку). Чтение и запись производятся с начала объекта, для чего применяется файловое позиционирование. Разумеется, в такой ситуации порожденный процесс не может обычным образом обнаружить конец передаваемого ему файла, поэтому приходится применять дополнительные средства. В данном случае это сигналы реального времени, которые несут двойную нагрузку - обеспечивают синхронизацию доступа к объекту в разделяемой памяти и передают от родительского процесса порожденному информацию о конце файла.

К сожалению, стандарт POSIX-2001 не следует приведенным выше естественным предположениям, касающимся связи между типом дескриптора и способом доступа к объекту, так что приведенная программа, строго говоря, не соответствует стандарту. Дело в том, что файловый дескриптор, возвращаемый функцией shm_open(), является дескриптором "второго сорта" (хотя и первой свежести): результат применения к нему функций fdopen(), read(), write() и т.п. не специфицирован.

Далее мы увидим, для чего этот дескриптор можно употребить и как организовать межпроцессное взаимодействие через разделяемые сегменты памяти реального времени в полном соответствии с положениями стандарта POSIX-2001. Здесь же отметим, что в большинстве реализаций для представления разделяемых сегментов применяются файлы, отображенные в память, так что их дескрипторы вполне пригодны для выполнения файловых операций, а мобильность приведенной программы с практической точки зрения можно считать удовлетворительной.

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >
Павел Храмцов
Павел Храмцов
Россия
Денис Комаров
Денис Комаров
Россия, Москва