[Previous] [Next]

Рабочие процедуры обслуживания IOCTL запросов

Запросы данного типа формулируются в рамках двух более гибких типов запросов на ввод/вывод (IRP пакетах). Значение основного кода IRP_MJ_Xxx драйвер может найти в соответствующей ему ячейке стека IRP пакета, a IOCTL код размещен в Parameters.DeviceIoControl.IoControlCode той же ячейки стека IRP пакета.

Следует отметить, что реализация процедуры разборки таких IRP запросов в драйвере требует вторичной диспетчеризации — в соответствии со значением IoControlCode. Это значение известно еще со времен MS DOS под именем IOCTL — Input/Output ConTroL code.

Значения IOCTL, передаваемые в драйвер, могут быть определены разработчиком драйвера и имеют фиксированную внутреннюю организацию. Рисунок 8.2 демонстрирует поля 32-битной структуры IOCTL кода. Пакет DDK имеет в своем составе макроопределение CTL_CODE, которое обеспечивает приемлемый механизм генерации значений IOCTL, уже использованный в главе 3. Таблица 8.8 описывает аргументы этого макроопределения.

Рис. 8.2.
Структура блока данных IOCTL

Таблица 8.8. Аргументы макроопределения CTL_CODE

Параметры Описание
DeviceType FILE_DEVICE_XXX значения передаваемые в IoCreateDevice
• 0x0000 — 0x7FFF — зарезервировано Microsoft
• 0x8000 — 0xFFFF — определяется пользователем
ControlCode Определяемые драйвером IOCTL значения
• 0x000 — 0x7FF — зарезервировано Microsoft (public)
• 0x800 — 0xFFF — определяется пользователем
TransferType Способ получения доступа к буферу
• METHOD_BUFFERED
• METHOD_IN_DIRECT
• METHOD_OUT_DIRECT
• METHOD_NEITHER
RequiredAccess

Требования инициатора относительно типа доступа
• FILE_ANY_ACCESS
• FILE_READ_DATA
• FILE_WRITE_DATA
• FILE_READ_DATA | FILE_WRITE_DATA

Операции драйвера, которые работают с IOCTL запросами, часто требуют задания буферной области для размещения входных либо выходных данных, то есть поступающих от пользовательского приложения в драйвер либо в обратном направлении, соответственно. Возможно, что в одном запросе используются сразу оба буфера. В самом деле, вызов функции пользовательского режима DeviceIoControl среди прочих входных параметров имеет два указателя на две буферные области, одну — для входных данных, другую — для выходных. Механизм переноса данных, обеспечиваемый Диспетчером ввода/вывода, определяется как раз в IOCTL. Это может быть либо буферизованный, либо прямой ввод-вывод, либо метод NEITHER. Как было сказано ранее относительно запросов чтения/записи, при буферизованном способе работы с данными, Диспетчер ввода/вывода копирует данные пользовательского буфера в/из промежуточного буфера, размещенного в нестраничном пуле, при работе с которым драйвер не будет испытывать сложностей. При прямом способе ввода/вывода драйвер получает прямой доступ к определенной пользователем буферной области памяти, которая предварительно зафиксирована в оперативной памяти.

В данном случае флаги, определяющие тип буферизации в объекте устройства (pDeviceObject->Flags), не имеют значения при работе с IOCTL запросами. Механизм буферизированного обмена определяется при каждом задании значения IOCTL в специально предназначенном для этого фрагменте этой структуры данных. Данный подход обеспечивает максимальную гибкость при работе с вызовом пользовательского режима DeviceIoControl.

Поле TransferType (таблица 8.8) представляет собой два бита, которые определяют один из следующих типов буферизации:

Так как поле TransferType встроено в значение IOCTL, то документированные Microsoft программные компоненты определяют и механизм буферизации. Для значений IOCTL, определенных в коде драйвера, могут быть заданы любые требуемые значения для описания механизма переноса данных. Для переноса небольших объемов данных и при небольшой скорости обмена вполне приемлем буферизованный ввод/вывод. Для переноса больших объемов и быстрой работы более подойдет прямой ввод/вывод.

Как только у драйвера появляется объявленная им рабочая процедура для обслуживания IRP пакетов с кодами IRP_MJ_INTERNAL_DEVICE_CONTROL либо IRP_MJ_DEVICE_CONTROL, Диспетчер ввода/вывода начинает пропускать соответствующие пакеты IRP внутрь драйвера. Интерпретация поступающих кодов IOCTL управления устройством становится обязанностью и ответственностью драйвера, включая проверку значений полей внутри кода IOCTL. Любое 32 разрядное число, посланное инициатором запроса в качестве IOCTL кода, поступит в соответствующую рабочую процедуру драйвер, поскольку Диспетчер ввода/вывода не выполняет проверки корректности IOCTL кодов.

Типовая конфигурация рабочей процедуры по обслуживанию IOCTL запросов должна быть большим оператором switch, например:

NTSTATUS IoControlCodeHandler(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
	NTSTATUS status = STATUS_SUCCESS;
	PMYDEVICE_EXTENSION pDevExt;
	ULONG ioctlCode, inSize, outSize;
	// Находим нужную ячейку стека IRP пакета
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
	// Находим код IOCTL запроса
	ioctlCode = pIrpStack ->Parameters.DeviceIoControl.IoControlCode;
	// и требуемого размера передаваемых данных
	inSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLenght;
	outsize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLenght;

	switch(controlCode) { // Вторичная диспетчеризация
	case IOCTL_CODE_1:
	{
		// Всегда следует проверять входные параметры
		if(inSize > 0 || outSize > 0)
		{
			Status = STATUS_INVALID_PARAMETER;
			break;
		}
	}
	default: // Драйвер получил непредусмотренные коды IOCTL
		status = STATUS_INVALID_DEVICE_REQUEST;
	break;
	}
	pIrp->IoStatus.Status = status;
	pIrp->IoStatus.Information = 0; // нет данных для передачи
	IoCompleteRequest( pIrp, IO__NO_INCREMENT );
	return status;
}

