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

       

Sys_msgctl


Можно смело утверждать, что sys_msgctl — самая большая функция в реализации очереди сообщений. Это отчасти связано с тем, что она выполняет множество различных действий, аналогично ioctl, и реализует набор слабо связанных функциональных средств. (Кстати, не обвиняйте в этой неразберихе разработчиков Linux; они просто обеспечили совместимость с уродливым проектом System V.) Параметр msqid содержит имя очереди сообщений, а cmd сообщает, что должна сделать с этой очередью функция sys_msgctl. Как вскоре станет очевидно, параметр buf может потребоваться или не потребоваться, в зависимости от cmd, и его назначение меняется от случая к случаю, даже когда он используется.

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

В случаях применения IPC_INFO и MSG_INFO вызывающая программа хочет получить информацию о свойствах данной реализации очереди сообщений. Она может, например, использовать эту информацию для выбора размера сообщений — на компьютерах с большими максимальными размерами сообщений вызывающий процесс может устанавливать свой собственный предел объема информации, отправляемой в расчете на одно сообщение.

Все явные константы, которые определяют применяемые по умолчанию пределы данной реализации очереди сообщений, копируются через объект типа struct msginfo (строка ). Включается небольшая дополнительная информация, если в качестве cmd применялась MSG_INFO, а не IPC_INFO, как показано, начиная со строки , но во всем остальном эти два случая идентичны.

Обратите внимание, что буфер вызывающей программы, buf, был объявлен как указатель на объект другого типа, struct msqid_ds. Это не имеет значения. Копирование выполняется с помощью функции copy_to_user (строка ), для которой типы параметров не имеют значения, но она активизирует ошибку, получив запрос на запись в недоступную память. Если вызывающая программа предоставит указатель на достаточно большое пространство, функция sys_msgctl скопирует туда затребованные данные; за определение правильного типа (или хотя бы размера) отвечает вызывающая программа.


Если копирование было выполнено успешно, функция sys_msgctl возвращает один дополнительный фрагмент информации, max_msqid. Обратите внимание, что в этом случае был полностью проигнорирован параметр msqid. Это абсолютно оправдано, поскольку предусматривает возврат информации о данной реализации очереди сообщений в целом, а не о какой-либо очереди сообщений в частности. Однако мнения о том, нужно ли в этом случае отбрасывать отрицательное значение msqid, могут расходиться. По общему признанию, отбросив недопустимые значения msqid, даже если эти значения не будут использоваться, можно, безусловно, значительно упростить код.

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

Возвращает ошибку, если параметр msqid является недопустимым, в заданной позиции не существует очередь или вызывающая программа не имеет разрешения читать из очереди. Поэтому разрешение на чтение из очереди означает разрешение читать не только сообщения, поставленные в очередь, но и «метаданные» о самой очереди.

Кстати, отметим, что команда MSG_STAT предполагает, что msqid включает только индекс msgque и не содержит порядкового номера.

Вызывающая программа прошла все проверки. Функция sys_msgctl копирует затребованную информацию во временную переменную, а затем копирует временную переменную обратно через буфер вызывающей программы.

Возвращает «полный» идентификатор, в котором теперь закодирован порядковый номер (это выполняется в строке ).

Осталось три случая: IPC_SET, IPC_STAT и IPC_RMID. В отличие от рассмотренных ранее случаев, которые были полностью отработаны внутри этого переключателя, последние три выполняются здесь только частично. Первый из них, IPC_SET, предусматривает просто проверку того, что буфер, предоставленный пользователем, не равен NULL, a затем копирует его в переменную tbuf для последующей обработки далее в этой функции. (Отметим, что присваивание значения переменной err в строке вслед за копированием является лишним: переменной err будет снова присвоено значение в строке перед ее использованием.)





Во втором из оставшихся трех случаях, IPC_STAT, просто выполняется проверка допустимости: настоящая работа для этой команды предусмотрена далее в этой функции. Для последнего из этих трех случаев, IPC_RMID, в этом переключателе работы нет; вся его работа отложена в этой функции на дальнейшее.

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

Выполнение оставшейся части команды IPC_STAT. Если пользователь имеет разрешение читать из очереди, функция sys_msgctl копирует статистическую информацию в буфер вызывающей программы. Если вам кажется, что это в значительной степени напоминает основную часть описанного раннее случая MSG_STAT, то вы правы. Единственным различием между этими двумя случаями является то, что MSG_STAT ожидает «неполный» msqid, как было показано выше, a IPC_STAT ожидает «полный» (то есть включающий порядковый номер).

Копирует статистические данные в пространство пользователя. Эти три строки выполнялись бы чуть быстрее, если бы были перезаписаны следующим образом:

err = 0 ; if (copy_to_user(buf, &tbuf, sizeof(*buf))) err = -EFAULT;

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

if (copy_to_user(buf, &tbuf, sizeof(*buf))) { err = -EFAULT; goto out; } err = id;

Или даже могли бы работать быстрее два следующих варианта, поскольку в них не выполняется ненужное присваивание:

if (copy_to_user (buf, &tbuf, sizeof(*buf))) err = -EFAULT; else err = 0;

или

err = copy_to_user (buf, &buf, sizeof(*buf)) ? -EFAULT : 0 ;

Вопреки очевидному, проведенные автором проверки всех этих изменений показывают, что версия ядра работает быстрее. Причина этого заключается в том, как gcc вырабатывает объектный код: по-видимому, стоимость дополнительного присваивания в версии ядра не идет в сравнение со стоимостью дополнительного перехода в версии автора. (Дополнительный переход нельзя сразу обнаружить, рассматривая исходный код С: необходимо просматривать ассемблерный вывод gcc.) Напомним, что в предыдущих главах мы уже говорили о том, что переходы связаны со значительными потерями, поскольку они заставляют процессор терять преимущества некоторых внутренних алгоритмов параллельного выполнения. Разработчики процессора сделали очень многое по предотвращению влияния потерь, связанных с ветвлениями, но, очевидно, не смогли устранить все проблемы.



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

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

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

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

Вызывающей программе нужно позволить выполнить эту операцию, поэтому выбранные параметры установлены из буфера tbuf, предоставленного вызывающей программой.

Команда IPC_RMID означает удаление указанной очереди: не только сообщений в ней, но и самой очереди. Если вызывающая программа владеет очередью или имеет возможность CAP_SYS_ADMIN, очередь освобождается с помощью вызова функции freeque (строка ).

В конце концов, cmd не оказалась одной из распознанных команд, поэтому вызывающая программа получает ошибку EINVAL. В таком случае, можно было бы избежать работы, выполненной в строке . Предположим, что мы попытались бы обнаружить неправильное значение cmd при самой первой возможности, удалив случай default из переключателя и добавив следующий код в первый переключатель функции в строке :

case IPC_RMID: break; /* Еще нечего делать. */ default: err = -EINVAL; goto out; break; /* He достигнуто. */



Это привело бы к изменению поведения функции. Если бы вызывающая программа предоставила недопустимое значение и cmd, и msqid, она получила бы ошибку, отличную от той, которую получает сейчас: после этого изменения недопустимое значение cmd было бы обнаружено раньше недопустимого значения msqid. Однако документация к msgctl не обещает ни той, ни иной реакции, поэтому мы должны быть вправе внести это изменение. Результатом явилось бы незначительное ускорение случая с недопустимым значением cmd.

Однако, отметим, что это решение, к сожалению, требует введения пустого случая IPC_RMID в первом переключателе. Без этого функция неправильно отбросила бы IPC_RMID как неверное значение cmd. Эта дополнительная ветка case замедляет обработку, не намного, но замедляет, при нормальных условиях, в которых cmd имеет допустимое значение. К тому же, как вы знаете, ускорение выполнения редкого случая за счет обычного почти никогда не бывает хорошим решением. Поэтому лучше оставить все, как еcть.


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