Объекты события
Событие есть синхронизационный примитив (объект), который должен быть явно установлен в сигнальное либо несигнальное состояние. Событие похоже на бинарный флаг, позволяющий одному потоку подавать сигналы другим потокам о том, что нечто, оговоренное заранее, произошло, и этот сигнал подается путем установки объекта события в сигнальное состояние.
Если присмотреться к определению объекта события в заголовочных файлах DDK, то становится очевидным, что объект события состоит только лишь из структуры DISPATCHER_HEADER, в то время как другие синхронизационные примитивы — таймеры, семафоры и мьютексы — содержат, помимо DISPATCHER_HEADER, некоторые дополнительные поля. То есть, события — самые простые из них.
Объекты события делятся на две категории: объекты для уведомления (Notification Events) и объекты для синхронизации (Synchronization Events). Тип выбирается в момент инициализации объекта. Эти два типа объекта событий проявляют различие в своем поведении в момент, когда объект переводится в сигнальное состояние.
Как только объект уведомляющего (Notification) события переходит в сигнальное состояние, все потоки, реагирующие на него, выходят из состояния ожидания. Однако объект такого типа необходимо перевести в несигнальное состояние явно (вызовом KeClearEvent), иначе он так и останется в активном состоянии. Поведение данного типа объектов аналогично поведению объектов события пользовательского режима, которые управляются ручной установкой.
Поведение синхронизационных (Synchronization) объектов события несколько отличается. Когда синхронизационный объект события переходит в сигнальное состояние, то он остается в этом состояние лишь столько времени, сколько это необходимо для выполнения одного вызова KeWaitForXxx. Затем объект переводит себя в несигнальное состояние автоматически. Другими словами, ворота остаются открытыми только до тех пор, пока кто-то первый не прошел через них, после чего они закрываются автоматически. Этот тип эквивалентен событиям с авто-сбросом (auto-reset) в пользовательском режиме.
Таблица 10.26. Функции для работы с объектами событий
Что необходимо сделать... | Какой вызов нужно использовать... |
Создать событие | KeInitializeEvent |
Создать именованное событие | IoCreateSynchronizationEvent IoCreateNotificationEvent |
Изменить состояние события | KeSetEvent KeClearEvent KeResetEvent |
Запросить состояние | KeReadStateEvent |
Несигнальное состояние объекта событий можно установить при помощи вызовов 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 — сигнальное состояние • FALSE — несигнальное состояние |
Возвращаемое значение | void |
u KeResetEvent
VOID KeClearEvent LONG KeResetEvent |
IRQL <= DISPATCH_LEVEL |
Параметры | Установка объекта события в несигнальное состояния |
IN PKEVENT pEvent | Указатель на инициализированный объект события |
Возвращаемое значение | KeResetEvent возвращает предыдущее состояние объекта события |
LONG KeSetEvent | IRQL <= DISPATCH_LEVEL |
Параметры | Переводит объект события в сигнальное состояние |
IN PKEVENT pEvent | Указатель на инициализированный объект события |
IN KPRIORITY Increment | Обычно используется значение IO_NO_INCREMENT |
IN BOOLEAN bWait | Обычно используется значение FALSE |
Возвращаемое значение | Возвращает ненулевое значение, если предыдущее состояние объекта события было сигнальным |
В качестве примера, когда применение объекта события будет весьма кстати, можно привести следующую ситуацию. Предположим, драйвер имеет специально созданный программный поток, который выполняет некоторую работу по получению сигнала прерывания. Завершив ее, поток замирает до прихода следующего прерывания. Реализовать эту схему легко, если рассматриваемый программный поток будет ожидать наступления сигнального состояния объекта события, а переводить его в такое состояние будет драйверная процедура DpcForIsr.
В основе объектов события пользовательского режима лежат объекты события режима ядра — именно те, которые обсуждались выше. Основное различие заключается в том, что типовой доступ в пользовательском режиме — по дескриптору, а в режиме ядра — по указателю. Иными словами, один и тот же объект события при некоторой сноровке можно использовать для синхронизации действий между разными драйверами и между драйверами и приложениями пользовательского режима.
Совместное использование двумя несвязанными драйверами одного объекта события, созданного вызовом KeInitializeEvent, есть весьма непростая задача. Более простого способа передать его, иначе как по специальному предварительному соглашению (например, с использованием специального внутреннего кода IOCTL), не существует. Имеется и такая проблема: как гарантировать, что драйвер, создавший объект события, и в момент получения указателя другим драйвером и во все время его использования все еще останется загруженным?
Функции IoCreateSynchronizationEvent и IoCreateNotificationEvent
позволяют создавать (или открывать, если таковые существуют) именованные объекты события. До тех пор, пока два драйвера используют одно и то же имя этого объекта, они без труда смогут получать указатель на один и тот же объект события. Действие этих функций вполне эквивалентно поведению API вызова CreateEvent. Итак, пусть первый драйвер делает вызов с целью создать объект события с определенным именем и действительно создает его.
Последующие вызовы (с целью создания объекта с тем же именем) нового объекта не создадут, а всего лишь возвратят дескриптор, относящийся к уже существующему объекту события.
При использовании именованного объекта события совместно драйвером и приложением пользовательского режима следует создавать такой объект сначала в пользовательском приложении. Причина кроется в том, что пользовательские объекты события должны размещаться в директории объектов \BaseNamedObjects, которая создается после инициализации подсистемы Win32 и к моменту запуска драйвера, возможно, еще не существует. После этого, в результате IOCTL запроса (выступающего в роли команды) к драйверу, последний должен получить доступ к объекту события по заранее определенному имени либо должен получить некоторую дополнительную информацию из IOCTL запроса — имя или дескриптор созданного объекта события.
Таблица 10.30. Прототип вызовов IoCreateSynchronization(Notification)Event
PKEVENT IoCreateSynchronizationEvent PKEVENT IoCreateNotificationEvent |
IRQL == PASSIVE_LEVEL |
Параметры | Создает новый или получает доступ к существующему объекту события по имени |
IN PUNICODE_STRING EventName | Имя объекта, заканчивающаяся нулем строка широких (UNICODE) символов |
OUT PHANDLE EventHandle | Указатель, по которому будет возвращен дескриптор объекта. |
Возвращаемое значение | Указатель на созданный или существующий объект события с данным именем либо NULL в случае ошибки. |
для того, чтобы уменьшить на единицу счетчик ссылок на объект, что, возможно, уничтожит его.
Эти вызовы могут быть выполнены только с уровня IRQL равного PASSIVE_LEVEL, что накладывает ограничения на то, где драйвер сможет их использовать.
В том случае, если драйвер получает от приложения дескриптор через IOCTL запрос, то этот дескриптор имеет силу, поскольку код драйвера (обработчика IOCTL запросов) работает в контексте пользовательского потока, обратившегося к драйверу.
Пример использования объекта события для синхронизации работы приложения и драйвера можно найти в следующей главе.