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

       

Новые рабочие процедуры в WDM драйверах


Процедура AddDevice, вызываемая PnP Менеджером, только лишь производит инициализацию объекта устройства и, если необходимо, структуры данных расширения объекта устройства. В процедуре AddDevice, по правилам хорошего тона WDM модели, действия над собственно аппаратурой не должны совершаться. Но тогда остаются нерешенными две важные задачи:

  • резервирование и конфигурирование аппаратных ресурсов обслуживаемого физического устройства;
  • инициализация и подготовка аппаратной части к использованию.
  • Все это должен сделать драйвер по получении IRP пакета с кодом IRP_MJ_PNP. Такие IRP пакеты посылается PnP Менеджером, когда происходят события включения или выключения устройства, либо возникают вопросы по конфигурированию устройства.

    Категория IRP_MJ_PNP пакетов включает запросы широкого спектра, которые детализируются суб-кодами IRP_MN_Xxx. Поскольку они пропускается через единственную рабочую процедуру, то ее обязанностью является вторичная диспетчеризация по этим суб-кодам, содержащимся в IRP пакете и описывающим специфические действия, осуществления которых ожидает PnP Менеджер.

    Регистрация новой для WDM модели рабочей процедуры, которой будет поручено обрабатывать запросы IRP_MJ_PNP со всеми подтипами IRP_MN_Xxx, производится традиционным образом в процедуре DriverEntry:

    pDriverObj-&#62MajorFunction[IRP_MJ_PNP] = MyPnP_Handler;

    Пример программного кода для осуществления вторичной диспетчеризации на основе суб-кодов IRP_MN_Xxx приводится ниже.

    NTSTATUS MyPnP_Handler ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { // Получить указатель на текущую ячейку стека IRP пакета PIO_STACK_LOCATION pIrpStackLocation = IoGetCurrentIrpStackLocation( pIrp );

    switch (pIrpStackLocation -&#62MinorFunction) { case IRP_MN_START_DEVICE: . . . // Внимание. Все ветви оператора switch должны возвратить // результаты обработки . . . default: // если не поддерживается здесь, то передать запрос вниз: IoSkipCurrentIrpStackLocation(pIrp); return IoCallDriver(. . ., pIrp); } }

    Первым параметром вызова IoCallDriver (см.
    таблицу 9.5), разумеется, является указатель на объект устройства, к которому было произведено подключение в процедуре AddDevice.



    Вызов IoSkipCurrentIrpStackLocation (см. таблицу 9.6) сообщает Диспетчеру ввода/вывода, что драйвер отказывается от дальнейшего участия в судьбе данного IRP пакета. В том случае, если драйвер желает получить управление над IRP пакетом в момент, когда его обработка нижними слоями драйверов будет завершена, то он должен воспользоваться системным вызовом IoCopyCurrentIrpStackLocationToNext

    (см. таблицу 9.7) и зарегистрировать процедуру CompletionRoutine. Она будет вызвана в соответствующий момент.

    Таблица 9.5. Прототип функции IoCallDriver

    NTSTATUS IoCallDriver IRQL &#60= DISPATCH_LEVEL
    Параметры Обращается к другому драйверу с запросом, сформулированным в пакете IRP (запросы типа IRP_MJ_POWER следует выполнять при помощи вызова PoCallDriver)
    IN PDEVICE_OBJECT pDevObj Указатель на объект устройства, которому адресован IRP запрос
    IN PIRP pIrp Указатель на отправляемый IRP пакет
    Возвращаемое значение • STATUS_SUCCESS

    • STATUS_PENDING &#8212 в случае, если пакет требует дополнительной обработки

    • STATUS_Xxx &#8212 в случае ошибки
    Таблица 9.6. Прототип функции IoSkipCurrentIrpStackLocation

    VOID IoSkipCurrentIrpStackLocation IRQL &#60= DISPATCH_LEVEL
    Параметры Изменяет указатель стека IRP так, что нижестоящий драйвер будет считать текущую ячейку стека IRP своей
    IN PIRP pIrp Указатель на модифицируемый IRP пакет
    Возвращаемое значение void
    Таблица 9.7. Прототип функции IoCopyCurrentIrpStackLocationToNext

    VOID IoCopyCurrentIrpStackLocationToNext IRQL &#60= DISPATCH_LEVEL
    Параметры Копирует содержимое ячейки стека IRP для текущего драйвера в ячейку стека для нижестоящего драйвера
    IN PIRP pIrp Указатель на модифицируемый IRP пакет
    Возвращаемое значение void
    Процедура завершения ввода/вывода CompletionRoutine есть обратный вызов от Диспетчера ввода/вывода, который позволяет перехватить IRP пакет после того, как низкоуровневый драйвер завершит его обработку.


    Процедура завершения ввода/вывода регистрируется вызовом IoSetCompletionRoutine (см. таблицу 9.8).

    Таблица 9.8. Прототип макроопределения IoSetCompletionRoutine

    VOID IoSetCompletionRoutine IRQL &#60= DISPATCH_LEVEL
    Параметры Выполняет регистрацию callback-функции завершения обработки IRP пакета
    IN PIRP pIrp Указатель на отслеживаемый IRP пакет
    IN PIO_COMPLETE_ROUTINE CompletionRoutine Функция, которая должна получить управление, когда обработка IRP будет завершена
    IN PVOID pContext Параметр, который получит регистрируемая callback функция CompletionRoutine
    IN BOOLEAN doCallOnSuccess Вызывать CompletionRoutine в случае успешного завершения обработки данного IRP пакета
    IN BOOLEAN doCallOnError Вызывать CompletionRoutine в случае завершения обработки данного IRP с ошибкой
    IN BOOLEAN doCallOnCancel Вызывать CompletionRoutine в случае прерванной обработки данного IRP пакета
    Возвращаемое значение void
    Подключать собственные процедуры CompletionRoutine для детектирования окончания обработки IRP пакетов можно не только к пакетам с кодом IRP_MJ_PNP, но также ко всем остальным, отправляемым драйверам нижних слоев.
    Вызов IoSetCompletionRoutine помещает регистрируемую функцию в ячейке стека IRP пакета, соответствующую нижнему драйверу. Поэтому, за исключением драйвера, находящегося в самом низу стека, каждый драйвер в иерархии может подключить свою собственную процедуру окончания ввода/вывода к обработке данного IRP пакета. Процедуры завершения выполняются в порядке помещения драйверных объектов в стек, то есть снизу к вершине стека.

    Таблица 9.9. Прототип функции завершения ввода/вывода CompletionRoutine

    NTSTATUS CompletionRoutine IRQL == см. текст ниже
    Параметры Перехватывает пакет IRP после завершения работы нижнего драйверного слоя
    IN PDEVICE_OBJECT pDevObj Объект устройства (в составе данного драйвера), которому был ранее адресован данный IRP пакет
    IN PIRP pIrp Указатель на IRP пакет, обработка которого только что завершена
    IN PVOID pContext Аргумент, указанный в IoSetCompleteRoutine
    Возвращаемое значение STATUS_MORE_PROCESSING_REQUIRED

    STATUS_SUCCESS
    <


    Однозначно предсказать, на каком уровне IRQL выполняется процедура завершения, невозможно. В том случае, если нижележащий драйвер, вызывает IoCompleteRequest

    (подробности &#8212 ниже) с уровня IRQL равного PASSIVE_LEVEL, то процедура завершения находящегося выше драйвера выполняется на уровне PASSIVE_LEVEL. В случае, если лежащий ниже драйвер завершает обработку IRP пакета на уровне DIPATCH_LEVEL (например, из DPC процедуры), то и процедура завершения лежащего выше драйвера выполняется на уровне DISPATCH_LEVEL.

    Выполнение программного кода на уровне DISPATCH_LEVEL ограничивается системными вызовами, которые работают на этом уровне IRQL. Разумеется, следует особо позаботиться, чтобы здесь не производилась работа со страничной памятью.

    Таблица 9.10. Прототип функции IoCompleteRequest

    VOID IoCompleteRequest IRQL &#60= DISPATCH_LEVEL
    Параметры Вызывается, когда драйвер желает полностью завершить обработку данного IRP пакета. Обеспечивает вызов процедур завершения всех драйверов, имеющихся над данным (см. ниже)
    IN PIRP pIrp Указатель на текущий IRP пакет, обработка которого только что завершена
    IN CCHAR PriorBoost Величина, на которую следует изменить приоритет потока, выполняющего обработку данного IRP пакета. Величина IO_NO_INCREMENT используется, если никаких изменений делать не нужно.
    Возвращаемое значение void
    Чтобы устранить упомянутую выше неоднозначность уровня IRQL работы процедуры завершения, можно прибегнуть к следующей уловке. Предположим, что мы имеем программный код рабочей процедуры. Известно также, что PnP Менеджер (как, впрочем, и Диспетчер ввода/вывода) всегда выполняет вызов рабочей процедуры драйвера на уровне PASSIVE_LEVEL. Тогда, отправляя пакет IRP нижним слоям драйвера, организуем ожидание (средствами объекта события режима ядра) не выходя из кода данной рабочей процедуры, пока отосланный нижним слоям IRP пакет не возвратится в зарегистрированную функцию CompletionRoutine. Как только это произойдет, объект события кодом функции CompletionRoutine будет переведен в сигнальное состояние, и стадия ожидания в основном потоке завершится.


    Таким образом, мы получим сигнал о завершении обработки пакета IRP на вполне определенном уровне IRQL, равном именно PASSIVE_LEVEL. Полностью данный метод описывается в примере ниже:

    . . . . . . // код рабочей процедуры, выполняющийся на уровне PASSIVE_LEVEL . . . . . . IoCopyCurrentIrpStackLocationToNext(pIrp);

    // Резервируем место под объект события: KEVENT myEvent; // Инициализируем его, состояние не сигнальное: KeInitializeEvent( &myEvent, NotificationEvent, FALSE ); // Регистрируем свою процедуру завершения обработки IRP пакета. // Указатель на объект myEvent передаем как дополнительный параметр. IoSetCompletionRoutine( pIrp, MyCompleteRoutine, (PVOID)&myEvent, TRUE, TRUE, TRUE);

    // Предположим, что указатель на объект устройства, // к которому был подключен текущий объект устройства, был ранее // сохранен в структуре расширения текущего объекта устройства. PDEVICE_EXTENSION pDeviceExtension = (PDEVICE EXTENSION) pDeviceObject-&#62DeviceExtension; PDEVICE_OBJECT pUnderlyingDevObj = pDeviceExtension-&#62pLowerDevice;

    // Отправляем IRP пакет на обработку нижними драйверными слоями IoCallDriver( pUnderlyingDevObj, pIrp );

    // Организуем ожидание, пока не закончится работа на нижних уровнях KeWaitForSingleObject( &myEvent, Execute, KernelMode, FALSE, NULL);

    // Теперь завершаем обработку IRP пакета. // Его адрес не изменился - pIrp. // По "возвращении" из "ожидания" уровень IRQL остался прежним для // данного потока. // Поскольку Диспетчер ввода/вывода и PnP Менеджер вызывают // рабочие процедуры драйвера на уровне PASSIVE_LEVEL, то таким // он в данном потоке и остался. . . . } NTSTATUS MyCompleteRoutine( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pContextArgument ) { // Вычисляем указатель на Объект События: PEVENT pEvent = (PEVENT) pContextArgument; // Устанавливаем его в сигнальное состояние KeSetEvent( pEvent, 0, FALSE ); // IRQL &#60=DISPATCH_LEVEL

    // Пакет IRP получен. Завершение работы здесь. Но не окончательно.


    return STATUS_MORE_PROCESSING_REQUIRED; }

    Рассмотрим подробнее работу вызова IoCompleteRequest. Когда некий код некоего драйвера делает этот вызов, программный код IoCompleteRequest

    обращается к ячейкам стека IRP пакета и анализирует, зарегистрировал ли верхний (над текущим) драйвер процедуру завершения CompleteRoutine &#8212 это как раз отмечено в стеке IRP пакета. В том случае, если таковой процедуры не обнаруживается, указатель стека поднимается и снова выполняется проверка. Если обнаружена зарегистрированная функция, то она выполняется. В том случае, если вызванная таким образом функция возвращает код завершения, отличный от STATUS_MORE_PROCESSING_REQUIRED, то указатель стека снова поднимается, и действия повторяются. Если в результате вызова получен код завершения STATUS_MORE_PROCESSING_REQUIRED, то управление возвращается инициатору вызова IoCompleteRequest.

    Когда код IoCompleteRequest благополучно достигает в своем рассмотрении вершины стека, то Диспетчер ввода/вывода выполняет действия по освобождению данного IRP пакета (наряду с некоторыми другими операциями).

    Отсюда несколько важных следствий.

    Во-первых, если драйвер сам создал IRP пакет (подробно рассматривается ниже), то вызов IoCompleteRequest означает приказ Диспетчеру ввода/вывода заняться его освобождением &#8212 поскольку иных драйверов, процедуры завершения которых можно было бы рассматривать, просто нет.

    Во-вторых, если текущий драйвер зарегистрировал свою процедуру завершения и, не вызывая нижних драйверов, сразу выполнил IoCompleteRequest, то такая процедура завершения вызвана не будет &#8212 код IoCompleteRequest

    ee в рассмотрение просто не примет, переходя сразу к анализу ячеек стека IRP для вышестоящих драйверов.

    В-третьих, возможна ситуация, когда после выполнения вызова IoCompleteRequest

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

    В любом случае, после вызова IoCompleteRequest драйвер не имеет права прикасаться к IRP пакету, который передан этому вызову как завершаемый. Кроме того, возврат кода STATUS_MORE_PROCESSING_REQUIRED &#8212 это практика зарегистрированных процедур завершения, что является "просьбой" Диспетчеру ввода/вывода возвратиться к данной процедуре завершения позже.


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