Сериализация
Ожидание (захват) диспетчерских объектов
Как уже было сказано, каждый диспетчерский объект всегда находится в одном из двух состояний – сигнальном (свободном) или несигнальном (занятым). Термины "свободный" и "занятый" довольно вольные, поэтому лучше использовать термины сигнальный и несигнальный. Для ожидания момента перехода объекта из несигнального в сигнальное состояние служат специальные функции ожидания: KeWaitFor SingleObject() и KeWaitForMultipleObjects(). Важной особенностью этих функций служит то, что в качестве одного из их параметров указывается интервал времени, в течение которого необходимо ждать.
Если указан нулевой интервал времени (но не NULL!!!), вызов функции ожидания не блокирует поток. В этом случае она работает как функция проверки состояния диспетчерского объекта, и может быть вызвана на уровне IRQL меньшем или равном DISPATCH_LEVEL.
Если интервал времени не указан (NULL в качестве параметра), или указан ненулевой интервал времени, функции ожидания можно вызывать на уровне IRQL строго меньшем DISPATCH_LEVEL. В противном случае будет сделана попытка блокирования потока, но, как мы говорили раньше, механизм диспетчеризации на уровнях IRQL меньших или равных DISPATCH_LEVEL не работает. Переключение контекста потока не сможет произойти, и функция ожидания завершит работу, как будто ожидаемый диспетчерский объект находится в сигнальном состоянии.
Отличие функции KeWaitForMultipleObjects() от KeWaitForSingleObject() в том, что она может ожидать перехода в сигнальное состояние сразу всех указанных в ней диспетчерских объектов, либо любого одного из них.
Мьютексы ядра
Слово Мьютекс (mutex = Mutually Exclusive) означает взаимоисключение, то есть мьютекс обеспечивает нескольким потокам взаимоисключающий доступ к совместно используемому ресурсу.
Вначале отметим, что кроме мьютексов ядра, есть еще быстрые мьютексы, являющиеся объектами исполнительной системы и не являющиеся диспетчерскими объектами. Мьютексы ядра обычно называют просто мьютексами.
Мьютексы ядра – это диспетчерские объекты, эквиваленты спин-блокировок. Двумя важными отличиями мьютексов от спин-блокировок являются:
- Захват мьютекса является уникальным в рамках конкретного контекста потока.
Поток, в контексте которого произошел захват мьютекса, является его владельцем, и может впоследствии рекурсивно захватывать его. Драйвер, захвативший мьютекс в конкретном контексте потока, обязан освободить его в том же контексте потока, нарушение этого правила приведет к появлению "синего экрана". - Для мьютексов предусмотрен механизм исключения взаимоблокировок.
Он заключается в том, что при инициализации мьютекса функцией KelnitializeMutex() указывается уровень (level) мьютекса. Если потоку требуется захватить несколько мьютексов одновременно, он должен делать это в порядке возрастания значения level.
Функции работы с мьютексами ядра:
- VOID KeInitializeMutex (IN PKMUTEX Mutex, IN ULONG Level);
Эта функция инициализирует мьютекс. Память под мьютекс уже должна быть выделена. После инициализации мьютекс находится в сигнальном состоянии. - LONG KeReleaseMutex (IN PKMUTEX Mutex, IN BOOLEAN Wait);
Эта функция освобождает мьютекс, с указанием того, последует ли сразу после этого вызов функции ожидания мьютекса. Если параметр Wait равен TRUE, сразу за вызовом KeReleaseMutex() должен следовать вызов одной из функций ожидания KeWaitXxx(). В этом случае гарантируется, что пара функций – освобождение мьютекса и ожидание – будет выполнена как одна операция, без возможного в противном случае переключения контекста потока. Возвращаемым значением будет 0, если мьютекс был освобожден, то есть переведен из несигнального состояния в сигнальное. В противном случае возвращается ненулевое значение. - 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, семафор переходит в несигнальное состояние (свободных ресурсов нет). Использование семафора не зависит от контекста потока или процесса в том смысле, что занять ресурс семафора может один поток, а освободить его – другой, но драйвер не должен использовать семафоры в случайном контексте потока, так как в этом случае будет заблокирован случайный поток, не имеющий к драйверу никакого отношения. Семафоры следует использовать в ситуациях, когда драйвер создал собственные системные потоки.