Уильям Стивенс - UNIX: взаимодействие процессов
Значение аргумента len функции mq_receive должно быть по крайней мере не меньше максимального размера сообщения, которое может быть помещено в очередь, то есть значения поля mq_msgsize структуры mq_attr для этой очереди. Если len оказывается меньше этой величины, немедленно возвращается ошибка EMSGSIZE.
ПРИМЕЧАНИЕ
Это означает, что большинству приложений, использующих очереди сообщений Posix, придется вызывать mq_getattr после открытия очереди для определения максимального размера сообщения, а затем выделять память под один или несколько буферов чтения этого размера. Требование, чтобы буфер был больше по размеру, чем максимально возможное сообщение, позволяет функции mq_receive не возвращать уведомление о том, что размер письма превышает объем буфера. Сравните это, например, с флагом MSG_NOERROR и ошибкой E2BIG для очередей сообщений System V (раздел 6.4) и флагом MSG_TRUNC для функции recvmsg, используемой с дейтаграммами UDP (раздел 13.5 [24]).
Аргумент prio устанавливает приоритет сообщения для mq_send, его значение должно быть меньше MQ_PRIO_MAX. Если при вызове mq_receive priop является ненулевым указателем, в нем сохраняется приоритет возвращаемого сообщения. Если приложению не требуется использование различных приоритетов сообщений, можно указывать его равным нулю для mq_send и передавать mq_receive нулевой указатель в качестве последнего аргумента.
ПРИМЕЧАНИЕ
Разрешена передача сообщений нулевой длины. Это тот случай, когда важно не то, о чем говорится в стандарте (Posix.1), а то, о чем в нем не говорится: нигде не запрещена передача сообщений нулевой длины. Функция mq_receive возвращает количество байтов в сообщении (в случае успешного завершения работы) или –1 в случае возникновения ошибок, так что 0 обозначает сообщение нулевой длины.
Очередям сообщений Posix и System V не хватает полезной функции: получатель не может определить отправителя сообщения. Эта информация могла бы пригодиться многим приложениям. К сожалению, большинство механизмов передачи сообщений IPC не позволяют определить отправителя сообщений. В разделе 15.5 мы расскажем, как эта возможность обеспечивается для дверей. В разделе 14.8 [24] описано, как эта возможность обеспечивается в BSD/OS для доменных сокетов Unix. В разделе 15.3.1 [21] описано, как SVR4 передает информацию об отправителе по каналу при передаче по нему дескриптора. В настоящее время методы BSD/OS широко используются, и хотя реализация SVR4 является частью стандарта Unix 98, она требует передачи дескриптора по каналу, что обычно является более дорогостоящей операцией, чем просто передача данных. Мы не можем предоставить отправителю возможность передать информацию о себе (например, эффективный идентификатор пользователя) в самом сообщении, поскольку мы не можем быть уверены, что эта информация окажется истинной. Хотя разрешения доступа к очереди сообщений определяют, имеет ли право отправитель помещать в нее сообщения, это все равно не дает однозначности. Существует возможность создавать одну очередь для каждого отправителя (о которой рассказывается в связи с очередями System V в разделе 6.8), но это плохо подходит для больших приложений. Наконец, если функции для работы с очередями сообщений реализованы как пользовательские функции (как мы показываем в разделе 5.8), а не как часть ядра, мы не можем доверять никакой информации об отправителе, передаваемой с сообщением, так как ее легко подделать.
Пример: программа mqsend
В листинге 5.5 приведен текст программы, помещающей сообщение в очередь.
Листинг 5.5. Программа mqsend//pxmsg/mqsend.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 mqd_t mqd;
6 void *ptr;
7 size_t len;
8 uint_t prio;
9 if (argc != 4)
10 err_quit("usage: mqsend <name> <#bytes> <priority>");
11 len = atoi(argv[2]);
12 prio = atoi(argv[3]);
13 mqd = Mq_open(argv[1], O_WRONLY);
14 ptr = Calloc(len, sizeof (char));
15 Mq_send(mqd, ptr, len, prio);
16 exit(0);
17 }
И размер сообщения, и его приоритет являются обязательными аргументами командной строки. Буфер под сообщение выделяется функцией callос, которая инициализирует его нулем.
Пример: программа mqreceive
Программа в листинге 5.6 считывает сообщение из очереди.
Листинг 5.6. Программа mqreceive//pxmsg/mqreceive.с
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int с flags;
6 mqd_t mqd;
7 ssize_t n;
8 uint_t prio;
9 void *buff;
10 struct mq_attr attr;
11 flags = O_RDONLY;
12 while ((c = Getopt(argc, argv, "n")) != –1) {
13 switch (c) {
14 case 'n':
15 flags |= O_NONBLOCK;
16 break;
17 }
18 }
19 if (optind != argc – 1)
20 err_quit("usage: mqreceive [ –n ] <name>");
21 mqd = Mq_open(argv[optind], flags);
22 Mq_getattr(mqd, &attr);
23 buff = Malloc(attr.mqjnsgsize);
24 n = Mq_receive(raqd, buff, attr.mq_msgsize, &prio);
25 printf("read %ld bytes, priority = %un", (long) n, prio);
26 exit(0);
27 }
Параметр -n запрещает блокировку14-17 Параметр командной строки –n отключает блокировку. При этом программа возвращает сообщение об ошибке, если в очереди нет сообщений.
Открытие очереди и получение атрибутов21-25 Мы открываем очередь и получаем ее атрибуты, вызвав mq_getattr. Нам обязательно нужно определить максимальный размер сообщения, потому что мы должны выделить буфер подходящего размера, чтобы вызвать mq_receive. Программа выводит размер считываемого сообщения и его приоритет.
ПРИМЕЧАНИЕ
Поскольку n имеет тип size_t и мы не знаем, int это или long, мы преобразуем эту величину к типу long и используем строку формата %ld. В 64-разрядной реализации int будет 32-разрядным целым, a long и size_t будут 64-разрядными целыми.
Воспользуемся обеими программами, чтобы проиллюстрировать использование поля приоритета.
solaris % mqcreate /test1
solaris % mqgetattr /test1 создаем очередь и смотрим на ее атрибуты
max #msgs = 128, max #bytes/msg = 1024, #currently on queue = 0
solaris % mqsend /test1 100 99999 отправка с некорректным значением приоритета
mq_send error: Invalid argument
solaris % mqsend /test1 100 6 100 байт, приоритет 6
solaris % mqsend /test1 50 18 50 байт, приоритет 18
solaris % mqsend /test1 33 18 33 байт, приоритет 18
solaris % mqreceive /test1
read 50 bytes, priority = 18 возвращается старейшее сообщение с
solaris % mqreceive /test1 наивысшим приоритетом
read 33 bytes, priority = 18
Solaris % mqreceive /test1
read 100 bytes, priority = 6
Solaris % mqreceive –n /test1 отключаем блокировку и убеждаемся, что очередь пуста
mq_receive error: Resource temporarily unavailable
Мы видим, что mq_receive действительно возвращает старейшее сообщение с наивысшим приоритетом.
5.5. Ограничения очередей сообщений
Мы уже сталкивались с двумя ограничениями, устанавливаемыми для любой очереди в момент ее создания:
■ mq_maxmsg — максимальное количество сообщений в очереди;
■ mq_msgsize — максимальный размер сообщения.
Не существует каких-либо ограничений на эти значения, хотя в рассматриваемых реализациях необходимо наличие в файловой системе места для файла требуемого размера. Кроме того, ограничения на эти величины могут накладываться реализацией виртуальной памяти (см. упражнение 5.5).
Другие два ограничения определяются реализацией:
■ MQ_OPEN_MAX — максимальное количество очередей сообщений, которые могут быть одновременно открыты каким-либо процессом (Posix требует, чтобы эта величина была не меньше 8);
■ MQ_PRIO_MAX — максимальное значение приоритета плюс один (Posix требует, чтобы эта величина была не меньше 32).
Эти две константы часто определяются в заголовочном файле <unistd.h> и могут быть получены во время выполнения программы вызовом функции sysconf, как мы покажем далее.
Пример: программа mqsysconf
Программа в листинге 5.7 вызывает функцию sysconf и выводит два ограничения на очереди сообщений, определяемые реализацией.
Листинг 5.7. Получение ограничений очередей с помощью sysconf//pxmsg/mqsysconf.с
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 printf("MQ_OPEN_MAX = %ld, MQ_PRIO_MAX = %ldn",