举例说明,下面的一些功能号检查磁盘的有效性。现在一律返回有效,这个请求简单,不带参数。
case IOCTL_DISK_CHECK_VERIFY:
case IOCTL_CDROM_CHECK_VERIFY:
case IOCTL_STORAGE_CHECK_VERIFY:
case IOCTL_STORAGE_CHECK_VERIFY2:
{
status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
break;
}
下面这个请求获得磁盘的物理属性:
case IOCTL_DISK_GET_DRIVE_GEOMETRY:
case IOCTL_CDROM_GET_DRIVE_GEOMETRY:
{
PDISK_GEOMETRY disk_geometry;
ULONGLONG length;
if (io_stack->Parameters.DeviceIoControl.OutputBufferLength <
sizeof(DISK_GEOMETRY))
{
status = STATUS_BUFFER_TOO_SMALL;
Irp->IoStatus.Information = 0;
break;
}
disk_geometry = (PDISK_GEOMETRY) Irp->AssociatedIrp.SystemBuffer;
length = device_extension->file_information.AllocationSize.QuadPart;
disk_geometry->Cylinders.QuadPart = length / MM_MAXIMUM_DISK_IO_SIZE;
disk_geometry->MediaType = FixedMedia;
disk_geometry->TracksPerCylinder = MM_MAXIMUM_DISK_IO_SIZE / PAGE_SIZE;
disk_geometry->SectorsPerTrack = PAGE_SIZE / SECTOR_SIZE;
disk_geometry->BytesPerSector = SECTOR_SIZE;
status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(DISK_GEOMETRY);
break;
}
请注意你要把结果写入到一个DISK_GEOMETRY结构中并把这个结构返回到Irp->AssociatedIrp.SystemBuffer中。这个结构的定义如下:
typedef struct _DISK_GEOMETRY {
LARGE_INTEGER Cylinders; // 磁柱个数
MEDIA_TYPE MediaType; // 媒质类型
ULONG TracksPerCylinder; // 每个磁柱上的磁道数
ULONG SectorsPerTrack; // 每个磁道上的扇区数
ULONG BytesPerSector; // 每个扇区上的字节数
} DISK_GEOMETRY, *PDISK_GEOMETRY;
以上这个结构说明来自ddk文档。你必须"如实"的返回这些数据。
此外比较重要的是获取分区信息,获取分区信息有两个功能号,IOCTL_DISK_GET_PARTITION_INFO和IOCTL_DISK_GET_PARTITION_INFO_EX,其处理过程是类似的,主要是返回结果的结构不同,下面只举出一个例子:
case IOCTL_DISK_GET_PARTITION_INFO_EX:
{
PPARTITION_INFORMATION_EX partition_information_ex;
ULONGLONG length;
if (io_stack->Parameters.DeviceIoControl.OutputBufferLength <
sizeof(PARTITION_INFORMATION_EX))
{
status = STATUS_BUFFER_TOO_SMALL;
Irp->IoStatus.Information = 0;
break;
}
partition_information_ex = (PPARTITION_INFORMATION_EX) Irp->AssociatedIrp.SystemBuffer;
length = device_extension->file_information.AllocationSize.QuadPart;
partition_information_ex->PartitionStyle = PARTITION_STYLE_MBR;
partition_information_ex->StartingOffset.QuadPart = SECTOR_SIZE;
partition_information_ex->PartitionLength.QuadPart = length - SECTOR_SIZE;
partition_information_ex->PartitionNumber = 0;
partition_information_ex->RewritePartition = FALSE;
partition_information_ex->Mbr.PartitionType = 0;
partition_information_ex->Mbr.BootIndicator = FALSE;
partition_information_ex->Mbr.RecognizedPartition = FALSE;
partition_information_ex->Mbr.HiddenSectors = 1;
status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(PARTITION_INFORMATION_EX);
break;
}
还有其他的一些功能号。你可以查看FileDisk的代码来了解他们。
做为文件虚拟磁盘,这些数据都是虚拟的,所以根据需要返回就行了。无需动用下层设备。而Disk的例子则不同。这些请求都要发到下层设备来获得结果。
* * *
6.每个设备的处理线程
前面说到为每一个磁盘设备对象生成了一个系统线程,用来处理Irp。系统线程的生成用以下的调用:
// 生成一个系统线程
status = PsCreateSystemThread(
&thread_handle,
(ACCESS_MASK) 0L,
NULL,
NULL,
NULL,
FileDiskThread,
device_object
);
device_object是我所生成的磁盘设备对象。作为线程上下文传入。便于我们在线程中得到设备对象指针,然后得到设备扩展。
稍后我们要把线程对象的指针保留下来存到设备扩展中。这使用ObReferenceObjectByHandle来实现。
status = ObReferenceObjectByHandle(
thread_handle,
THREAD_ALL_ACCESS,
NULL,
KernelMode,
&device_extension->thread_pointer,
NULL
);
此后我们关注这个线程的运作。线程的启动函数是FileDiskThread.这个FileDisk实现了以下的功能:
1.获得设备扩展。
2.设置线程优先级。
3.进入死循环。首先等待事件的发生(device_extension->request_event),避免空循环消耗资源。
4.检查终止标志(device_extension->terminate_thread)。如果外部要求终止,就使用PsTerminateSystemThread(STATUS_SUCCESS)终止它。
5.检查请求链表(device_extension->list_head),若有请求,则完成他们。
读写请求的处理非常简单,如下:
switch (io_stack->MajorFunction)
{
case IRP_MJ_READ:
// 对于读,我直接读文件即可
ZwReadFile(
device_extension->file_handle,
NULL,
NULL,
NULL,
&irp->IoStatus,
MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority),
io_stack->Parameters.Read.Length,
&io_stack->Parameters.Read.ByteOffset,
NULL
);
break;
case IRP_MJ_WRITE:
// 写也是与之类似的。
if ((io_stack->Parameters.Write.ByteOffset.QuadPart +
io_stack->Parameters.Write.Length) >
device_extension->file_information.AllocationSize.QuadPart)
{
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
irp->IoStatus.Information = 0;
}
ZwWriteFile(
device_extension->file_handle,
NULL,
NULL,
NULL,
&irp->IoStatus,
MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority),
io_stack->Parameters.Write.Length,
&io_stack->Parameters.Write.ByteOffset,
NULL
);
break;
};
最后都用下面的代码完成这些请求:
IoCompleteRequest(
irp,
(CCHAR) (NT_SUCCESS(irp->IoStatus.Status) ?
IO_DISK_INCREMENT : IO_NO_INCREMENT)
);
这个线程最后在Unload中终止。Unload例程中调用了FileDiskDeleteDevice,这个函数的主要用途是删除生成的磁盘对象,并终止其处理线程。下面的代码终止处理线程并等待成功终止:
// 得到设备扩展
device_extension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
// 设置线程终止标志
device_extension->terminate_thread = TRUE;
// 设置启动事件
KeSetEvent(
&device_extension->request_event,
(KPRIORITY) 0,
FALSE
);
// 等待线程的结束
KeWaitForSingleObject(
device_extension->thread_pointer,
Executive,
KernelMode,
FALSE,
NULL
);
ObDereferenceObject(device_extension->thread_pointer);
使用IoDeleteDevice()最终将磁盘设备对象删除。
上面曾经提起的,每个虚拟磁盘设备对象打开一个真实的文件作为物理媒质。打开和关闭文件对象也是在处理线程中进行的。FileDisk的作者自定义了两个设备控制功能号,IOCTL_FILE_DISK_CLOSE_FILE和IOCTL_FILE_DISK_OPEN_FILE,很容易让人误解这是在硬盘上生成文件。但是开头我们已经说过生成文件是文件系统驱动所处理的任务,磁盘驱动是不会接受生成文件这样的请求的。这是两个自定义的功能号。
当收到打开文件的请求时,本驱动根据传入的路径使用ZwCreateFile()打开文件。关闭则反之。然后保留文件句柄在设备扩展中(device_extension->file_handle),然后以后处理读写请求就读写这个文件了。以此来实现用文件来虚拟磁盘空间。这部分操作和磁盘驱动无关,这里也不详细叙述了。
* * *
7.总结
ifs下的例子disk是一个磁盘类驱动。而FileDisk虽然是一个磁盘驱动,但是并不是一个类驱动。类驱动应该能得到PDO(发现总线上的物理设备),然后生成功能设备(FDO)去绑定它,并处理给这些设备的请求。存储类设备有一个复杂的框架,其基础代码在E:\WINDDK\2600\src\storage\class\下面。类设备有专用的分发函数,并把大部分请求转发给下层设备。
而FileDisk从上层到下层已经全部包办,因此其构造要简单得多。但是这也使我们更清楚的了解磁盘驱动的本质,在合适的时候生成合适的磁盘设备对象,并处理系统发来的请求。
FileDisk被作为虚拟磁盘的框架广泛应用。可以作为网络磁盘(不同于用文件系统驱动实现的网络卷),简单的存储设备驱动等的开发基础。(全文完)