磁盘的过滤
10.3 驱动分析
10.3.3 PnP 请求的处理
? 作为一个卷过滤驱动 PnP 请求处理是非常重要的,因为 Windows 操作系统在某些时刻会向存储设备发出专门的请求,如果没有进行正确的处理,将会造成系统无法正常关机等一系列问题。
? 在收到 PnP 请求之后,由于在 DriverEntry 中对 PnP 请求的处理函数特别设置成了 DPDispatchPnp 函数,所以这个函数将会被调用。它有两个参数: DeviceObject 和 irp,分别说明了这个请求发往的设备和这个请求的具体细节。由于这是过滤驱动的 PnP 分发函数,所以只有过滤驱动所建立的设备收到 PnP 请求时才会调用这个函数,那么我们应该很容易的想到,这个 DeviceObject 是过滤设备的设备对象。
//用来指向过滤设备的设备扩展的指针
PDP_FILTER_DEV_EXTENSION DevExt = DeviceObject->DeviceExtension;
//返回值
NTSTATUS ntStatus = STATUS_SUCCESS;
//用来指向 irp stack 的指针
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
? 在获取到了这些参数之后,可以通过判断 irp stack 中的 MinorFunction 来判断这个 IRP 请求的具体目的是什么。在 irp stack 中,通常会存在 MajorFunction 和 MinorFunction 两个请求号。前者为大请求号,一般是类似于 Write、Read、PnP、DeviceIoControl 等大分类的请求;而后者是小请求号,一般是在某一个大类中的子请求号。在这里,我们已知大请求号一定为 PnP,那么我们只需要关心小请求号。
? 第一个需要处理的 PnP 子请求号是设备移除请求,这个请求会在 Windows 进行设备热插拔、均衡、或者关机的时候被发送到磁盘卷设备。当然,过滤驱动设备会先于磁盘卷设备收到这个请求,在这个请求发送时,所有的磁盘卷设备的读写请求都应该已经完成,所以在过滤驱动收到这个请求的时候,只需要间的地将曾经建立过的所有设备和初始化过的所有内部数据结构全部销毁即可。建立过的设备主要是在 AddDevice 函数中建立的过滤设备和绑定而生成的下层设备,内部数据结构主要包括了下面将要介绍到的 bitmap 数据结构。此外,在 AddDevice 函数中为卷设备建立的请求处理线程也需要被停止。
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);switch(irpsp->MinorFunction) {case IRP_MN_REMOVE_DEVICE://如果是PnP manager发过来的移除设备的irp,将进入这里{//这里主要做一些清理工作if (DevExt->ThreadTermFlag != TRUE && NULL != DevExt->ThreadHandle){//如果线程还在运行的话需要停止它,这里通过设置线程停止运行的标志并且发送事件信息,让线程自己终止运行DevExt->ThreadTermFlag = TRUE;KeSetEvent(&DevExt->ReqEvent,(KPRIORITY) 0,FALSE);//等待线程结束KeWaitForSingleObject(DevExt->ThreadHandle,Executive,KernelMode,FALSE,NULL);//解除引用线程对象ObDereferenceObject(DevExt->ThreadHandle);}if (NULL != DevExt->Bitmap){//如果还有位图,就释放DPBitmapFree(DevExt->Bitmap);}if (NULL != DevExt->LowerDevObj){//如果存在着下层设备,就先去掉挂接IoDetachDevice(DevExt->LowerDevObj);}if (NULL != DevExt->FltDevObj){//如果存在过滤设备,就要删除它IoDeleteDevice(DevExt->FltDevObj);}break;}
? 第二个需要处理的请求是设备使用通告请求, Windows 操作系统会在建立或者删除特殊文件的时候向存储设备发出这个 IRP 请求,作为存储设备卷过滤设备自然也会收到这个请求。这里说的特殊文件通常包括页面文件、休眠文件和 dump 文件。Windows 会通过 irp stack 中的 ParametersUsageNotification.Type 域来说明请求的是哪种文件,并且会使用 ParametersUsageNotification.InPath 域来说明这个请求是在询问是否可以建立这个文件,还是删除了这个文件之后对这个设备的通知。在处理这个请求的时候,过滤驱动比较关心的是对页面文件的处理,因为这牵扯到过滤设备标志位中的 DO_POWER_PAGABLE 位。关于这个位,简单的说就是如果有页面文件在这个卷上,那么就应该清除该标志位。否则就加上这个位。
? 这个请求的根本目的是, Windows 系统用来查询是否可以在其上建立特殊文件,作为过滤驱动不该对这种请求做出回答的。正确的做法就是将该请求发送给下层设备。同时要监视下层设备的回答,如果下层设备不支持这个请求,过滤设备就什么都可以不做。如果下层设备支持这个请求,那么过滤设备就要做相应的处理。比如在下层设备对一个页面文件的建立请求回答是之后,过滤设备就要对 DO_POWER_PAGABLE 位进行相应的设置,并做一个计数,这个计数会随着页面文件建立的请求增加而增加,随折页面文件删除通知而减少,当减少到最后一个计数时,过滤设备又需要对 DO_POWER_PAGABLE 进行相应的设置。
//这个是PnP 管理器用来询问设备能否支持特殊文件的irp,作为卷的过滤驱动,我们必须处理case IRP_MN_DEVICE_USAGE_NOTIFICATION:{BOOLEAN setPagable;//如果是询问是否支持休眠文件和dump文件,则直接下发给下层设备去处理if (irpsp->Parameters.UsageNotification.Type != DeviceUsageTypePaging) {ntStatus = DPSendToNextDriver(DevExt->LowerDevObj,Irp);return ntStatus; }//这里等一下分页计数事件ntStatus = KeWaitForSingleObject(&DevExt->PagingPathCountEvent,Executive, KernelMode,FALSE, NULL);//setPagable初始化为假,是没有设置过DO_POWER_PAGABLE的意思setPagable = FALSE;if (!irpsp->Parameters.UsageNotification.InPath &&DevExt->PagingPathCount == 1 ) {//如果是PnP manager通知我们将要删去分页文件,且我们目前只剩下最后一个分页文件的时候会进入这里if (DeviceObject->Flags & DO_POWER_INRUSH){} else {//到这里说明没有分页文件在这个设备上了,需要设置DO_POWER_PAGABLE这一位了DeviceObject->Flags |= DO_POWER_PAGABLE;setPagable = TRUE;}}//到这里肯定是关于分页文件的是否可建立查询,或者是删除的通知,我们交给下层设备去做。这里需要用同步的方式给下层设备,也就是说要等待下层设备的返回ntStatus = DPForwardIrpSync(DevExt->LowerDevObj,Irp);if (NT_SUCCESS(ntStatus)) {//如果发给下层设备的请求成功了,说明下层设备支持这个操作,会执行到这里//在成功的条件下我们来改变我们自己的计数值,这样就能记录我们现在这个设备上到底有多少个分页文件IoAdjustPagingPathCount(&DevExt->PagingPathCount,irpsp->Parameters.UsageNotification.InPath);if (irpsp->Parameters.UsageNotification.InPath) {if (DevExt->PagingPathCount == 1) {//如果这个请求是一个建立分页文件的查询请求,并且下层设备支持这个请求,而且这是第一个在这个设备上的分页文件,那么我们需要清除DO_POWER_PAGABLE位DeviceObject->Flags &= ~DO_POWER_PAGABLE;}}}else {//到这里说明给下层设备发请求失败了,下层设备不支持这个请求,这时候我们需要把之前做过的操作还原if (setPagable == TRUE) {//根据setPagable变量的值来判断我们之前是否做过对DO_POWER_PAGABLE的设置,如果有的话就清楚这个设置DeviceObject->Flags &= ~DO_POWER_PAGABLE;setPagable = FALSE;}}//设置分页计数事件KeSetEvent(&DevExt->PagingPathCountEvent,IO_NO_INCREMENT, FALSE);//到这里我们就可以完成这个irp请求了IoCompleteRequest(Irp, IO_NO_INCREMENT);return ntStatus;}
? 这个请求基本属于每个磁盘必须的操作,没有什么自定义的空间。在需要的时候只需要查文档填入即可。相信在未来也会被框架化。在这里,对于其他的 PnP 请求,直接交给下层设备处理即可。
10.3.4 Power 请求的处理
? 本书在这里考虑了 Windows Vista 和 Windows XP 之间版本区别,对电源做了不同的处理。而电源请求也是向下层下发即可,在本书这里的学习意义不大。
明日计划
大概在15号交初稿以前,都以论文为重心。减少其他的学习时间,但是作为终身学习习惯的培养,还是要坚持下去。所以明日仍然为:
搞论文
驱动编程- bitmap 的作用和分析