Доступ к буферным областям, содержащим данные или предназначенным для данных, описывается таблицей 8.9.

Таблица 8.9. Передача адресов буферов данных в IRP пакетах, описывающих IOCTL запросы

  METHOD_BUFFERED METHOD_IN_DIRECT или METHOD_OUT_DIRECT METHOD_NEITHER
Input Буфер с данными Использует буферизацию (системный буфер)
Адрес буфера в системном адресном пространстве указан в pIrp->AssociatedIrp.SystemBuffer
Клиентский виртуальный адрес в Parameters.DeviceIoControl. Type3InputBuffer
Длина указана в Parameters.DeviceIoControl.InputBufferLength
Output Буфер для данных Использует буферизацию (системный буфер)
Адрес буфера в системном адресном пространстве указан в pIrp-> AssociatedIrp.SystemBuffer
Использует прямой доступ, клиентский буфер преобразован в MDL список, указатель на который размещен в
PIrp->MdlAddress
Клиентский виртуальный адрес в pIrp->UserBuffer
Длина указана в Parameters.DeviceloControl.OutputBufferLength

Названия Input и Output здесь и в литературе трактуются с точки зрения драйвера. Буфер 'Input' содержит данные, поступающие от клиента, скорее всего, предназначенные для вывода в устройство. Буфер 'Output' указывает на то место, куда следует поместить данные, ожидаемые клиентом, скорее всего, прочитанные из устройства. Кстати сказать, в описании вызова DeviceIoControl в документации MSDN никакого разночтения с данной трактовкой названий не наблюдается: буфер с данными для выполнения операции (3-й параметр вызова) называется lplnputBuffer, а буфер для получаемых данных (5-й параметр вызова) называется lpOutputBuffer.

Следует также обратить внимание на то, что при методе буферизации METHOD_BUFFERED Диспетчер ввода/вывода в качестве системного буфера для драйвера получает одну область системного адресного пространства, достаточно большую для того, чтобы вместить наибольший из входного/выходного буферов клиента. В эту область Диспетчер ввода/вывода перед вызовом рабочей процедуры, обслуживающей запросы данного типа, копирует данные, передаваемые драйверу. В эту же область драйвер выводит свой данные, предназначенные для получающего буфера клиента. Следует внимательно относиться к операциям записи в этот буфер вводимых данных, поскольку есть опасность испортить, возможно, еще находящиеся там данные, предназначенные к выводу в устройство. Разумеется, если клиент драйвера предлагает в одном запросе действия и ввода, и вывода.

Может показаться странным, что для метода METHOD_IN_DIRECT строится MDL список для выходного (output) буфера, а для входного как бы используется менее мощный метод METHOD_BUFFERED. Тем не менее, это так. Желающие могут обойти это самостоятельно, просто переставив в своих запросах DeviceIoControl адрес input буфера на место output буфера (или учитывая это в драйвере, как это сделала фирма Cypress в драйвере Ezusb.sys). Следует отметить, что имеющаяся в документации DDK пометка относительно построения MDL списка для поставляемых клиентом данных (для input буфера с размещением в поле IRP пакета MdlAddress) на практике и в остальной литературе по данному вопросу подтверждения не находит.

Повторим сведения о размещении областей с данными/для данных при подготовке IRP пакета, описывающего IOCTL запрос к драйверу.

При методе METHOD_BUFFERED

Диспетчер ввода/вывода выделяет единственный буфер в нестраничной памяти, достаточно большой, чтобы вместить входной или выходной буфер инициатора вызова. Адрес этой области размещается в пакете IRP в поле AssociatedIrp.SystemBuffer. Затем производится копирование входного (input) буфера с данными инициатора запроса в эту область. В поле UserBuffer пакета IRP заносится оригинальный адрес буфера для получения данных инициатора запроса.

По завершении обработки IOCTL запроса, Диспетчер ввода/вывода копирует содержимое системного буфера, размещенного в нестраничной памяти, в собственно буфер инициатора. Следует обратить внимание, что только один внутренний буфер предоставляется драйверу, даже если пользователь указал два независимых буфера в качестве входного и выходного.

При методе METHOD_IN_DIRECT и METHOD_OUT_DIRECT

Диспетчер ввода/вывода проверяет приемлемость выходного (output) буфера инициатора вызова и производит его фиксацию (lock) в физической памяти. Затем производит построение списка MDL (Memory Descriptor List) для выходного буфера и сохраняет указатель на MDL в поле MdlAddress пакета IRP.

Кроме того, Диспетчер ввода/вывода выделяет временную область в нестраничном пуле и сохраняет этот адрес в поле AssociatedIrp.SystemBuffer пакета IRP. Производится копирование содержимого входного (input) буфера инициатора вызова в выделенный системный буфер, а в поле UserBuffer производится запись значения NULL. После этого IRP пакет поступает в вызываемую рабочую процедуру драйвера.

При методе METHOD_NEITHER

Диспетчер ввода/вывода помещает адрес входного (input) буфера инициатора вызова в поле Parameters.DeviceIoControl.Type3InputBuffer в текущей ячейке стека пакета IRP текущей операции ввода/вывода. В поле UserBuffer производится запись адреса выходного (output) буфера инициатора вызова, где инициатор вызова ожидает получить результаты выполнения операции. Оба этих адреса указывают в область памяти инициатора вызова.