11.2.1 Сообщения

11.2.1 Сообщения

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

Синтаксис вызова системной функции msgget:

msgqid = msgget(key, flag);

где msgqid — возвращаемый функцией дескриптор, а key и flag имеют ту же семантику, что и в системной функции типа "get". Ядро хранит сообщения в связном списке (очереди), определяемом значением дескриптора, и использует значение msgqid в качестве указателя на массив заголовков очередей. Кроме вышеуказанных полей, описывающих общие для всего механизма права доступа, заголовок очереди содержит следующие поля:

• Указатели на первое и последнее сообщение в списке;

• Количество сообщений и общий объем информации в списке в байтах;

• Максимальная емкость списка в байтах;

• Идентификаторы процессов, пославших и принявших сообщения последними;

• Поля, указывающие время последнего выполнения функций msgsnd, msgrcv и msgctl.

Когда пользователь вызывает функцию msgget для того, чтобы создать новый дескриптор, ядро просматривает массив очередей сообщений в поисках существующей очереди с указанным идентификатором. Если такой очереди нет, ядро выделяет новую очередь, инициализирует ее и возвращает идентификатор пользователю. В противном случае ядро проверяет наличие необходимых прав доступа и завершает выполнение функции.

Для посылки сообщения процесс использует системную функцию msgsnd:

msgsnd(msgqid, msg, count, flag);

где msgqid — дескриптор очереди сообщений, обычно возвращаемый функцией msgget, msg — указатель на структуру, состоящую из типа в виде назначаемого пользователем целого числа и массива символов, count — размер информационного массива, flag — действие, предпринимаемое ядром в случае переполнения внутреннего буферного пространства.

алгоритм msgsnd /* послать сообщение */

входная информация:

 (1) дескриптор очереди сообщений

 (2) адрес структуры сообщения

 (3) размер сообщения

 (4) флаги

выходная информация: количество посланных байт

{

 проверить правильность указания дескриптора и наличие соответствующих прав доступа;

 do while (для хранения сообщения не будет выделено место) {

  if (флаги не разрешают ждать) return;

  sleep (до тех пор, пока место не освободится);

 }

 получить заголовок сообщения;

 считать текст сообщения из пространства задачи в пространство ядра;

 настроить структуры данных: выстроить очередь заголовков сообщений, установить в заголовке указатель на текст сообщения, заполнить поля, содержащие счетчики, время последнего выполнения операций и идентификатор процесса;

 вывести из состояния приостанова все процессы, ожидающие разрешения считать сообщение из очереди;

}

Рисунок 11.4. Алгоритм посылки сообщения

Ядро проверяет (Рисунок 11.4), имеется ли у посылающего сообщение процесса разрешения на запись по указанному дескриптору, не выходит ли размер сообщения за установленную системой границу, не содержится ли в очереди слишком большой объем информации, а также является ли тип сообщения положительным целым числом. Если все условия соблюдены, ядро выделяет сообщению место, используя карту сообщений (см. раздел 9.1), и копирует в это место данные из пространства пользователя. К сообщению присоединяется заголовок, после чего оно помещается в конец связного списка заголовков сообщений. В заголовке сообщения записывается тип и размер сообщения, устанавливается указатель на текст сообщения и производится корректировка содержимого различных полей заголовка очереди, содержащих статистическую информацию (количество сообщений в очереди и их суммарный объем в байтах, время последнего выполнения операций и идентификатор процесса, пославшего сообщение). Затем ядро выводит из состояния приостанова все процессы, ожидающие пополнения очереди сообщений. Если размер очереди в байтах превышает границу допустимости, процесс приостанавливается до тех пор, пока другие сообщения не уйдут из очереди. Однако, если процессу было дано указание не ждать (флаг IPC_NOWAIT), он немедленно возвращает управление с уведомлением об ошибке. На Рисунке 11.5 показана очередь сообщений, состоящая из заголовков сообщений, организованных в связные списки, с указателями на область текста.

Рисунок 11.5. Структуры данных, используемые в организации сообщений

