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

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

Во многих приложениях взаимодействующим посредством очереди сообщений процессам требуется синхронизировать свое выполнение. Например, процесс-получатель, пытавшийся прочитать сообщение и обнаруживший, что очередь пуста (либо сообщение указанного типа отсутствует), должен иметь возможность подождать, пока процесс-отправитель не поместит в очередь требуемое сообщение. Аналогичным образом, процесс, желающий отправить сообщение в очередь, в которой нет достаточного для него места, может ожидать его освобождения в результате чтения сообщений другими процессами. Процесс, вызвавший подобного рода " операцию с блокировкой ", приостанавливается до тех пор, пока либо станет возможным выполнение операции, либо будет ликвидирована очередь. С другой стороны, имеются приложения, где подобные ситуации должны приводить к немедленному (и неудачному) завершению вызова функции.

Если не указано противное, функции msgsnd() и msgrcv() выполняют операции с блокировкой, например: msgsnd (msqid, msgp, size, 0); msgrcv (msqid, msgp, size, type, 0). Чтобы выполнить операцию без блокировки, необходимо установить флаг IPC_NOWAIT: msgsnd (msqid, msgp, size, IPC_NOWAIT); msgrcv (msqid, msgp, size, type, IPC_NOWAIT).

Аргумент msgp указывает на значение структурного типа, в котором представлены тип и тело сообщения (см. листинг 8.26).

struct msgbuf {
 long mtype;        /* Тип сообщения */
 char mtext [1];    /* Текст сообщения */
};
Листинг 8.26. Описание структурного типа для представления сообщений.

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

#define MAXSZTMSG 8192

struct mymsgbuf {
 long mtype;             /* Тип сообщения */
 char mtext [MAXSZTMSG]; /* Текст сообщения */
};
struct mymsgbuf msgbuf;
Листинг 8.27. Описание структуры для хранения сообщений.

В качестве аргумента msgsz обычно указывается размер текстового буфера, например: sizeof ( msgbuf.text ).

Если не указано противное, в случае, когда длина выбранного сообщения больше, чем msgsz, вызов msgrcv() завершается неудачей. Если же установить флаг MSG_NOERROR, длинное сообщение обрезается до msgsz байт. Отброшенная часть пропадает, а вызывающий процесс не получает никакого уведомления о том, что сообщение обрезано.

При успешном завершении msgsnd() возвращает 0, а msgrcv() - значение, равное числу реально полученных байт; при неудаче возвращается -1.

Процессы, обладающие достаточными правами доступа, посредством функции msgctl() могут получать информацию о состоянии очереди, изменять ряд характеристик, удалять очередь.

Управляющее действие определяется значением аргумента cmd. Допустимых значений три: IPC_STAT - получить информацию о состоянии очереди, IPC_SET - переустановить характеристики очереди, IPC_RMID - удалить очередь.

Команды IPC_STAT и IPC_SET для хранения информации об очереди используют имеющуюся в прикладной программе структуру типа msqid_ds, указатель на которую содержит аргумент buf: IPC_STAT копирует в нее ассоциированную с очередью структуру данных, а IPC_SET, наоборот, в соответствии с ней обновляет ассоциированную структуру. Команда IPC_SET позволяет переустановить значения идентификаторов владельца ( msg_perm.uid ) и владеющей группы ( msg_perm.gid ), режима доступа ( msg_perm.mode ), максимально допустимый суммарный размер сообщений в очереди ( msg_qbytes ). Увеличить значение msg_qbytes может только процесс, обладающий соответствующими привилегиями.

В листинге 8.28 приведена программа, изменяющая максимально допустимый суммарный размер сообщений в очереди. Предполагается, что очередь сообщений уже создана, а ее идентификатор известен. Читателю предлагается выполнить эту программу с разными значениями максимально допустимого суммарного размера (как меньше, так и больше текущего), действуя от имени обычного и привилегированного пользователя.

#include <stdio.h>
#include <sys/msg.h>

int main (int argc, char *argv []) {
 int msqid;
 struct msqid_ds msqid_ds;

 if (argc != 3) {
   fprintf (stderr, "Использование: %s идентификатор_очереди максимальный_размер\n", argv [0]);
   return (1);
 }

 (void) sscanf (argv [1], "%d", &msqid);
 
 /* Получим исходное значение структуры данных */
 if (msgctl (msqid, IPC_STAT, &msqid_ds) == -1) {
   perror ("IPC_STAT-1");
   return (2);
 }
 printf ("Максимальный размер очереди до изменения: %ld\n", msqid_ds.msg_qbytes);

 (void) sscanf (argv [2], "%d", (int *) &msqid_ds.msg_qbytes);

 /* Попробуем внести изменения */
 if (msgctl (msqid, IPC_SET, &msqid_ds) == -1) {
   perror ("IPC_SET");
 }

 /* Получим новое значение структуры данных */
 if (msgctl (msqid, IPC_STAT, &msqid_ds) == -1) {
   perror ("IPC_STAT-2");
   return (3);
 }
 printf ("Максимальный размер очереди после изменения: %ld\n", msqid_ds.msg_qbytes);

 return 0;
}
Листинг 8.28. Пример программы управления очередями сообщений.

