Windows磁盘驱动基础教程【二】

 有非常多的固有设备控制功能码要处理。这些请求都有参数,从输入空间得到参数后,把返回结果写到输出空间。无论什么请求,其输入和输出空间都是Irp->AssociatedIrp.SystemBuffer,但是每种请求的参数和返回都有自己的格式,需要一一阅读文档才行。

     举例说明,下面的一些功能号检查磁盘的有效性。现在一律返回有效,这个请求简单,不带参数。

     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被作为虚拟磁盘的框架广泛应用。可以作为网络磁盘(不同于用文件系统驱动实现的网络卷),简单的存储设备驱动等的开发基础。(全文完)

你可能感兴趣的:(windows)