Рассмотрим программу, представленную на Рисунке 11.6. Процесс вызывает функцию msgget для того, чтобы получить дескриптор для записи с идентификатором MSGKEY. Длина сообщения принимается равной 256 байт, хотя используется только первое поле целого типа, в область текста сообщения копируется идентификатор процесса, типу сообщения присваивается значение 1, после чего вызывается функция msgsnd для посылки сообщения. Мы вернемся к этому примеру позже.

Процесс получает сообщения, вызывая функцию msgrcv по следующему формату:

count = msgrcv(id, msg, maxcount, type, flag);

где id — дескриптор сообщения, msg — адрес пользовательской структуры, которая будет содержать полученное сообщение, maxcount — размер структуры msg, type — тип считываемого сообщения, flag — действие, предпринимаемое ядром в том случае, если в очереди сообщений нет. В переменной count пользователю возвращается число прочитанных байт сообщения.

Ядро проверяет (Рисунок 11.7), имеет ли пользователь необходимые права доступа к очереди сообщений. Если тип считываемого сообщения имеет нулевое значение, ядро ищет первое по счету сообщение в связном списке. Если его размер меньше или равен размеру, указанному пользователем, ядро копирует текст сообщения в пользовательскую структуру и соответствующим образом настраивает свои внутренние структуры: уменьшает счетчик сообщений в очереди и суммарный объем информации в байтах, запоминает время получения сообщения и идентификатор процесса-получателя, перестраивает связный список и освобождает место в системном пространстве, где хранился текст сообщения. Если какие-либо процессы, ожидавшие получения сообщения, находились в состоянии приостанова из-за отсутствия свободного места в списке, ядро выводит их из этого состояния. Если размер сообщения превышает значение maxcount, указанное пользователем, ядро посылает системной функции уведомление об ошибке и оставляет сообщение в очереди. Если, тем не менее, процесс игнорирует ограничения на размер (в поле flag установлен бит MSG_NOERROR), ядро обрезает сообщение, возвращает запрошенное количество байт и удаляет сообщение из списка целиком.

#include ‹sys/types.h›

#include ‹sys/ipc.h›

#include ‹sys/msg.h›

#define MSGKEY 75

struct msgform {

 long mtype;

 char mtext[256];

};

main() {

 struct msgform msg;

 int msgid, pid, *pint;

 msgid = msgget(MSGKEY, 0777);

 pid = getpid();

 pint = (int *) msg.mtext;

 *pint = pid; /* копирование идентификатора процесса в область текста сообщения */

 msg.mtype = 1;

 msgsnd(msgid, &msg, sizeof(int), 0);

 msgrcv(msgid, &msg, 256, pid, 0);

 /* идентификатор процесса используется в качестве типа сообщения */

 printf("клиент: получил от процесса с pid %d ", *pint);

}

Рисунок 11.6. Пользовательский процесс

алгоритм msgrcv /* получение сообщения */

входная информация:

 (1) дескриптор сообщения

 (2) адрес массива, в который заносится сообщение

 (3) размер массива

 (4) тип сообщения в запросе

 (5) флаги

выходная информация: количество байт в полученном сообщении

{

 проверить права доступа;

loop:

 проверить правильность дескриптора сообщения;

 /* найти сообщение, нужное пользователю */

 if (тип сообщения в запросе == 0)

  рассмотреть первое сообщение в очереди;

 else

if (тип сообщения в запросе › 0)

    рассмотреть первое сообщение в очереди, имеющее данный тип;

  else /* тип сообщения в запросе ‹ 0 */

   рассмотреть первое из сообщений в очереди с наименьшим значением типа при условии, что его тип не превышает абсолютное значение типа, указанного в запросе;

 if (сообщение найдено) {

  переустановить размер сообщения или вернуть ошибку, если размер, указанный пользователем слишком мал; скопировать тип сообщения и его текст из пространства ядра в пространство задачи;

  разорвать связь сообщения с очередью;

  return;

 }

 /* сообщений нет */

 if (флаги не разрешают приостанавливать работу)

  return ошибку;

 sleep (пока сообщение не появится в очереди);

 перейти на loop;

}

Рисунок 11.7. Алгоритм получения сообщения

Процесс может получать сообщения определенного типа, если присвоит параметру type соответствующее значение. Если это положительное целое число, функция возвращает первое значение данного типа, если отрицательное, ядро определяет минимальное значение типа сообщений в очереди, и если оно не превышает абсолютное значение параметра type, возвращает процессу первое сообщение этого типа. Например, если очередь состоит из трех сообщений, имеющих тип 3, 1 и 2, соответственно, а пользователь запрашивает сообщение с типом -2, ядро возвращает ему сообщение типа 1. Во всех случаях, если условиям запроса не удовлетворяет ни одно из сообщений в очереди, ядро переводит процесс в состояние приостанова, разумеется если только в параметре flag не установлен бит IPC_NOWAIT (иначе процесс немедленно выходит из функции).

Рассмотрим программы, представленные на Рисунках 11.6 и 11.8. Программа на Рисунке 11.8 осуществляет общее обслуживание запросов пользовательских процессов (клиентов). Запросы, например, могут касаться информации, хранящейся в базе данных; обслуживающий процесс (сервер) выступает необходимым посредником при обращении к базе данных, такой порядок облегчает поддержание целостности данных и организацию их защиты от несанкционированного доступа. Обслуживающий процесс создает сообщение путем установки флага IPC _CREAT при выполнении функции msgget и получает все сообщения типа 1 — запросы от процессов-клиентов. Он читает текст сообщения, находит идентификатор процесса-клиента и приравнивает возвращаемое значение типа сообщения значению этого идентификатора. В данном примере обслуживающий процесс возвращает в тексте сообщения процессу-клиенту его идентификатор, и клиент получает сообщения с типом, равным идентификатору клиента. Таким образом, обслуживающий процесс получает сообщения только от клиентов, а клиент — только от обслуживающего процесса. Работа процессов реализуется в виде многоканального взаимодействия, строящегося на основе одной очереди сообщений.

#include ‹sys/types.h›

#include ‹sys/ipc.h›

#include ‹sys/msg.h›

#define MSGKEY 75

struct msgform {

 long mtype;

 char mtext[256];

} msg;

int msgid;

main() {

 int i, pid, *pint;

 extern cleanup();

 for (i = 0; i ‹ 20; i++) signal(i, cleanup);

 msgid = msgget(MSGKEY, 0777, IPC_CREAT);

 for (;;) {

  msgrcv(msgid, &msg, 256, 1, 0);

  pint = (int *) msg.mtext;

  pid = *pint;

  printf("сервер: получил от процесса с pid %d ", pid);

  msg.mtype = pid;

  *pint = getpid();

  msgsnd(msgid, &msg, sizeof(int), 0);

 }

}

cleanup() {

 msgctl(msgid, IPC_RMID, 0);

 exit();

}

Рисунок 11.8. Обслуживающий процесс (сервер)

Сообщения имеют форму "тип — текст", где текст представляет собой поток байтов. Указание типа дает процессам возможность выбирать сообщения только определенного рода, что в файловой системе не так легко сделать. Таким образом, процессы могут выбирать из очереди сообщения определенного типа в порядке их поступления, причем эта очередность гарантируется ядром. Несмотря на то, что обмен сообщениями может быть реализован на пользовательском уровне средствами файловой системы, представленный вашему вниманию механизм обеспечивает более эффективную организацию передачи данных между процессами.

С помощью системной функции msgctl процесс может запросить информацию о статусе дескриптора сообщения, установить этот статус или удалить дескриптор сообщения из системы. Синтаксис вызова функции:

msgctl(id, cmd, mstatbuf)

где id — дескриптор сообщения, cmd — тип команды, mstatbuf — адрес пользовательской структуры, в которой будут храниться управляющие параметры или результаты обработки запроса. Более подробно об аргументах функции пойдет речь в Приложении.

Вернемся к примеру, представленному на Рисунке 11.8. Обслуживающий процесс принимает сигналы и с помощью функции cleanup удаляет очередь сообщений из системы. Если же им не было поймано ни одного сигнала или был получен сигнал SIGKILL, очередь сообщений остается в системе, даже если на нее не ссылается ни один из процессов. Дальнейшие попытки исключительно создания новой очереди сообщений с данным ключом (идентификатором) не будут иметь успех до тех пор, пока старая очередь не будет удалена из системы.