Две программы, показанные в листингах 8.29 и 8.30, демонстрируют полный цикл работы с очередями сообщений - от создания до удаления. Программа из листинга 8.29 представляет собой родительский процесс, читающий строки со стандартного ввода и отправляющий их в виде сообщений процессу-потомку (листинг 8.30). Последний принимает сообщения и выдает их тела на стандартный вывод. Предполагается, что программа этого процесса находится в файле msq_child текущего каталога.

#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/msg.h>

/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через очередь сообщений                               */

#define FTOK_FILE       "/home/galat"
#define FTOK_CHAR       "G"

#define MSGQ_MODE       0644

#define MY_PROMPT       "Вводите строки\n"
#define MY_MSG          "Вы ввели: "

int main (void) {
 key_t key;
 int msqid;
 struct mymsgbuf {
   long mtype;
   char mtext [LINE_MAX];
 } line_buf, msgbuf;

 switch (fork ()) {
   case -1:
     perror ("FORK");
     return (1);
   case 0:
     /* Чтение из очереди и выдачу на стандартный вывод */
     /* реализуем в порожденном процессе.               */
     (void) execl ("./msq_child", "msq_child", FTOK_FILE, FTOK_CHAR, (char *) 0);
     perror ("EXEC");
     return (2);   /* execl() завершился неудачей */
 }

 /* Чтение со стандартного ввода и запись в очередь */
 /* возложим на родительский процесс                */

 /* Выработаем ключ для очереди сообщений */
 if ((key = ftok (FTOK_FILE, FTOK_CHAR [0])) == (key_t) (-1)) {
   perror ("FTOK");
   return (3);
 }

 /* Получим идентификатор очереди сообщений */
 if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) {
   perror ("MSGGET");
   return (4);
 }

 /* Приступим к отправке сообщений в очередь */
 msgbuf.mtype = line_buf.mtype = 1;
 strncpy (msgbuf.mtext, MY_PROMPT, sizeof (msgbuf.mtext));
 if (msgsnd (msqid, (void *) &msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) {
   perror ("MSGSND-1");
   return (5);
 }
 strncpy (msgbuf.mtext, MY_MSG, sizeof (msgbuf.mtext));

 while (fgets (line_buf.mtext, sizeof (line_buf.mtext), stdin) != NULL) {
   if (msgsnd (msqid, (void *) &msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) {
     perror ("MSGSND-2");
     break;
   }
   if (msgsnd (msqid, (void *) &line_buf, strlen (line_buf.mtext) + 1, 0) != 0) {
     perror ("MSGSND-3");
     break;
   }
 }

 /* Удалим очередь */
 if (msgctl (msqid, IPC_RMID, NULL) == -1) {
   perror ("MSGCTL-IPC_RMID");
   return (6);
 }

 return (0);
}
Листинг 8.29. Передающая часть программы работы с очередями сообщений.
#include <stdio.h>
#include <limits.h>
#include <sys/msg.h>

/* Программа получает сообщения из очереди */
/* и копирует их тела на стандартный вывод */

#define MSGQ_MODE       0644

int main (int argc, char *argv []) {
 key_t key;
 int msqid;
 struct mymsgbuf {
   long mtype;
   char mtext [LINE_MAX];
 } msgbuf;

 if (argc != 3) {
   fprintf (stderr, "Использование: %s имя_файла цепочка_символов\n", argv [0]);
   return (1);
 }

 /* Выработаем ключ для очереди сообщений */
 if ((key = ftok (argv [1], *argv [2])) == (key_t) (-1)) {
   perror ("CHILD FTOK");
   return (2);
 }

 /* Получим идентификатор очереди сообщений */
 if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) {
   perror ("CHILD MSGGET");
   return (3);
 }

 /* Цикл приема сообщений и выдачи строк */
 while (msgrcv (msqid, (void *) &msgbuf, sizeof (msgbuf.mtext), 0, 0) > 0) {
   if (fputs (msgbuf.mtext, stdout) == EOF) {
     break;
   }
 }

 return 0;
}
Листинг 8.30. Приемная часть программы работы с очередями сообщений.

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

Антон Коновалов
Антон Коновалов

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

Алексей Бутузов
Алексей Бутузов
Украина, Харьков, ХНУ им. В.Н.Каразина, 2007
Роман Ковтунович
Роман Ковтунович
Украина, Ровно, РГГУ, 2008