Массив MajorFunction устанавливает соответствие между кодом типа запроса IRP_MJ_NNN и диспетчерской функцией, которая его обрабатывает. Наш драйвер обрабатывает запросы на открытие и закрытие устройства (RamDskCreateClose), чтение и запись (RamDskReadWrite), расширенные запросы обработки IOCTL(RamDskIOCtl), запросы Plug and Play (RamDskDispatchPnp), функции управления питанием (RamDskDispatchPower), запросы от подсистемы инструментария Windows (RamDskDispatchSystemControl).
Сам объект функционального устройства, который будет отвечать за наше устройство, создается далее, когда система вызовет процедуру RamDskAddDevice. Но для его создания необходимо узнать путь реестра с параметрами драйвера, а также необходим признак, что устройство добавлено. Данные параметры будем хранить в структуре расширения драйвера. Поля и размер структуры выбираются разработчиком, для хранения нужных ему данных. Объявим структуру расширения драйвера следующим образом:
typedef struct _RAMDSK_DRIVER_EXTENSION { UNICODE_STRING RegistryPath; ULONG DeviceInitialized; } RAMDSK_DRIVER_EXTENSION, *PRAMDSK_DRIVER_EXTENSION;
В процедуре DriverEntry мы создаем экземпляр структуры и инициализируем ее поля: копируем путь реестра RegistryPath, который передается в параметрах процедуры, и сбрасываем признак DeviceInitialized того, что устройство добавлено.
status = IoAllocateDriverObjectExtension(DriverObject, RAMDSK_DRIVER_EXTENSION_KEY, sizeof(RAMDSK_DRIVER_EXTENSION), &driverExtension); // Сохраним путь реестра driverExtension->RegistryPath.Length = RegistryPath->Length; driverExtension->RegistryPath.MaximumLength = RegistryPath->MaximumLength + sizeof(UNICODE_NULL); driverExtension->RegistryPath.Buffer = ExAllocatePoolWithTag(PagedPool, driverExtension->RegistryPath.MaximumLength, RAMDSK_TAG_GENERAL); RtlCopyUnicodeString( &(driverExtension->RegistryPath), RegistryPath); driverExtension->DeviceInitialized = FALSE;
Процедура RamDskAddDevice будет вызвана, когда будет обнаружено устройство. Ее действие заключается в создании объекта устройства диска и создании символических ссылок для него.
Проверим, может быть уже создан объект устройство, а процедура RamDskAddDevice вызывается повторно. Для этого проанализируем признак DeviceInitialized расширения драйвера:
// Может присутствоать только одно устройство, если повторно вызывается запрос
// AddDevice для следующего устройства, отклоним запрос
if ( driverExtension->DeviceInitialized == TRUE )
{
DBGPRINT( DBG_COMP_INIT, DBG_LEVEL_ERROR, ("Device exists\n") );
return STATUS_DEVICE_ALREADY_ATTACHED;
}
Если процедура RamDskAddDevice вызвана в первый раз, создаем объект устройство с типом дисковый накопитель (FILE_DEVICE_DISK) и инициализируем расширение устройства DEVICE_EXTENSION:
status = IoCreateDevice(
DriverObject,
sizeof(DEVICE_EXTENSION),
&uniDeviceName,
FILE_DEVICE_DISK ,
(FILE_DEVICE_SECURE_OPEN),
FALSE,
&functionDeviceObject);
Если создание прошло успешно, мы читаем параметры диска из реестра и устанавливаем начальное состояние устройства остановлено (STOPPED) в расширении устройства, а также устанавливаем флаги сигнализирующие о том, что мы используем прямой метод передачи данных (DO_DIRECT_IO) и процедуры драйвера находятся в страничной памяти(DO_POWER_PAGABLE):
RamDskQueryDiskRegParameters(&driverExtension->RegistryPath,&devExt->DiskRegInfo);
devExt->DevState = STOPPED; // состояние остановлен
// Установим флаги
functionDeviceObject->Flags |= DO_POWER_PAGABLE;
functionDeviceObject->Flags |= DO_DIRECT_IO;
// Резервируем память под образ диска
devExt->DiskImage =
ExAllocatePoolWithTag ( NonPagedPool,
devExt->DiskRegInfo.DiskSize,
RAMDSK_TAG_DISK ) ;
Последней операцией мы резервируем память в нестраничном пуле под образ диска(размер образа devExt->DiskRegInfo.DiskSize байт).
Т.к. выделенная память может содержать произвольные данные, необходимо записать метаданные файловой системы и подсчитать геометрию диска, что делается вызовом
RamDskFormatDisk( functionDeviceObject );
Создаем символическую ссылку, которая ассоциирует с нашим драйвером указанную в параметрах букву диска и имеет формат «\DosDevices\X:», где «Х» - буква диска.
RtlInitUnicodeString( &uniWin32Name, DOS_DEVICE_NAME );
devExt->SymbolicLink.MaximumLength = DOS_DEVNAME_LENGTH;
devExt->SymbolicLink.Length = uniWin32Name.Length;
RtlCopyUnicodeString( &(devExt->SymbolicLink), &uniWin32Name );
RtlAppendUnicodeStringToString(&(devExt->SymbolicLink),&(devExt->DiskRegInfo.DriveLetter));
// Создаем символьную ссылку в пространстве имен Win32
status = IoCreateSymbolicLink( &devExt->SymbolicLink, &uniDeviceName );
Если символическая ссылка создана без ошибок, то установим в расширении устройства флаг, что символическая ссылка создана и устройство подключается к стеку устройств:. При выгрузке драйвера если флаг установлен будет удаляться и символическая ссылка драйвера:
devExt->Flags |= FLAG_LINK_CREATED;
devExt->LowerDeviceObject = // подключение к стеку устройств
IoAttachDeviceToDeviceStack( functionDeviceObject, PhysicalDeviceObject );
// Сбросим флаг DO_DEVICE_INITIALIZING
functionDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
// устройство добавлено успешно
driverExtension->DeviceInitialized = TRUE;
Последней операцией устанавливается признак DeviceInitialized расширения драйвера, что устройство добавлено.
Когда пользовательское приложение или компонент ядра пытается открыть драйвер виртуального диска для записи или чтения, то диспетчер ввода вывода формирует запросы IRP с кодом IRP_MJ_CREATE. При закрытии обрабатывается запрос IRP_MJ_CLOSE. Особых действий в обработке этих пакетов не требуется, поэтому драйвер возвращает успешный статус завершения. В данном драйвере оба пакета обрабатываются в одной процедуре RamDskCreateClose:
switch ( irpStack->MajorFunction )
{
case IRP_MJ_CREATE:
DBGPRINT( DBG_COMP_INIT, DBG_LEVEL_INFO, ("IRP_MJ_CREATE (%p)\n", Irp) );
COMPLETE_REQUEST( Irp, status, 0 );
break;
case IRP_MJ_CLOSE:
DBGPRINT( DBG_COMP_INIT, DBG_LEVEL_INFO, ("IRP_MJ_CLOSE (%p)\n", Irp) );
COMPLETE_REQUEST( Irp, status, 0 );
break;
default:
status = STATUS_NOT_IMPLEMENTED;
COMPLETE_REQUEST( Irp, status, 0 );
ASSERTMSG("BUG: we should never get here", 0);
break;
} // switch
В качестве проверки, если мы получили пакет с другим кодом, то возвратим ошибочный статус.
Обработка запросов на запись или чтение , также реализуется в одной процедуре, т.к. отличия в действиях заключаются в направлении копирования данных: из буфера в образ диска или наоборот. Для выполнения запроса, требуется, чтобы виртуальный диск находился в состоянии WORKING:
if ( devExt->DevState != WORKING )
{
// Устройство не готово или удалено, отменить любые запросы
DBGPRINT( DBG_COMP_READ, DBG_LEVEL_WARN, ("Device not ready\n" ) );
status = STATUS_INVALID_DEVICE_STATE;
COMPLETE_REQUEST( Irp, status, information );
}
Далее требуется проверить, что переданные параметры(начальное смещение Parameters.Read.ByteOffset и количество байт Parameters.Read.Length) не выходят за границы нашего диска, чтобы обеспечить корректность операции чтения/записи. Дополнительно количество байт должно быть кратно размеру сектора на диске:
if (RtlLargeIntegerGreaterThan(
RtlLargeIntegerAdd(
irpStack->Parameters.Read.ByteOffset,
RtlConvertUlongToLargeInteger(irpStack->Parameters.Read.Length)),
RtlConvertUlongToLargeInteger(devExt->DiskRegInfo.DiskSize)) ||
(irpStack->Parameters.Read.Length & (devExt->DiskGeometry.BytesPerSector - 1)))
{
DBGPRINT( DBG_COMP_READ, DBG_LEVEL_ERROR,
(
"Error invalid parameter\n"
"ByteOffset: %x\n"
"Length: %d\n"
"Operation: %x\n",
irpStack->Parameters.Read.ByteOffset,
irpStack->Parameters.Read.Length,
irpStack->MajorFunction
));
status = STATUS_INVALID_PARAMETER;
COMPLETE_REQUEST( Irp, status, information );
IoReleaseRemoveLock(&devExt->RemoveLock, Irp);
return status;
}
Если какой-либо из параметров не верен, то статус обработки запроса будет равен STATUS_INVALID_PARAMETER (неверные параметры).
Драйвер использует прямой метод передачи буфера данных, нам передается MDL список для буфера пользователя в параметре Irp->MdlAddress, который мы отображаем в адресное пространство ядра с помощью функции MmGetSystemAddressForMdlSafe:
ASSERT ( Irp->MdlAddress != NULL );
currentAddress = MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);
if ( currentAddress == NULL )
{
status = STATUS_INSUFFICIENT_RESOURCES;
COMPLETE_REQUEST( Irp, status, information );
IoReleaseRemoveLock(&devExt->RemoveLock, Irp);
DBGPRINT( DBG_COMP_READ, DBG_LEVEL_ERROR, ("Unable to get the system-space virtual address\n" ) );
return status;}
Когда адрес получен адрес буфера (currentAddress), можно произвести копирование данных с помощью функции RtlMoveMemory
information = irpStack->Parameters.Read.Length;
switch (irpStack->MajorFunction)
{
case IRP_MJ_READ:
RtlMoveMemory(
currentAddress,
devExt->DiskImage + irpStack->Parameters.Read.ByteOffset.LowPart,
irpStack->Parameters.Read.Length);
break;
case IRP_MJ_WRITE:
RtlMoveMemory(
devExt->DiskImage + irpStack->Parameters.Read.ByteOffset.LowPart,
currentAddress, irpStack->Parameters.Read.Length);
break;
default:
information = 0;
break;
}
status = STATUS_SUCCESS;
COMPLETE_REQUEST( Irp, status, information );
При этом поле information содержит количество байт, которые были записаны/прочитаны.
Поскольку наш драйвер обращается с виртуальным диском, при следующих запросах выполнять каких-либо действий не требуется, просто сообщить, что запрос успешно обработан. Это запросы:
case IOCTL_DISK_IS_WRITABLE://проверка можно ли на диск записывать данные
{
DBGPRINT( DBG_COMP_IOCTL, DBG_LEVEL_INFO, ("IOCTL_DISK_IS_WRITABLE \n" ) );
status = STATUS_SUCCESS;
break;
}
case IOCTL_MOUNTMGR_QUERY_POINTS: // сообщить о символической ссылке для тома
{
DBGPRINT( DBG_COMP_IOCTL, DBG_LEVEL_INFO, ("IOCTL_MOUNTMGR_QUERY_POINTS\n" ) );
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
case IOCTL_DISK_FORMAT_TRACKS: //Форматировать дорожки
{
DBGPRINT( DBG_COMP_IOCTL, DBG_LEVEL_INFO, ("IOCTL_DISK_FORMAT_TRACKS\n" ) );
status = STATUS_SUCCESS ;
break;
}
case IOCTL_DISK_MEDIA_REMOVAL: //блокировать извлечение носителя
{
DBGPRINT( DBG_COMP_IOCTL, DBG_LEVEL_INFO, ("IOCTL_DISK_MEDIA_REMOVAL\n" ) );
status = STATUS_SUCCESS ;
break;
}
case IOCTL_DISK_VERIFY: //провреить данные
{
PVERIFY_INFORMATION verifyInformation;
DBGPRINT( DBG_COMP_IOCTL, DBG_LEVEL_INFO, ("IOCTL_DISK_VERIFY\n" ) );
status = STATUS_SUCCESS ;
break;
}
case IOCTL_DISK_CHECK_VERIFY:// проверить, сменился ли носитель
{
DBGPRINT( DBG_COMP_IOCTL, DBG_LEVEL_INFO, ("IOCTL_DISK_CHECK_VERIFY\n" ) );
status = STATUS_SUCCESS ;
break;
}
Запрос IOCTL_DISK_GET_PARTITION_INFO, требует сообщить информацию о разделах на диске. На рамдиске имеется один раздел. Результатом обработки запроса будет структура PARTITION_INFORMATION
typedef struct _PARTITION_INFORMATION
{
LARGE_INTEGER ; //смещение, с которого начинается раздел
LARGE_INTEGER ;//размер раздела
DWORD ; //скрытых секторов
DWORD ; //порядковый номер раздела
BYTE ; //тип раздела
BOOLEAN ; //TRUE - раздел является загрузочным
BOOLEAN ; //распознан ли раздел
BOOLEAN ; //TRUE – изменились параметры раздела
}
Тип раздела для виртуального диска может быть PARTITION_FAT_12 или PARTITION_FAT_16(он определяется при инициализации и хранится в расширении драйвера). Остальные поля заполняются следующим образом:
case IOCTL_DISK_GET_PARTITION_INFO: