РПЗ (774931), страница 3
Текст из файла (страница 3)
IN PDEVICE_OBJECT PhysicalDeviceObject) // указатель на родительский PDO
В драйверах, поддерживающих PnP, через эту точку входа менеджер PnP посылает драйверу уведомление об обнаружении устройства, за которое должен отвечать драйвер. Функция AddDevice должна создать объект устройства с помощью вызова IoCreateDevice и при необходимости присоединить его к стеку устройств с помощью IoAttachDeviceToDeviceStack.
NTSTATUS DriverUnload(
IN PDRIVER_OBJECT DriverObject) // указатель на объект драйвера
Вызывается при выгрузке драйвера. В этой функции должны осво
бождаться все затребованные драйвером ресурсы. Драйверы WDM-модели
выполняют эти действия в обработчике запросов IRP_MJ_PNP с субкодом IRP_MN_REMOVE_DEVICE, то есть при удалении устройства из системы.
Следует выделить отдельный класс точек входа драйвера, которые предназначены для обработки IRP-пакетов с различными кодами операций. Эти точки входа регистрируются при загрузке драйвера в функции DriverEntry. Регистрация производится путем заполнения элементов массива MajorFunction адресами диспетчеризуемых функций. Индексом в этом массиве являются коды IRP_MJ_XXX, то есть описанные числами типы пакетов IRP. Диспетчер ввода/вывода, ориентируясь на заполнение этого массива, вызывает нужные функции драйвера.
Поскольку для драйвера важны только адреса рабочих процедур, то все рабочие процедуры могут иметь совершенно произвольные имена.
Пакет запроса ввода/вывода (IRP)
Пакеты ввода/вывода (IRP-пакеты) используются для передачи запросов к драйверу от его клиентов. Они являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых ячейками стека ввода/вывода (I/O Stack Location).
Приведем структуру заголовка IRP-пакета:
Структура заголовка IRP-пакета.
Поля | Описание |
IO_STATUS_BLOCK IoStatus | Статус запроса |
PVOID AssociatedIrp.SystemBuffer | Указатель на системный буфер для случая, если устройство поддерживает буферизованный ввод/вывод |
PMDL MdlAddress | Указатель на MDL-список в случае, если устройство поддерживает прямой ввод/вывод |
PVOID UserBuffer | Адрес пользовательского буфера для ввода/вывода |
BOOLEAN Cancel | Индикатор того, что IRP-пакет должен быть аннулирован |
Основное назначение ячеек стека ввода/вывода состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/вывод. Ниже, в таблице 1.5.2 приводятся поля ячеек стека ввода/вывода, к которым драйвер может обращаться непосредственно по указателю (чего не рекомендуется делать для остальных полей):
Структура ячейки стека ввода/вывода.
Поля | Описание |
UCHAR MajorFunction | Код IRP_MJ_XXX, описывающий назначение операции |
UCHAR MinorFunction | Субкод операции |
PDEVICE_OBJECT DeviceObject | Указатель на объект устройства, которому был адресован данный объект IRP |
PFILE_OBJECT FileObject | Файловый объект для данного запроса, если он задан |
union Parameters (трактовка определяется значением MajorFunction) | |
struct Read | Параметры для IRP типа IRP_MJ_READ: ULONG Length ULONG Key LARGE_INTEGER ByteOffset |
struct Write | Параметры для IRP типа IRP_MJ_WRITE: ULONG Length ULONG Key LARGE_INTEGER ByteOffset |
struct DeviceControl | Параметры для IRP типа IRP_MJ_DEVICE_CONTROL: ULONG OutputBufferLength ULONG InputBufferLength ULONG IoControlCode PVOID Type3InputBuffer |
Приведем графическое представление структуры IRP-пакета:
Рис. 8. Структура IRP пакета.
Общение с USB-накопителями в ОС Windows NT 5 на уровне драйверов, как уже было сказано в разделе, происходит посредством передачи URB-пакетов. Указатели на URB-пакеты содержат ячейки стека IRP-пакета, доступ к этим указателям осуществляется следующим образом:
…
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PURB Urb = IrpSp->Parameters.Others.Argument1;
…
Приведем частичное объявление структуры из справочной документации Microsoft. Отметим только поля, использование которых необходимо в рамках данной курсовой работы:
typedef struct _URB {
union{
struct _URB_HEADER UrbHeader;
struct _URB_SELECT_INTERFACE UrbSelectInterface;
struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
}
} URB, *PURB;
Поле UrbHeader хранит информацию о коде URB-пакета, по которому можно определить, какая операция запрашивается.
Поля UrbSelectInterface и UrbSelectConfiguration служат для запроса по выбору интерфейса и конфигурации устройства, которые будут использоваться при работе с устройством. Пакеты этой структуры отправляются хостом к устройству в начале его работы, при конфигурировании.
Поле UrbBulkOrInterruptTransfer несет наиболее важную в рамках данной курсовой работы информацию – указатели на блоки ввода/вывода USB-устройства. Приведем описание структуры _URB_BULK_OR_INTERRUPT_TRANSFER:
struct _URB_BULK_OR_INTERRUPT_TRANSFER {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;};
Поля этой структуры описаны в следующей таблице:
Поля структуры _URB_BULK_OR_INTERRUPT_TRANSFER.
Поле | Описание |
struct _URB_HEADER Hdr | Стандартный заголовок URB-пакета, содержащий код запроса |
USBD_PIPE_HANDLE PipeHandle | Дескриптор канала, на который передаются данные |
ULONG TransferFlags | Флаги, определяющие направление передачи данных и способ обработки ошибок |
ULONG TransferBufferLength | Длина передаваемого блока данных в байтах |
PVOID TransferBuffer | Указатель на передаваемый буфер. Буфер находится в нестраничной памяти |
PMDL TransferBufferMDL | Указатель на MDL-список, несущий передаваемую информацию. Буфер находится в страничной памяти |
Следует отметить, что один из указателей TransferBuffer или TransferBufferMDL равен NULL, то есть в пределах одного пакета передается только одна порция данных.
Задача протоколирования обмена информацией сводится к перехвату и сохранению буферов TransferBuffer и TransferBufferMDL.
Уровни запроса прерываний
В каждый момент времени центральный процессор находится на одном из уровней IRQL (Interrupt Request Level – уровень запросов прерываний). Уровни IRQL располагаются в порядке убывания от HIGHEST_LEVEL до PASSIVE_LEVEL. Каждому из прерываний (прерывания от внешних устройств, системные часы, и т. д.) соответствует свой уровень IRQL. Специальным действиям операционной системы также назначены IRQL. Они отмечены в нижней части приведённой таблицы:
Уровни запросов прерываний.
Уровень | Назначение |
HIGHEST_LEVEL | Наивысший уровень. Все прерывания заблокированы |
POWER_LEVEL | Прерывания по отказу питания |
IPI_LEVEL | Межпроцессорное взаимодействие |
CLOCK2_LEVEL | Прерывание по системному таймеру 2 |
СLOCK1_LEVEL | Прерывание по системному таймеру 1 |
PROFILE_LEVEL | Прерывание по таймеру замера производительности |
уровни DRQL | Обычные прерывания устройств |
DISPATCH_LEVEL | Диспетчеризация потоков и выполнение отложенных процедур |
APC_LEVEL | Выполнение асинхронного вызова процедуры |
PASSIVE_LEVEL | Обычное исполнение кода потока |
Общее правило обработки уровней запросов прерываний гласит, что прерывания с IRQL, меньшим, чем у выполняемого в данный момент кода, маскируются. Во время исполнения кода потока (пользовательского или системного) устанавливается наименьший IRQL = 0 (PASSIVE_LEVEL). Работа драйвера чаще всего выполняется на уровне IRQL = 2 (DISPATCH_LEVEL). Уровни, лежащие над ним, называются DIRQL (Device IRQL) и выставляются для обработчиков прерываний от внешних устройств (ISR — interrupt service routine). Даже во время выполнения ISR драйвера может произойти прерывание с большим IRQL, например, принадлежащее другому драйверу.
Чем выше текущий уровень IRQL исполняемого кода, тем меньше функций ему доступно. Так, например, диспетчер потоков работает на уровне
DISPATCH_LEVEL, и, следовательно, не будет вызываться, пока на процессоре с уровнем большим или равным DISPATCH_LEVEL исполняется другой код. Таким образом, на уровнях DISPATCH_LEVEL и выше отключается переключение потоков. Функции ожидания диспетчерских объектов (события, мьютексы, семафоры) с отличным от нуля временем, обращение к файлам, подкачка отсутствующих в физической памяти страниц — всё это также становится недоступным. Для корректного сохранения запросов в файле фильтр в таких случаях должен применять специальную методику.
Уведомление о завершении запроса нижестоящим драйвером
При отслеживании обмена данными драйвер-фильтр может получать уведомления о том, что некоторый переданный запрос был завершён нижестоящим драйвером. Механизм уведомления заключается в том, что вызовом специальной функции IoSetCompletionRoutine фильтр обращается к стеку в пакете IRP. В позиции стека, следующей за текущей позицией, он устанавливает в специальном поле адрес функции завершения (completion routine). Затем при передаче пакета по цепочке позиция стека увеличивается.
Когда нижестоящий драйвер отправляет пакет запроса на завершение (вызовом IoCompleteRequest), подсистема ввода/вывода начинает просматривать стек внутри этого пакета от конца к началу. Если в какой-то позиции стека определена функция завершения, управление передаётся ей. Отработав, функция возвращает результат, сигнализирующий об успехе, ошибке или необходимости дальнейшей обработки запроса.
При первых двух вариантах подсистема ввода/вывода переходит к следующей позиции стека и продолжает просмотр, пока не будет достигнуто его начало. После этого запрос завершается нормальным образом.