В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Средства межпроцессного взаимодействия
Во многих приложениях взаимодействующим посредством очереди сообщений процессам требуется синхронизировать свое выполнение. Например, процесс-получатель, пытавшийся прочитать сообщение и обнаруживший, что очередь пуста (либо сообщение указанного типа отсутствует), должен иметь возможность подождать, пока процесс-отправитель не поместит в очередь требуемое сообщение. Аналогичным образом, процесс, желающий отправить сообщение в очередь, в которой нет достаточного для него места, может ожидать его освобождения в результате чтения сообщений другими процессами. Процесс, вызвавший подобного рода " операцию с блокировкой ", приостанавливается до тех пор, пока либо станет возможным выполнение операции, либо будет ликвидирована очередь. С другой стороны, имеются приложения, где подобные ситуации должны приводить к немедленному (и неудачному) завершению вызова функции.
Если не указано противное, функции 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() в обоих процессах одинаковы), но на корректность работы программы это не влияет.