Программирование драйверов Windows

       

Объекты события


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

Если присмотреться к определению объекта события в заголовочных файлах DDK, то становится очевидным, что объект события состоит только лишь из структуры DISPATCHER_HEADER, в то время как другие синхронизационные примитивы &#8212 таймеры, семафоры и мьютексы &#8212 содержат, помимо DISPATCHER_HEADER, некоторые дополнительные поля. То есть, события &#8212 самые простые из них.

Объекты события делятся на две категории: объекты для уведомления (Notification Events) и объекты для синхронизации (Synchronization Events). Тип выбирается в момент инициализации объекта. Эти два типа объекта событий проявляют различие в своем поведении в момент, когда объект переводится в сигнальное состояние.

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

Поведение синхронизационных (Synchronization) объектов события несколько отличается. Когда синхронизационный объект события переходит в сигнальное состояние, то он остается в этом состояние лишь столько времени, сколько это необходимо для выполнения одного вызова KeWaitForXxx. Затем объект переводит себя в несигнальное состояние автоматически. Другими словами, ворота остаются открытыми только до тех пор, пока кто-то первый не прошел через них, после чего они закрываются автоматически. Этот тип эквивалентен событиям с авто-сбросом (auto-reset) в пользовательском режиме.


Таблица 10.26. Функции для работы с объектами событий



Что необходимо сделать... Какой вызов нужно использовать...
Создать событие KeInitializeEvent
Создать именованное событие IoCreateSynchronizationEvent

IoCreateNotificationEvent
Изменить состояние события KeSetEvent

KeClearEvent

KeResetEvent
Запросить состояние KeReadStateEvent
Для использования объекта события сначала необходимо получить память для его хранения размером sizeof(KEVENT), и только после этого можно выполнять вызовы функций, перечисленные выше. Рассмотрим некоторые из них.

Несигнальное состояние объекта событий можно установить при помощи вызовов KeResetEvent и KeClearEvent. Разница между ними заключается в том, что функция KeResetEvent еще и возвращает состояние объекта, в котором тот пребывал перед данным вызовом. Функция KeClearEvent

работает несколько быстрее, поэтому в тех случаях, когда нет необходимости знать предыдущее состояние объекта, следует использовать этот вызов.

Таблица 10.27. Прототип вызова KeInitializeEvent

VOID KeInitializeEvent IRQL == PASSIVE_LEVEL
Параметры Инициализация объекта события и установка его начального состояния
IN PKEVENT pEvent Указатель на область памяти для объекта события
IN EVENT_TYPE Type Одно из двух значений

NotificationEvent

SynchronizationEvent
IN BOOLEAN bInitalState Начальное состояние объекта

TRUE &#8212 сигнальное состояние

FALSE &#8212 несигнальное состояние
Возвращаемое значение void
Таблица 10.28. Прототип вызовов KeClearEvent

u KeResetEvent

VOID KeClearEvent

LONG KeResetEvent
IRQL &#60= DISPATCH_LEVEL
Параметры Установка объекта события в несигнальное состояния
IN PKEVENT pEvent Указатель на инициализированный объект события
Возвращаемое значение KeResetEvent возвращает предыдущее состояние объекта события
Таблица 10.29. Прототип вызова KeSetEvent

LONG KeSetEvent IRQL &#60= DISPATCH_LEVEL
Параметры Переводит объект события в сигнальное состояние
IN PKEVENT pEvent Указатель на инициализированный объект события
IN KPRIORITY Increment Обычно используется значение IO_NO_INCREMENT
IN BOOLEAN bWait Обычно используется значение FALSE
Возвращаемое значение Возвращает ненулевое значение, если предыдущее состояние объекта события было сигнальным
<


В качестве примера, когда применение объекта события будет весьма кстати, можно привести следующую ситуацию. Предположим, драйвер имеет специально созданный программный поток, который выполняет некоторую работу по получению сигнала прерывания. Завершив ее, поток замирает до прихода следующего прерывания. Реализовать эту схему легко, если рассматриваемый программный поток будет ожидать наступления сигнального состояния объекта события, а переводить его в такое состояние будет драйверная процедура DpcForIsr.

В основе объектов события пользовательского режима лежат объекты события режима ядра &#8212 именно те, которые обсуждались выше. Основное различие заключается в том, что типовой доступ в пользовательском режиме &#8212 по дескриптору, а в режиме ядра &#8212 по указателю. Иными словами, один и тот же объект события при некоторой сноровке можно использовать для синхронизации действий между разными драйверами и между драйверами и приложениями пользовательского режима.

Совместное использование двумя несвязанными драйверами одного объекта события, созданного вызовом KeInitializeEvent, есть весьма непростая задача. Более простого способа передать его, иначе как по специальному предварительному соглашению (например, с использованием специального внутреннего кода IOCTL), не существует. Имеется и такая проблема: как гарантировать, что драйвер, создавший объект события, и в момент получения указателя другим драйвером и во все время его использования все еще останется загруженным?

Функции IoCreateSynchronizationEvent и IoCreateNotificationEvent

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


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

При использовании именованного объекта события совместно драйвером и приложением пользовательского режима следует создавать такой объект сначала в пользовательском приложении. Причина кроется в том, что пользовательские объекты события должны размещаться в директории объектов \BaseNamedObjects, которая создается после инициализации подсистемы Win32 и к моменту запуска драйвера, возможно, еще не существует. После этого, в результате IOCTL запроса (выступающего в роли команды) к драйверу, последний должен получить доступ к объекту события по заранее определенному имени либо должен получить некоторую дополнительную информацию из IOCTL запроса &#8212 имя или дескриптор созданного объекта события.

Таблица 10.30. Прототип вызовов IoCreateSynchronization(Notification)Event

PKEVENT IoCreateSynchronizationEvent

PKEVENT IoCreateNotificationEvent
IRQL == PASSIVE_LEVEL
Параметры Создает новый или получает доступ к существующему объекту события по имени
IN PUNICODE_STRING EventName Имя объекта, заканчивающаяся нулем строка широких (UNICODE) символов
OUT PHANDLE EventHandle Указатель, по которому будет возвращен дескриптор объекта.
Возвращаемое значение Указатель на созданный или существующий объект события с данным именем либо NULL в случае ошибки.
Для работы драйверу требуется указатель на объект события. Его можно получить из дескриптора существующего объекта следующим способом.

  • Выполнить вызов ObReferenceObjectByHandle. Эта функция возвращает указатель на собственно объект и увеличивает на единицу число ссылок на данный объект.


  • Поскольку собственно дескриптор становится ненужным, необходимо вы полнить вызов ZwClose со значением дескриптора. Эта функция выполнит уменьшение на единицу счетчика ссылок на данный объект.


  • Когда объект события станет ненужным, необходимо выполнить вызов ObDereferenceObject

    для того, чтобы уменьшить на единицу счетчик ссылок на объект, что, возможно, уничтожит его.


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

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

    Пример использования объекта события для синхронизации работы приложения и драйвера можно найти в следующей главе.


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