`
gogohulo
  • 浏览: 27878 次
  • 性别: Icon_minigender_2
  • 来自: 济南
社区版块
存档分类
最新评论

MSDN Kernel-Mode Driver Architecture学习笔记(4)――Kernel-Mode Driver Components (3)

 
阅读更多

MSDN Kernel-Mode Driver Architecture学习笔记(4)――Kernel-Mode Driver Components (3)
2011年04月23日
  研读MSDN,边学边做同步翻译日志。暂时有些语义不好理解,待通读一遍后会好的。^_^ 生活雅趣Blog:http://blog.163.com/hook_ttg/
  
  
  
  
  
  
  
  随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
  处理任何I/O请求包(IRP)开始于一个派遣例程,这个派遣例程是驱动程序提供来处理IRP主功能代码的(IRP_MJ_Xxx)。驱动程序的DriverEntry例程输出派遣例程的入口点到一个驱动程序的DRIVER_OBJECT结构的派遣表中。
  驱动程序可以分别为每个要处理的主I/O功能代码提供派遣例程。然而,派遣例程也可以写成能处理多个I/O功能代码。 1)        派遣例程的功能
  一个特定的派遣例程的必需功能是不定的,取决于它要处理的I/O功能代码,还有个别驱动程序在驱动程序链中的位置,以及底层物理设备的类型。
  大多数派遣例程处理传入的I/O请求包(IRPs)如下:
  1.         检查在IRP中的驱动程序I/O栈存储单元来确定什么要做和检查参数的有效性,如果有的话。
  是否要执行上述操作取决定于给定的IRP_MJ_XXX,还有驱动程序是否为每个IRP_MJ_Xxx提供了独立的派遣例程。
  2.         满足这个请求并完成这个IRP,如果可能的话;否则,传递此IRP到低一层的驱动程序或者其他设备驱动例程来进一步处理。
  是否执行上述操作取决于参数的有效性,如果有的话,还取决于IRP_MJ_XXX和驱动程序在分层的驱动程序链中的层次,如果有的话。
  2)        必需的派遣例程
  大多数驱动程序必需处理下面的派遣例程:
  l  DispatchPnP
  IRP_MJ_PNP请求,涉及PnP设备识别、硬件配置或者资源分配。这样的请求通常来自PnP管理器或者紧密连接的高层驱动程序。
  l  DispatchPower
  IRP_MJ_POWER请求,与设备或者系统的电源状态相关。这样的请求通常由电源管理器或者紧密连接的高层驱动程序发送给设备驱动程序。
  l  DispatchCreate
  IRP_MJ_Create指示了,即可能是一个用户模式保护子系统,也许代表一个应用程序或者子系统特有的驱动程序,请求处理目标设备对象的文件对象;也可能是一个高层驱动程序正在连接或者附加其设备对象到目标设备对象。
  l  DispatchCLose
  IRP_MJ_CLOSE指示了最后处理的关联目标设备对象的文件对象已经被关闭和释放。所有的I/O请求已经被处理完成或者被取消,也就是说再没有为解决的关于这个文件对象指针的引用。
  l  DispatchRead
  IRP_MJ_READ指示了一个从底层物理设备传送数据到系统的I/O请求。
  l  DispatchWrite
  IRP_MJ_WRITE指示了一个从系统传送数据到底层物理设备的I/O请求。
  l  DIspatchDeviceControl
  IRP_MJ_DEVICE_CONTROL指示了一个请求,包含一个系统定义的、设备类型特有的I/O控制代码,指定一个设备类型特有的操作。高层驱动程序传递这些IRPs到它们的底层设备驱动程序,这些底层设备驱动程序通常通过访问设备来处理这些请求。
  l  DispatchInternalDeivceControl
  IRP_MJ_INTERNAL_DEVICE_CONTROL指示了一个发送到设备驱动程序的请求,大多数情况下来自一个紧密连接的高层驱动程序,通常具有一个非公开定义的、驱动程序特有的和设备类型特有的或者设备特有的I/O控制代码,请求一个设备类型特有或者设备特有的操作。
  只有某些类型的驱动程序需要处理系统定义的内部设备I/O控制请求,包括某些SCSI驱动程序、键盘或者鼠标设备驱动程序,和与系统提供的驱动程序交互的并行驱动程序。
  l  DispatchSystemControl
  IRP_MJ_SYSTEM_CONTROL被用来描述驱动程序的WMI请求。 驱动程序必须根据底层物理设备的类型和功能提供不同的派遣例程。关于驱动程序必须处理的IRP主功能代码的设备类型指定信息,参看WDK中的设备类型特定文档。 3)        可选的派遣例程
  l  DispatchCleanup
  IRP_MJ_CLEANUP指示了,最后处理的关联目标设备对象的一个文件对象正在被关闭。此时还存在没有处理的对于文件对象的I/O请求。驱动程序可以实现一个DispatchCleanup例程来执行对任意特定的文件句柄的清理,也不仅限于清理操作。驱动程序也可以使用DispatchClose例程来达到同样目的。
  l  DispatchQueryInformation和DispatchSetInformation
  某些顶层驱动程序可能需要处理IRP_MJ_QUERY_INFORMATION和IRP_MJ_SET_INFORMATION这些IRPs。这些请求表示了一个用户模式应用程序、内核模式组件或者驱动程序为具有一个句柄的用户模式请求者已经请求的关于文件对象的长度信息,或者用户模式请求者正尝试在文件对象上设置一个文件结束标记。
  并行类型和串口设备驱动程序处理这些请求,通过设置FILE_STANDARD_INFORMATION或者FILE_POSITION_INFORMATION长度或者位置为零。其他顶层设备驱动程序应该支持这些请求,特别是如果一个用户模式应用程序或者内核模式驱动程序可能会调用C运行时功能来维护这个文件对象。文件系统驱动程序必须比这些顶层设备驱动程序更加全面支持这些请求。
  l  DispatchFlushBuffers
  驱动程序要高速缓存设备中的数据或者缓冲在驱动程序分配的内存的内部数据,应该接收IRP_MJ_FLUSH_BUFFERS。接收到这个请求就表示驱动程序应该向设备写入其缓冲的数据或者从设备中清除器高速缓存的数据,或者应该丢弃缓冲的或者高速缓存的从设备读取到的数据。
  例如,系统键盘和鼠标类型驱动程序,具有用于来自设备输入数据的内环缓冲区,支持flush(清除)请求。大容量存储设备的驱动程序和比其更高层的驱动程序也支持这个请求。
  l  DispatchShutdown
  任何驱动程序想在系统关机前被调用就必须处理IRP_MJ_SHUTDOWN。DIspatchShutDown例程应该执行必要驱动程确定的清理,在电源管理器发送一个系统设置电源的IRP来关闭系统之前。驱动程序可以调用IoRegisterShutdownNotification或者IoRegisterLastChanceShutdownNotification来注册关闭通知。 大容量存储设备的驱动程序和层叠在他们之上的中间层驱动程序可以依赖一个顶层文件系统驱动程序来发送他们的关闭IRPs,在系统即将关闭的时候。那是因为,FSD必须确保任何高速缓存的文件数据被写入到外围设备,并调用底层驱动程序来清除来自其设备的高速缓存或者缓冲的数据(如果有的话),等等在系统关闭之前完成。
  大容量存储设备的驱动程序要在内部高速缓存数据的话,必须提供DispatchShutdown和DispatchFlushBuffers例程。如果大容量存储驱动程序要在内存中缓冲数据但是其设备没有内部高速缓冲器,那么它也必须提供DispatchShutdown和DispatchFlushBuffers例程。
  任何层叠在处理IRP_MJ_FLUSH和IRP_MJ_SHUTDOWN请求的驱动程序之上的中间层驱动程序也必须提供DispatchShutdown和DispatchFlushBuffers例程。 4)        派遣例程和IRQLs
  大多数驱动程序的派遣例程是在一个任意的具有IROL=PASSIVE_LEVEL级别的线程上下文中被调用的,下面的除外:
  l  任何顶层驱动程序的派遣例程在发起I/O请求的线程上下文中被调用,那么他就是一个一般的用户模式应用程序线程。
  换句话说,文件系统驱动程序和其他顶层驱动程序的派遣例程在一个并非具有IRQL=PASSIVE_LEVEL级别的任意线程上下文中被调用。
  l  底层设备驱动程序或者在系统分页路径中层叠其上的中间层驱动程序的DispatchRead、DIspatchWrite和DispatchDeviceControl例程,可以在具有IRQL=APC_LEVEL的任意线程上下文中被调用。
  DispatchRead和/或者DispatchWrite例程,和在这样一个底层设备或者中间层驱动程序中的任何其他处理读取和/或者写入请求的例程,必须在任何时刻都常驻。这些驱动程序例程即不是可分页的也不是驱动程序的可分页镜像区的一部分,它们绝对不能访问任何分页的内存。另外,它们也不能从属于任何阻塞调用(比如带有一个非零超时的KeWaitForSingleObject)。
  l  在休止和/或者分页路径中的驱动程序的DispatchPower例程可以在IRQL=DISPATCH_LEVEL级别被调用。这些驱动程序的DispatchPnP例程必须准备好处理PnP IRP_MN_DEVICE_USAGE_NOTIFICATION请求的准备。
  l  在启动是需要大电流的驱动程序的DispatchPower例程可以在IRQL=DISPATCH_LEVEL级别被调用。
  5)        何时检查驱动程序的I/O栈存储单元
  为每个传入的IRP都会在驱动程序的I/O栈存储单元中设置一个主I/O功能代码。
  驱动程序的派遣例程必须检查驱动程序的用于IRP的I/O栈存储单元来确定要做什么,如果具有下面的一些条件:
  l  这个派遣例程处理多于一个的主I/O功能代码。
  l  这个派遣例程必须为某些主功能代码处理一组次一级的功能代码。带有次一级功能代码的IRPs有IRP_MJ_PNP和IRP_MJ_POWER,还有SCSI端口驱动程序和文件系统驱动程序的某些IRPs必须要处理。
  l  一个设备的驱动程序的或者与其紧密相连的高层驱动程序的派遣例程处理IRP_MJ_DEVICE_CONTROL或者IRP_MJ_INTERNAL_DEVICE_CONTROL请求,这些请求含有相关的一套I/O控制代码。 为了获取一个驱动程序的I/O栈存储单元指针,其派遣例程可以调用IoGetCurrentIrpStackLocation。
  高层驱动程序的派遣例程总是调用IoGetCurrentIrpStackLocation和IoGetNextIrpStackLocation来获取下一层驱动程序的I/O栈存储单元指针,为了它们为下一层驱动程序建立的IRPs,在向下传递IRPs给驱动程序栈时。
  一个设备驱动程序的DispatchDeviceControl或者DispatchInternalDeviceControl例程,或者它的可能的紧密相连的类型驱动程序,必须确定每个请求的那个I/O控制代码被设置在驱动程序的I/O栈存储单元的Parameters.DeviceIoControl.IoControlCode中。I/O控制代码包含在驱动程序的I/O栈存储单元中。
  在大多数情况下,一个高层驱动程序的DispatchDeviceControl或者DIspatchInternalDeviceControl例程简单的传递一个IRP_MJ_DEVICE_CONTROL或者IRP_MJ_INTERNAL_DEVICE_CONTROL请求到下一层的驱动程序,在建立其在IRP中的栈存储单元之后。然而,SCSI类型驱动程序必须检查某些SCSI端口I/O控制代码,这样做是为了在传递这些请求之前能正确的建立SCSI端口驱动程序的I/O栈存储单元。 6)        DispatchCreate、DispatchClose和DispatchCreateClose例程
  驱动程序的DispatchCreate和DispatchClose例程分别处理带有IRP_MJ_CREATE和IRP_MJ_CLOSE的I/O功能代码的IRPs。另外,一个合并的DIspatchCreateClose例程可以处理所有这些I/O功能代码的IRPs。
  一个创建请求可以发起自一个用户模式子系统尝试获取代表一个设备的文件对象(可能是一个应用程序或者子系统级的驱动程序),也可以在一个高层驱动程序调用IoGetDeviceObjectPointer或者IoAttachDevice中。
  一个对等的关闭请求来自一个用户模式子系统的关联驱动程序的设备对象的文件对象句柄的关闭。
  这些请求的每一个天生就是同步的。 分开的DispatchCreate和DispatchClose例程
  驱动程序用于IRP_MJ_CREATE和IRP_MJ_CLOSE请求的派遣例程可以什么都不做而仅仅使用STATUS_SUCCESS完成输入的IRP。
  另一些驱动程序用于IRP_MJ_CREATE和IRP_MJ_CLOSE请求的派遣例程可以做很多工作,取决于底层设备驱动程序或者底层设备。考虑下列场景: l  在收到一个创建请求之后,一个类型驱动程序可以初始化一个内部队列,并向下发送一个IRP_MJ_INTERNAL_DEVICE_CONTROL请求到相对应的端口驱动程序来请求设备配置信息或者独占访问一个控制器端口。
  l  收到IRP_MJ_CLOSE就表示最后引用的关联目标设备对象的文件对象已经被移除了。这就意味着文件对象的所有句柄都被关闭,并且所有未解决的I/O请求都被完成或者取消。
  l  在收到一个创建请求之后,一个很少使用的设备的一个驱动程序可以调用MmLockPagableCodeSection来使得一些处理其他IRP_MJ_XXX请求的驱动程序例程常驻。在收到一个对等的关闭请求后,此时用于这样一个驱动程序设备对象的所有文件对象都被关闭,驱动程序可以调用MmUnlockPagableImageSection来通过将其可分页镜像区移出分页而保全系统内存。 一些驱动程序处理IRP_MJ_CLOSE请求仅仅是为了对称的原因,在他们的设备对象被一个保护的子系统或者高层驱动程序打开之后,低层驱动程序的设备对象将不能被关闭,直到系统自己被关闭。例如,键盘和鼠标驱动程序建立设备对象来代表物理设备,并且在系统运行时这个设备对象必须起作用,所以这些驱动程序可以有个最小的DispatchClose例程用于对称,或者他们可以有个合并的DispatchCreateClose例程。
  如果被底层驱动程序控制的设备是系统持续运行所必需的,那么这个驱动程序的DispatchClose例程通常是不会被调用的。例如,一些系统磁盘驱动程序就没有DispatchClose例程,但是这些驱动程序一般都有DispatchFlushBuffer和DispatchShutdown例程,以便在系统关闭之前完成为解决的文件I/O操作。
  虽然你可以实现独立的DispatchCreate和DispatchClose例程,但是驱动程序有时可以只有一个单独的DispatchCreateClose例程来同时处理创建和关闭请求。
  一个单独的DispatchCreateClose例程
  许多驱动程序,特别是在分层的驱动程序链中较低层驱动程序,仅仅需要在接受到一个创建请求后创建他们的存在物和仅仅需要确认接收到一个关闭请求即可。
  例如,用于设备控制器的端口驱动程序,具有一个或者多个紧密关联的类型驱动程序,调用了IoGetDeviceObjectPointer,可以有一个最小化的DispatchCreateClose例程。这个例程可以什么都不做仅仅是完成IRP,如下:
  : : { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }    这个最小化的DispatchCreateClose例程设置I/O状态块的Information成员为零,表示这个文件对象因一个打开请求而被打开;Information对于关闭请求没有意义。例程设置Status成员为STATUS_SUCCESS并且也返回这个状态值,表示驱动程序已经接受I/O请求。
  这个最小化的DispatchCreateClose例程完成这个创建IRP且没有提高IRP发起者的优先级。因为这个发起者被假想成对于这个请求的完成而等待一个不定的但是非常小的时间间隔。
  一个DispatchCreateClose例程做多少工作一部分取决于驱动程序的设备或者底层设备的特性,另一部分取决于驱动程序的设计。如果一个驱动程序对创建和关闭请求执行非常不同的操作,那么它应该处理这些请求在分开的DispatchCreate和DispatchClose例程。
  为了处理一个创建请求,对于打开代表一个逻辑或者物理设备的文件对象,顶层驱动程序应该做如下操作: 1.         调用IoGetCurrentStackLocation来获取其I/O栈存储单元在IRP中的指针。
  2.         检查在I/O栈存储单元中的FileObject.FileName,如果FileName中的Unicode字符串长度为0,则以STATUS_SUCCESS来完成IRP;否则,以STATUS_INVALID_PARAMETER完成这个IRP。 按照上述步骤,可以确保不会尝试打开一个设备上的一个虚假文件,否则随后会导致很多问题。例如,这个会预防尝试打开一个不存在的\\Device\Parallel0\temp.dat。
  实现DispatchCreate、DispatchClose和DispatchCreateClose例程的规则
  在设计所说的三个例程时一定要牢记下面几点: l  至少,例程必须做到下列几点:
  1.         设置传入的IRP的I/O状态块的Status域,使用一个合适的NTSTATUS,通常是STATUS_SUCCESS。
  2.         设置传入的IRP的I/O状态块的Information域为0。
  3.         调用IoCompleteRequest,使用这个IRP和取值为IO_NO_INCREMENT的PriorityBoost参数。
  4.         返回其设置在IRP的I/O状态块的Status域的NTSTATUS值。
  l  在一个顶层或者中间层驱动程序中,例程可能不得不做一些额外工作来处理一个创建或者关闭请求,这取决于其设备或者底层设备的特性,以及驱动程序的设计。
  l  对于一个代表一个逻辑或者物理设备的文件对象的打开请求,顶层驱动程序应该检查在I/O栈存储单元中的FileObject.FileName,并且以STATUS_SUCCESS完成这个IRP,如果FileName的Unicode字符串的长度为0。否则,应该以STATUS_INVALID_PARAMETER完成这个IRP。
  l  底层驱动程序的例程将被调用,仅在紧邻的上一层驱动程序调用IoAttachDeviceToDeviceStack、IoGetDeviceObjectPointer或者IoAttachDevice。在分层的驱动程序链的最底层驱动程序通常仅做最小化的创建或者关闭请求处理。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics