[Previous] [Next]

Работа с нижними слоями драйверного стека

Переходя к модели WDM и многослойной архитектуре, возникает необходимость уяснения практических механизмов сотрудничества драйверов в рамках этой концепции. Что должен делать драйвер, чтобы задействовать нижние драйвера и соответствовать требованиям модели, оправдывая надежды верхних драйверов?

Второе требование удовлетворить легче. Драйвер не знает и не может знать, что за клиент инициировал запрос, какова была его мотивация и полномочия. В этой ситуации драйвер просто получает IRP пакеты и должен добросовестно их обработать, отправляя их нижним слоям драйверов или выполняя всю обработку самостоятельно. Возвращая управление Диспетчеру ввода/вывода, рабочая процедура должна пометить текущий IRP пакет как завершенный (вызовом IoCompleteRequest, таблица 9.10) или как требующий дополнительной обработки (вызовом IoMarkIrpPending, таблица 9.12). Как должна развиваться ситуация в последнем варианте событий, было рассмотрено ранее.

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

VOID IoMarkIrpPending IRQL <= DISPATCH_LEVEL
Параметры Помечает пакет IRP как требующий дополнительной обработки
IN PIRP pIrp Указатель на текущий IRP пакет
Возвращаемое значение void

Драйвер обязан возвратить управление и пометить пакет как требующий дополнительной обработки в том случае, если пакет IRP, отправленный нижним слоям драйверов, вернулся со значением TRUE в поле pIRP->PendingReturned (разумеется, если только драйвер сам не является создателем этого пакета).

Ситуация усложняется, если речь заходит о взаимодействии с нижними драйверными слоями. Если драйвер, получая запрос от Диспетчера ввода/вывода, просто отправляет его нижним слоям (устанавливая процедуру CompletionRoutine перехвата пакета "на обратном пути", или не делая этого), то все сводится к тому, чтобы правильно манипулировать вызовами IoSetCompletionRoutine, IoCallDriver, IoGetCurrentIrpStackLocation, IoSkipCurrentIrpStackLocation и IoCopyCurrentIrpStackLocationToNext. Однако в том случае, если драйвер должен дробить поступающие запросы, накапливать, размножать (например, чтобы послать устройствам, работающим параллельно) или формировать собственные, то возникает задача создания новых пакетов IRP, поскольку только они являются средством общения между драйверами. Не составляют исключения и драйверы, работающие через прямой интерфейс (адреса вызовов), поскольку начальная инициализация интерфейса поначалу происходит через IRP запрос.

Диспетчер ввода/вывода конструирует пакеты IRP по запросу драйвера, который тот может осуществить с помощью вызовов:

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

В случае, если промежуточный драйвер использует IoAllocateIrp или ExAllocatePool (см. далее в тексте) для создания IRP пакета, то есть создает IRP с нуля — начиная с выделения памяти, то драйвер должен явным образом указать количество ячеек стека в пакете IRP при его создании или учесть их количество при выделении памяти. Общепринято использование поля StackSize в целевом объекте устройства для этой цели.

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

// Откуда берется указатель pTargetDevice, см. выше в 9 главе
pNewIrp = IoAllocateIrp ( pTargetDevice -> StackSize + 1, FALSE );

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

IoSetNextIrpStackLocation( pNewIrp );
pUsefulArea = IoGetCurrentIrpStackLocation ( pNewIrp );
// Теперь можем использовать пространство ячейки стека вывода,
// на которую указывает pUsefulArea, по собственному усмотрению.

// Устанавливаем разнообразные необходимые значения в ячейке стека,
// соответствующей нижнему драйверу
pNextIoStackLocation = IoGetNextIrpStackLocation ( pNewIrp );
pNextIoStackLocation -> MajorFunction = IRP_MJ_XXX;
. . . . . . .
// Подключаем процедуру завершения
IoSetCompletionRoutine( pNewIrp,
                        OurIoCompletionRoutine,
                        NULL, TRUE, TRUE, TRUE );


// Посылаем IRP пакет целевому драйверу:
IoCallDriver (pTargetDevice, pNewIrp ); 

Что можно хранить в такой искусственно созданной ячейке? Например, число попыток отослать данный IRP пакет нижнему драйверу, если он отказывается обработать его немедленно. При сериализации поступившего от клиента запроса на объемную операцию ввода/вывода, можно хранить число переданных байт данных и общий размер операции, используя один и тот же IRP пакет многократно.