Диспетчерские точки входа драйвера
Информация, требуемая для выполнения запроса ввода/вывода, содержится в различных элементах как фиксированной части IRP, так и стека размещения ввода/вывода. Рассмотрим эти элементы. Структура поля Parameters в стеке размещения ввода/ вывода зависит от кода главной и второстепенной функции ввода/вывода. Нас в основном будет интересовать структура поля Parameters для запросов чтения, записи и пользовательских запросов ввода/вывода:
- 1. IRPJVIJ_READ. Параметры
для этого функционального кода содержат следующее:
- Parameters.Read.Length (ULONG) содержит размер в байтах буфера инициатора запроса.
- Parameters. Read.Key (ULONG) содержит ключевое значение, которое нужно использовать при чтении. Обычно представляет интерес только для драйверов файловой системы.
- Parameters .Read.ByteOfFset (LARGE_INTEGER) содержит смещение (обычно в файле), с которого должна начаться операция чтения.
- 2. IRP_MJ_WRITE. Параметры
для этого функционального кода следующие:
- Parameters. Write.Length (ULONG) содержит размер в байтах буфера инициатора запроса.
- Parameters.Write.Key (ULONG) содержит ключевое значение, которое нужно использовать при записи. Обычно представляет интерес только для драйверов файловой системы.
- Parameters.Write.ByteOffset (LARGE_INTEGER) содержит смещение (обычно в файле) с которого должна начаться операция записи.
- 3. IRPJMJ_DEVICE_CONTROL. Параметры
для этого функционального кода следующие:
- Parameters.DeviceloControl.OutputBufferLength (ULONG) содержит длину в байтах буфера OutBuffer.
- Parameters.DeviceloControl.InputBufferLength (ULONG) содержит длину в байтах буфера InBuffer.
- Parameters. DeviceloControl.ControlCode (ULONG) содержит код управления вводом/выводом, идентифицирующий запрашиваемую функцию управления устройством. Этот управляющий код обычно предварительно определен драйвером с использованием макрокоманды CTL_CODE.
- Parameters.DeviceloControl.TypeSInputBuffer (PVOID) содержит виртуальный адрес буфера инициатора запроса InBuffer (см. функцию Win32 API DeviceloControl()). Адрес обычно используется только тогда, когда IOCTL использует METHOD_NEITHER.
Запросы чтения и записи IRP_MJ_READ и IRPJVLMVRITE
Метод передачи буфера, используемый в запросах чтения и записи, контролируется полем Flags объекта-устройства. После создания объекта-устройства с помощью функции loCreateDevice() необходимо инициализировать это поле. Поле может иметь установленными несколько флагов, при этом применяются следующие правила:
- 1. Если установлены флаги DO_BUFFERED_IO или DO_DIRECT_IO, метод передачи буфера будет соответственно буферизованным или прямым.
- 2. Если поле флагов не инициализировано (никакие флаги не установлены), используется метод передачи буфера Neither («никакой» ввода/вывода).
- 3. Одновременная установка флагов DO_BUFFERED_IO и DO_DIRECT_IO запрещена и будет являться ошибкой.
- 4. Установленный полем Flags метод передачи будет использован и запросом чтения, и запросом записи.
Расположение буфера в зависимости от метода его передачи
для запросов чтения и записи полностью определяется таблицей 6.
Для завершения запроса IRP на чтение/запись, необходимо установить поле
Irp>IoStatus.Information равным числу прочитанных/записанных в буфер
байт. В случае буферизованного ввода/вывода это поле укажет Диспетчеру
ввода/вывода, сколько байт нужно скопировать из промежуточного буфера
в невыгружаемой области системного адресного пространства в пользовательский
буфер.
Пример обработки запросов чтения/записи
Данный пример обработки запросов чтения/записи демонстрирует получение адреса буфера для чтения/записи и его длины. Такой код вставляется в обработчик диспетчерских функций MajorFunction[IRP__MJ_READ], MajorFuriction[IRP_MJ_WRITE].
//получение адреса буфера для чтения/записи
//в случае буферизованного ввода/вывода
BufferAddress = Irp->AssociatedIrp.SystemBuffer/
//в случае прямого ввода/вывода
BufferAddress = MmGetSystemAddressForMdl(Irp->MdlAddress)/ //в случае
Neither i/o
BufferAddress = Irp->AssociatedIrp.UserBuffer; //получение длины буфера
для чтения/записи stack = = loGetCurrentlrpStackLocation ( Irp );
BufferLength = stack->Parameters.Read.Length;
Запросы IRP_MJ_DEVICE_CONTROL и IRP^MJJNTERNA^DEVICE^CONTROL
Как говорилось выше, точка входа драйвера IRP_MJ_DEVICE_CONTROL вызывается при вызове пользовательской программой функции DeviceloControl(). Прототип этой функции:
BOOL DeviceloControl ( HANDLE hDevice,// описатель открытого
устройства
DWORD dwIoControlCode,// контрольный код запрашиваемой операции
DWORD nlnBufferSize, LPVOID IpOutBuffer,// адрес буфера со входными данными
DWORD nOutBufferSize, LPDWORD IpBytesReturnedy// размер входного буфера
// адрес буфера для приема
// выходных данных
// размер выходного буфера
// адрес переменной
// для получения
// числа реально
// переданных байтов данных
LPOVERLAPPED IpOverlapped
// адрес структуры
// для обеспечения
// асишсронности
// ввода/вывода
Зачем нужен IRP_MJ_DEVICE_CONTROL? Когда драйвер поддерживает
определенный тип устройства, такое устройство обычно имеет набор специализированных
возможностей, которые могут управляться через драйвер. Эти возможности
не могут быть задействованы использованием стандартных кодов функций IRP_MJ_CREATE,
IRP_MJ_CLOSE, IRP_MJ_READ и IRP_MJ_WRITE. Например, драйвер устройства
для лентопротяжного устройства SCSI должен обеспечить механизм, который
дает возможность пользователям послать запрос на стирание ленты. Такие
зависящие от устройства запросы описываются, используя главный функциональный
код IRP_MJ_DEVICE_CONTROL. Устройство обычно может принимать несколько
разнотипных команд. Для драйвера тип такой команды указывается Кодом
Управления ввода/вывода (IOCTL), который передается как часть запроса
ввода/вывода.
Возвращаясь к функции DeviceloControl(), код управления ввода/вывода
(IOCTL) указывается во втором параметре этой функции. Код управления
ввода/вывода имеет специальный формат, указывающий метод передачи буфера
и другую информацию. Этот формат будет рассмотрен в следующем разделе.
Путаница может возникнуть при задании буфера для ввода/вывода.
Как видно из прототипа функции DeviceloControl(), она может передавать
два буфера (третий и пятый параметры). Несмотря на названия буферов -
входной и выходной буфер - выходной буфер может быть использован как для
передачи данных в драйвер, так и для приема данных из драйвера. Разница
будет в используемом методе передачи буфера. Использование буферов будет
подробно рассмотрено в разделе «Получение буфера».
Задание кода управления вводом/выводом (IOCTL)
Формат кода управления ввода/вывода показан на рис. 11.
Рис11. Формат кода управления вводом/выводом
CTL_CODE( DeviceType, Function, Method, Access ) - специальный
макрос, определенный в заголовочных файлах ntddk.h и windows.h, для задания
кода в формате, представленном на рис. 11.
Рассмотрим составляющие кода управления вйода/вывода:
- 1. Поле DeviceType определяет
тип объекта-устройства, которому предназначен запрос. Это тот самый
тип устройства, который передается функции IoCreateDevice()
при создании устройства. Как уже говорилось, существует два диапазона значений типов устройств: 0-32767 - зарезервированные значения для стандартных типов устройств, 32768-65535 — диапазон значений типов устройств для выбора разработчиком. Следует отметить, что несколько разных устройств могут иметь одинаковое значение типа устройства. Поскольку каждый запрос ввода/вывода предназначен конкретному устройству, совпадение типов устройств не приводит к неприятностям. Также необходимо отметить, что тип устройства в коде управления ввода/вывода может не совпадать с типом устройства объекта-устройства, и это не будет являться ошибкой.
2. Поле Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса/Значения поля Function должны быть уникальны внутри устройства. Как и для типов устройств, существует два диапазона значений поля Function: 0-2047 — зарезервированный диапазон значений, и 2048-4095 — диапазон значений, доступный разработчикам устройств.
3. Поле Method указывает метод передачи буферов данных. Для понимания этого поля вернемся к функции DeviceloControl(). Функция передает два буфера - InBuffer и OutBuffer. Буфер InBuffer передает данные драйверу, буфер OutBuffer может передавать данные в обоих направлениях (к драйверу и от драйвера).
В следующей таблице приведены возможные значения поля Method и методы пе-J редачи буферов InBuffer и OutBuffer:
Значение
поля Method
|
Использование
OutBuffer
|
Используемый
метод передачи буфера
|
|
InBuffer
|
OutBuffer
|
||
METHOD
BUFFERED
|
|
Буферизованный
ввод/вывод (Buffered I/O)
|
|
METHOD_IN_DIRECT
|
Передача
данных к драйверу
|
Буферизованный
ввод/вывод
|
Прямой
ввод/вывод. Осуществляется про- верка буфера на дос- туп по чтению
|
METHODJDUTJDIRECT
|
Приема
данных от драйвера
|
Буферизованный
ввод/вывод
|
Прямой
ввод/вывод. Осуществляется про- верка буфера на дос- туп по записи
|
METHOD
NEITHER
|
|
Neither
I/O
|
Местоположение буферов данных в пакете IRP будет рассмотрено в следующем разделе («Получение буфера»).
- 4. Поле Access указывает тип доступа, который должен был быть запрошен
(и предоставлен) при открытии объекта-файла, для которого передается
данный код Управления вводом/выводом. Возможные значения для этого параметра
следующие:
- FILE_ANY_ACCESS. Это значение указывает, что при вызове CreateFile() мог быть запрошен любой доступ.
- FILE_READ_ACCESS. Это значение указывает, что должен был быть запрошен доступ для чтения.
- FILE_WRITE_ACCESS. Это значение указывает, что должен был быть запрошен доступ для записи.
Заметим, что file_read_access и file_write_access могут
быть указаны одновременно, чтобы указать, что при открытии устройства
должен быть предоставлен и доступ на чтение, и доступ на запись.
Параметр Access требуется потому, что операции управления ввода/вывода,
по своей сути не есть операция чтения или записи. Прежде, чем будет разрешена
запрашиваемая операция ввода/вывода, Диспетчер Ввода/вывода должен знать,
какой режим доступа нужно проверить в таблице описателей.
Получение буфера
При использовании буферизованного
метода, Диспетчер ввода/вывода выделяет в системной невыгружаемой
памяти промежуточный буфер, размер которого равен максимальному из размеров
буферов InBuffer и OutBuffer. Если при запросе был определен InBuffer
и его длина не нулевая, содержание InBuffer копируется в промежуточный
буфер. В любом случае, адрес промежуточного буфера помещается в IRP в
поле Associatedlrp.SystemBuffer. Затем IRP, содержащий запрос, передается
драйверу.
Данные, находящиеся в промежуточном буфере, могут читаться и перезаписываться
драйвером. Затем драйвер размещает в промежуточном буфере данные, которые
нужно вернуть в OutBuffer.
При завершении запроса ввода/вывода, если OutBuffer был определен при
запросе ввода/вывода и его длина не нулевая, Диспетчер ввода/вывода копирует
из промежуточного буфера в OutBuffer столько байтов, сколько было указано
в поле 1гр->IoStatus.Information. После этого, как и при любом буферизированном
запросе Ввода/вывода, Диспетчер ввода/вывода освобождает промежуточный
буфер.
При использовании методов METHOD_IN_DIRECT и METHOD_OUT_
DIRECT, буфер InBuffer, если он определен в запросе ввода/вывода
и его длина не нулевая, обрабатывается в точности так же, как и при буферизованном
вводе/выводе. В этом случае выделяется промежуточный буфер, в него копируется
InBuffer, указатель на промежуточный буфер помещается в IRP в поле Associatedlrp.SystemBuffer.
Буфер OutBuffer, если он определен в запросе ввода/вывода и его длина
не нулевая, обрабатывается в соответствии с прямым вводом/выводом. В этом
случае адрес проверяется на возможность доступа (запись или чтение), производится
закрепление физических страниц в памяти, и создается таблица описания
памяти MDL, описывающая OutBuffer. Указатель на MDL передается в поле
Irp->MdlAddress.
При использовании метода METHOD_NEITHER, оба
буфера передаются в соответствии с методом Neither. To есть, не производится
проверка доступности памяти, не выделяются промежуточные буфера и не создаются
MDL. В пакете IRP передаются виртуальные адреса буферов в пространстве
памяти инициатора запроса ввода/вывода. Адрес буфера OutBuffer передается
в фиксированной части IRP в поле Irp-
>UserBuffer, адрес буфера InBuffer передается в стеке размещения ввода/вывода
в поле stack->Parameters.DeviceControl.Type3InputBuffer. Положение
буферов показано в таблице 7.
Таблица 7
|
METHOD
BUFFERED
|
METHOD
IN DIRECT
|
METHOD
OUT DIRECT
|
METHOD
NEITHER
|
|
InBuffer
|
Метод передачи
|
Buffered
I/O
|
Buffered
I/O
|
Buffered
I/O
|
Виртуальный
адрес инициатора запроса
|
Если су-
ществует, то где располо- жен
|
Адрес промежуточного
буфера в фиксированной части IRP в поле Irp->AssociatedIrp.
SystemBuffer
|
В стеке
размещения ввода/вывода вир- туальный адрес инициатора запроса
в Parame ters. Devicelo- Control. TypeSInputBuffer
|
|||
Длина
|
Длина в
байтах в поле Parameters.DeviceloControl.InputBuffer Length в
текущем стеке размещения ввода/вывода.
|
||||
Out- Buffer
|
Метод передачи
|
Buffered
I/O
|
Direct
I/O
|
Direct
I/O
|
Виртуальный
адрес инициатора запроса
|
Если су-
ществует, то где располо- жен
|
Адрес промежуточного
буфера в фиксированной части IRP в поле Irp->Associate-dlrp.SystemB
uffer
|
MDL, адрес
в Irp->MdlAd- dress
|
MDL, адрес
в Irp->MdlAd- dress
|
Виртуальный
адрес инициатора запроса в Irp->UserBuffer
|
|
Длина
|
Длина в
байтах в поле Parameters.DeviceloControl.OutputBufferLength в
текущем стеке раз- мещения ввода/вывода.
|
Для завершения запроса IRP
необходимо установить поле Irp->IoStatus.Information равным числу прочитанных/записанных
в буфер байт. В случае буферизованного ввода/вывода это поле укажет Диспетчеру
ввода/вывода, сколько
байт нужно скопировать из промежуточного буфера в невыгружаемой области
системного адресного пространства в пользовательский буфер.
Пример обработки
Пример получения адресов и длин буферов в диспетчерской функции драйвера, обрабатывающей функциональные коды IRP_MJ_CREATE, IRP_MJ_CLOSE и IRP_MJ_DEVICE_CONTROL:
stack = loGetCurrentlrpStackLocation (Irp); switch (pIrpStack->MajorFunction)
{
case IRP_MJ_CREATE: case IRP_MJ_CLOSE: break;
case IRP_MJ_DEVICE_CONTROL:
switch (stack->Parameters.DeviceloControl.loControlCode) { case IOCTL_MY_BUFFERED:
InBuffer = Irp->AssociatedIrp.SystemBuffer; InLength = stack->Parameters.DeviceloControl.InputBuffer.Length;
OutBuffer = Irp->AssociatedIrp.SystemBuffer; OutLength = stack->Parameters.DeviceloControl.OutputBufferLength;
case IOCTL_MY_IN_DIRECT:
//OutBuffer доступен только для чтения InBuffer = Irp->AssociatedIrp.SystemBuffer;
InLength = stack->Parameters.DeviceloControl.InputBufferLength; OutBuffer
= MmGetSystemAddressForMdl( Irp->MdlAddress );
OutLength = stack->Parameters.DeviceloControl.OutputBufferLength; break;
case IOCTL_MY_OUT_DIRECT:
//OutBuffer доступен для чтения/записи InBuffer = Irp->AssociatedIrp.SystemBuffer;
InLength = stack->Parameters.DeviceloControl.InputBufferLength; OutBuffer
= MmGetSystemAddressForMdl( Irp->MdlAddress
);
OutLength = stack->Parameters.DeviceloControl.OutputBufferLength; break;
case IOCTL_MY_NEITHER:
InBuffer = irpStack->Parameters.DeviceloControl.Type3InputBuffer;
InLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;
OutBuffer = Irp->UserBuffer;
OutLength = irpStack->Parameters.Device!oControl.OutputBufferLength;
break;