Ядро Linux в комментариях

Sys_msgget


Поскольку структура управления функции sys_msgget проще по сравнению с sys_msgsnd и sys_msgrcv, нет необходимости переводить весь код функции sys_msgget в отдельную вспомогательную функцию. Однако она имеет свои собственные вспомогательные функции, которые рассматриваются далее в этой главе.

Выполняется ненужная инициализация в –EPERM переменной ret, которая отслеживает требуемое возвращаемое значение функции. Переменная ret получает значение на каждом пути через функцию, поэтому присваивание в этой строке является избыточным. Однако оптимизатор транслятора gcc достаточно интеллектуален для того, чтобы устранить это ненужное присваивание, поэтому вопрос о том, действительно ли эта инициализация не нужна, остается открытым.

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

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

Это утверждение может показаться преувеличенным, но имена временных файлов создают во многом аналогичную проблему— вам просто остается надеяться, что в других приложениях не будет выбрана такая же схема именования. Однако на практике эта проблема возникает редко: тип key_t с помощью typedef просто установлен в int, поэтому существует более 4-х миллиардов возможных значений на 32-разрядном компьютере и свыше 9 квинтиллионов — на 64-разрядном компьютере. Такой огромный размер пространства ключей позволяет уменьшить вероятность случайного столкновения. К тому же, схема прав доступа способствует дополнительному уменьшению вероятности возникновения проблем, даже если возникает случайное совпадение ключей очереди сообщений или имен файлов.


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

При более внимательном изучении здесь обнаруживаются две разные проблемы. Для приложения обычно не важно, каким будет имя его временного файла, при условии, что оно не конфликтует с именем существующего файла. Но приложение, как правило, должно знать заранее, каким будет ключ его очереди сообщений, чтобы другие приложения, которые хотят послать в нее сообщения, знали, в какую очередь их посылать. Если приложение выбирает ключ очереди сообщений динамически, оно должно иметь способ сообщить другим приложениям этот выбранный ключ. (Оно может с таким же успехом передавать вместо ключа параметр msqid.) А если рассматриваемые приложения уже имеют способ посылать друг другу сообщения наподобие этого, то для чего им тогда нужны очереди сообщений?

Следовательно, эта проблема, вероятно, не стоит выеденного яйца. Приложение, которое требует получения уникального ключа для очереди общего пользования, но не накладывает слишком жестких ограничений на то, каким будет этот ключ, может получить его, просто попытавшись применить ключ 1 (помните, что 0 — это ключ IPC_PRIVATE), а затем пробуя последовательные значения ключа одно за другим, пока не добьется успеха — это потребует немного больше работы, но в действительности вряд ли понадобится.

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

Если ключ не используется, sys_msgget может создать очередь. Если бит IPC_CREAT не установлен, возвращается ошибка ENOENT; в ином случае, функция newque (строка ) создает очередь.

Ключ используется. Если в вызывающей программе установлены оба бита, IPC_CREAT и IPC_EXCL, то ей в этом случае нужно было получить ошибку и она ее получает. (Это было специально сделано в виде полного аналога битов O_CREAT и O_EXCL функции open.)



Кстати, трудно сказать, выполнялась бы проверка if быстрее, если бы она была записана либо в этой, либо в следующей эквивалентной форме:

} else if (msgflg & (IPC_CREAT | IPC_EXCL) == (IPC_CREAT | IPC_EXCL)) {

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

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

В возвращаемом значении закодированы порядковый номер и индекс msgque. Оно становится параметром msgid, который вызывающая программа передаст функциям sys_msgsnd, sys_msgrcv и sys_msgctl.

Эта схема кодирования имеет две важных особенности. Более очевидная особенность состоит в том, как обеспечивается раздельное хранение части с порядковым номером и части с индексом массива: поскольку id — это индекс массива в msgque, он может только принимать значения, достигающие (но не включающие) MSGMNI, число элементов в msgque. Поэтому после умножения порядкового номера на это значение младшие биты остаются свободными для хранения id — это своего рода система счисления по основанию MSGMNI.

Отметим также, что возвращаемое значение никогда не может быть отрицательным — это важно, поскольку эта реализация библиотеки С может предусматривать интерпретацию возвращаемого отрицательного значения как ошибки. Поскольку значение MSGMNI в настоящее время установлено равным 128, индекс массива занимает младшие 7 битов возвращаемого значения. Порядковые номера занимают 16 битов, поэтому в результате этого присваивания в 1 могут быть установлены только младшие 23 бита переменной ret, а все старшие биты равны 0. Поэтому, в частности, знаковый разряд равен 0, так что переменная ret может быть только положительной или равной 0.

Итак, вычисления выполнены и теперь возвращается значение ret.


Содержание раздела