Иллюстрированный самоучитель по программированию систем защиты

Сериализация

Ожидание (захват) диспетчерских объектов

Как уже было сказано, каждый диспетчерский объект всегда находится в одном из двух состояний – сигнальном (свободном) или несигнальном (занятым). Термины "свободный" и "занятый" довольно вольные, поэтому лучше использовать термины сигнальный и несигнальный. Для ожидания момента перехода объекта из несигнального в сигнальное состояние служат специальные функции ожидания: KeWaitFor SingleObject() и KeWaitForMultipleObjects(). Важной особенностью этих функций служит то, что в качестве одного из их параметров указывается интервал времени, в течение которого необходимо ждать.

Если указан нулевой интервал времени (но не NULL!!!), вызов функции ожидания не блокирует поток. В этом случае она работает как функция проверки состояния диспетчерского объекта, и может быть вызвана на уровне IRQL меньшем или равном DISPATCH_LEVEL.

Если интервал времени не указан (NULL в качестве параметра), или указан ненулевой интервал времени, функции ожидания можно вызывать на уровне IRQL строго меньшем DISPATCH_LEVEL. В противном случае будет сделана попытка блокирования потока, но, как мы говорили раньше, механизм диспетчеризации на уровнях IRQL меньших или равных DISPATCH_LEVEL не работает. Переключение контекста потока не сможет произойти, и функция ожидания завершит работу, как будто ожидаемый диспетчерский объект находится в сигнальном состоянии.

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

Мьютексы ядра

Слово Мьютекс (mutex = Mutually Exclusive) означает взаимоисключение, то есть мьютекс обеспечивает нескольким потокам взаимоисключающий доступ к совместно используемому ресурсу.

Вначале отметим, что кроме мьютексов ядра, есть еще быстрые мьютексы, являющиеся объектами исполнительной системы и не являющиеся диспетчерскими объектами. Мьютексы ядра обычно называют просто мьютексами.

Мьютексы ядра – это диспетчерские объекты, эквиваленты спин-блокировок. Двумя важными отличиями мьютексов от спин-блокировок являются:

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

Функции работы с мьютексами ядра:

  1. VOID KeInitializeMutex (IN PKMUTEX Mutex, IN ULONG Level);
    Эта функция инициализирует мьютекс. Память под мьютекс уже должна быть выделена. После инициализации мьютекс находится в сигнальном состоянии.
  2. LONG KeReleaseMutex (IN PKMUTEX Mutex, IN BOOLEAN Wait);
    Эта функция освобождает мьютекс, с указанием того, последует ли сразу после этого вызов функции ожидания мьютекса. Если параметр Wait равен TRUE, сразу за вызовом KeReleaseMutex() должен следовать вызов одной из функций ожидания KeWaitXxx(). В этом случае гарантируется, что пара функций – освобождение мьютекса и ожидание – будет выполнена как одна операция, без возможного в противном случае переключения контекста потока. Возвращаемым значением будет 0, если мьютекс был освобожден, то есть переведен из несигнального состояния в сигнальное. В противном случае возвращается ненулевое значение.
  3. LONG KeReadStateMutex(IN PKMUTEX Mutex);
    Эта функция возвращает состояние мьютекса – сигнальное или несигнальное.

Семафоры

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

Семафор инициализируется с помощью функции KeInitializeSemaphore():

VOID KelnitializeSemaphore(
IN PKSEMAPHORE Semaphore,
IN LONG Count,
IN LONG Limit);

Где:

  • Count – начальное значение, присвоенное семафору, определяющее число свободных в данный момент ресурсов. Если Count=0, семафор находится в несигнальном состоянии (свободных ресурсов нет), если >0 – в сигнальном;
  • Limit – максимальное значение, которое может достигать Count (максимальное число свободных ресурсов).

Функция KeReleaseSemaphore() увеличивает счетчик семафора Count на указанное в параметре функции значение, то есть освобождает указанное число ресурсов. Если при этом значение Count превышает значение Limit, значение Count не изменяется и генерируется исключение STATUS_SEMAPHORE_COUNT_EXCEEDED.

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

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.