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

       

Sys_semop


Функция sys_semop реализует системный вызов semop. Функция sys_semop не имеет прямого эквивалента в программной реализации очереди сообщений, и является аналогом функции sys_msgsnd, sys_msgrcv, или их обеих, в зависимости от того, под каким углом она рассматривается. Как бы то ни было, ее назначение состоит в выполнении одной или нескольких операций над одним или несколькими семафорами. Она пытается выполнять все операции атомарно (то есть без прерывания). Если она не может выполнить их все, она не будет выполнять ни одной из них.

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

Проверка допустимости параметров. Отметим, в частности, что значение nsops ограничено SEMOPM, максимальным числом операций над семафорами, которые можно попытаться выполнить за один раз. Это значение установлено равным 32 директивой #define в строке .

Копирует описание затребованных операций из пространства пользователя во временный буфер, sops.

Проверка наличия входа в указанной позиции массива. Можно видеть, что эквивалентом массива msgque в программной реализации очереди сообщений является semary (строка ). Отметим также, что индекс массива и порядковый номер упакованы в параметре semid таким же способом, который применялся в программной реализации очереди сообщений. Здесь, безусловно, применяется немного другая константа, SEMMNI, значение которой установлено равным 128 (и случайно совпадает со значением MSGMNI) директивой #define в строке .

Начинается цикл по всем указанным операциям. В нем вначале выполняется проверка того, не находится ли номер семафора, заданный в этой операции, вне диапазона, и если это так, он отбрасывается. Любопытно, что при неудачном завершении здесь возвращается ошибка EFBIG (которая означает, что «файл слишком велик»), а не ошибка EINVAL («недопустимый параметр»). Однако это соответствует документации.


Подсчет числа операций, для которых установлен флажок SEM_UNDO. Однако переменная undos просто применяется в качестве флажка: важно только знать, отличается ли она от 0, поэтому присвоение ей 1 (или любого другого ненулевого значения), если выполнено это условие, будет иметь одинаковый эффект. Тем не менее, версия ядра немного быстрее. А поскольку внешний цикл повторяется не более SEMOPM раз, значения undos не могут наращиваться так много раз, чтобы эта целочисленная переменная переполнилась и снова установилась в0.

Следующие несколько проверок обновляют два локальных флажка: decrease и alter. Они отслеживают, соответственно, будет ли какая-либо операция в наборе уменьшать значение семафора и будет ли какая-либо операция изменять значение семафора. Флажок alter вычисляется не полностью до выхода из цикла в строке , поскольку внутри цикла он только следит за тем, увеличивает ли какая-либо операция значение семафора; затем это значение объединяется с информацией в флажке decrease для получения сведений о том, будут ли происходить какие-либо изменения.

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

Проверка того, что процесс имеет разрешение на выполнение указанных операций с семафорами. Если переменная alter имеет истинное значение, то процесс изменяет семафоры и поэтому должен иметь разрешение на запись; в ином случае, он только ожидает, пока значения одного или нескольких семафоров не установятся в 0, поэтому он просто нуждается в разрешении на чтение.



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

Процесс еще не имеет набора операций отмены для данного набора семафоров, поэтому распределяется новый набор. В соответствии с принятой практикой, которую мы уже рассматривали на примере программной реализации очереди сообщений, пространство для корректировок отмены (массив semadj) распределяется непосредственно за самим объектом struct sem_undo в составе того же распределения памяти. После этого происходит заполнение объекта struct sem_undo.

В рассматриваемом наборе операций нет операций отмены, поэтому переменная un просто устанавливается в NULL.

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

Функция try_atomic_semop возвращает 0 в случае успеха и отрицательное значение — при возникновении ошибки. В любом из этих случаев управление переходит вперед на строку .

В ином случае, функция try_atomic_semop возвращает положительное значение. Оно указывает, что операции нельзя было выполнить прямо сейчас, но процесс собирается ждать, а затем предпринять еще оцну попытку. Для начала заполняется локальный объект struct sem_queue.

Узлы, представляющие операции, которые изменят значения семафоров, размещены в конце очереди; все узлы, представляющие операции, которые ожидают достижения нуля значениями семафоров, размещаются в начале. Почему так происходит, мы покажем при описании функции update_queue (строка ) далее в этой главе.



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

Начало цикла, который повторно пытается выполнить эти операции, и прекращает свою работу, только если были успешно выполнены все затребованные операции или если произошла ошибка.

Переходит в состояние ожидания до тех пор, пока не возникнет прерывание от сигнала или пока появится какая-то причина для выполнения повторной попытки.

Если процесс был активизирован функцией update_queue, поскольку у него теперь есть шанс добиться успеха, он повторяет операции.

Удаляет этот процесс из очереди процессов, ждущих возможности изменить набор семафоров.

Если этот набор операций изменил очередь, он мог создать условия, появления которых ждут какие-то другие процессы. Функция sys_semop вызывает update_queue, чтобы она нашла и активизировала такие процессы.


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