MSDN Kernel-Mode Driver Architecture学习笔记(4)——Kernel-Mode Driver Components (5)

随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang) 

 

9)        DispatchDeviceControlDispatchInternalDeviceControl例程

一个驱动程序的DispatchDeviceControlDispatchInternalDeviceControl例程分别处理IRP_MJ_DEVICE_CONTROLIRP_MJ_INTERNAL_DEVICE_CONTROL I/O功能代码的IRPs

对于每个普通类型的外围设备,系统为IRP_MJ_DEVICE_CONTROL请求定义了一套I/O控制代码。每个设备类型的新驱动程序必须指出这些请求。在大多情况下,这些用于每个设备类型的公共I/O控制代码是不对用户模式应用程序开放的。

这些系统定义的I/O控制代码中的一些由较高层驱动程序使用来创建用于底层设备驱动程序的IRPs,通过调用IoBuildDeviceIoControlRequest。其他一些被Win32组件使用来与底层设备驱动程序通讯,通过调用Win32函数DeviceIoControl轮流调用系统服务。I/O管理器建立一个IRP,并且保存主功能代码IRP_MJ_DEVICE_CONTROL和给定的I/O控制代码到IO栈存储单元结构,位于Parameters.DeviceIoControl.IoControlCode。随后,I/O管理器调用链路中顶层驱动程序的DispatchDeviceControl例程。

对于某些系统提供的驱动程序是为了交互操作而设计的并且支持新的驱动程序,操作系统也定义了一套用于IRP_MJ_INTERNAL_DEVICE_CONTROL请求的I/O控制代码。在大多数情况下,这些公共I/O控制代码允许附加的较高层驱动程序与一个底层设备驱动程序交互操作。

举个例子,系统提供的并行驱动程序支持一套内部的I/O控制代码,这些代码由厂家提供的驱动程序在IRP_MJ_INTERNAL_DEVICE_CONTROL中发送。

几乎所有通过系统定义I/O控制代码请求的操作都使用缓冲I/O,因为这个类型的请求很少需要传输很大数量的数据。换句话说,甚至建立用于直接I/O的设备对象的驱动程序也会被发送一些数据传入或者传出位于Irp->AssociatedIrp.SystemBuffer缓冲区的设备控制请求IRPs(除了某些类型的与Win32通讯驱动程序紧密连接的顶层设备驱动程序)。

另外,驱动程序可以定义一套私有I/O控制代码,其他驱动程序可以使用它们来通讯。新的公共I/O控制代码只有在与微软公司合作下才能被添加到系统,因为公共I/O控制代码是操作系统自身内建的。

1.         底层驱动程序中的DispatchDeviceControl

用于底层驱动程序的IRP_MJ_DEVICE_CONTROL请求要求驱动程序要么改变它的设备状态要么提供它的设备的状态信息。由于大多数类型的驱动程序需要处理许多I/O控制代码,因此它们的DispatchDeviceControl例程通常包含一个Switch语句,就像下面这样:

 

据上代码所示,DIspatchDeviceControl例程也会检查参数,有时是驱动程序必须支持的每个I/O控制代码,有时是一组这些I/O控制代码。

考虑如下实现指导方针,适用于设备驱动程序的DispatchDeviceControl例程:

l  DispatchDeviceControl必须检查检出的正确性,并且立即使用参数错误来完成IRPs

l  Case语句中分组I/O控制代码,就驱动程序性能和大小以及代码维护而言,在测试有效参数的时候都是很简练的。就像上面代码片段建议的那样,I/O控制代码要使用一个公共的结构,很自然的就选择了这样一个case分组。

l  第一个接入的I/O控制代码可以改善DispatchDeviceControl例程满足和完成IRP的性能,因为驱动程序可以很快的返回控制。

l  最后一个接入的I/O控制代码,说明是不常用的请求操作,也可以改善驱动程序处理IRP_MJ_DEVICE_CONTROL_REQUESTS的性能。

l  为了更好的性能,每个底层设备驱动程序的DispatchDeviceControl例程应该尽可能满足任何设备控制请求,除了将IRP排队到其他驱动程序例程。

如果DisptachDeviceControl例程可以完成这个IRP,那么它就应该调用IoCompleteRequestPriortyBoost参数为IO_NO_INCREMENT。如果DispatchDeviceControl例程必须排队IRP以求进一步处理,那么它必须调用IoMarkIrpPending,并且返回STATUS_PENDING

2.         高层驱动程序中的DispatchDeviceControl

通常,高层驱动程序的DispatchDeviceControl例程只是简单的为下一层驱动程序建立I/O栈存储单元,并传递IRPIoCallDriverDispatchDeviceControl例程很少检查输入IRP中参数的有效性,因为假定底层设备驱动程序有更好的有关如何处理每个设备类型指定的I/O控制请求的信息。

这个一般规则的一个可能例外就是类型/端口驱动程序对的类型驱动程序的DispatchDeviceControl

任何新的不与一个特定设备驱动程序紧密相关的高层驱动程序,应该简单的为下一层驱动程序建立I/O栈存储单元,并且传递IRP_MJ_DEVICE_CONTROL请求以求更进一步处理。

一个设备的控制请求通常是同步处理的。也就是说,一个高层驱动程序的DispatchDeviceCOntrol例程可以时常按如下方式向系统返回控制:

 

然而,如果一个较低层驱动程序为这样一个请求返回STATUS_PENDING,那么一个高层驱动程序就不能使用前述的技术。在这种情况下,高层驱动程序应该调用IoSetCompletionRoutine来注册一个IoCompletion例程。在IoCompletion例程被调用的时候,它可以检查I/O状态块来确定IRP是否仍然处于pending(挂起)状态。如果是,那么IoCompletion例程可以重试这个请求,或者,可能,在它调用IoCompleteRequest之前调用IoMarkIrpPending,并返回STATUS_PENDING。高层驱动程序不必使用STATUS_PENDING来完成一个IRP,除非它首次为那个IRP调用IoMarkIrpPending

如果底层设备驱动程序必须在它完成请求之前处理来自设备的大量数据传输,那么高层驱动程序可能异步处理这样一个设备控制请求。换句话说,高层驱动程序可能调用IoSetCompletionRoutine来注册一个IoCompletion例程,向底层驱动程序传递IRP,并且从它的DispatchDeviceControl例程返回控制。

几乎所有系统定义的I/O控制代码要求底层设备驱动程序传输只是适当数量的数据,通常少于PAGE_SIZE总额。一般来说,高层驱动程序应该同步处理这些请求,就像上面所示的代码段那样,因为底层驱动程序返回控制是非常快的。换句话说,调用高层驱动程序的IoCompletion例程的开销不能补偿任何额外的IRP处理,驱动程序可以在这样短的间隔完成。

使用IoBuildDeviceIoControlRequest为一个底层设备驱动程序分配IRPs的高层驱动程序可以异步处理这些设备控制请求。较高层驱动程序可以等待一个可选的事件对象,这个事件对象对传递到IoBuildDeviceIoControlRequest并且与驱动分配的IRP相关联。

3.         类型/端口驱动程序中的Dispatch(Internal)DeviceControl

一个类/端口对的较高层驱动程序有时在其DispatchDeviceControl例程中完成IRPs。例如,一个类驱动程序可能,在初始化过程中,搜集和存储有关底层设备的特征信息,在随后的IRP_MJ_DEVICE_CONTROL请求中将搜寻这些信息,从而经由不用传递到底层设备驱动程序的请求来达到节省处理时间。一个类驱动程序可能也被设计来检查IRP的参数和发送只有有效参数的请求到端口驱动程序。

紧密联系的类/端口驱动程序也可以定义一套驱动特定的内部I/O控制代码,类驱动程序可以用于到端口驱动程序的IRP_MJ_INTERNAL_DEVICE_CONTROL请求。

例如,在系统键盘和鼠标类驱动程序的DispatchCreateClose例程中发送系统定义的内部设备控制请求来使能或禁止键盘和鼠标中断到底层端口驱动程序。这些系统类驱动程序建立IRP_MJ_INTERNAL_DEVICE_CONTROL请求用于底层端口驱动程序。一些新的与这些系统类驱动程序交互操作的键盘或者鼠标端口驱动程序必须提供这些公共的内部设备控制请求。

系统并行类/端口驱动程序模型具有类似的特征。新的并行类驱动程序可以从系统并行端口驱动程序获取支持,通过建立用于带有公共IOCTL_PARALLEL_PORT_XXX控制代码的IRP_MJ_INTERNAL_DEVICE_CONTROL请求。可以替换系统并行端口驱动程序,但是任何一个新的驱动程序也必须提供这样一套公共的内部设备控制请求。

对于紧密联系的端口/类驱动程序,类驱动程序可能操作某些设备控制请求处理而不传递它们到端口驱动程序。在一个新的类/端口驱动程序对中,类驱动程序的DispatchDeviceControl例程可以下面任意一项:

l  检查在其拥有的I/O栈存储单元中参数的有效性,如果发现任何参数错误就设置I/O状态块,并调用IoCompleteRequest且设置参数PriortiyBoost值为IO_NO_INCREMENT;否则,调用IoGetNextIrpStackLocation复制其拥有的I/O栈存储单元到端口驱动程序的I/O栈存储单元中,并且与IoCallDriver一起传递IRP

l  或者,什么都不做,只在IRP中建立端口驱动程序的I/O栈存储单元而不检查参数,并且传递其到端口驱动程序以求处理。

SCSI类驱动程序对于处理设备控制请求有特定的要求。

4.         编写Dispatch(Internal)DeviceControl的指导方针

l  在最小限度,一个较高层驱动程序必须从其拥有的在IRP中的I/O栈存储单元终复制用于IRP_MJ_DEVICE_CONTROL或者IRP_INTERNAL_DEVICE_CONTROL请求的参数到下一个较低层驱动程序的I/O栈存储单元。然后,还必须使用一个指向下一个较低层驱动程序的设备对象和IRP指针来调用IoCallDriver

较高层驱动程序应该传送IoCallDriver返回的状态值,或者在其为一个较低驱动程序异步处理的请求返回控制时,在返回的IRPI/O状态块中设置状态值。

l  底层设备驱动程序必须处理设备控制请求,除非其有一个紧密联系的类驱动程序,这个类驱动程序完成这些请求的一个子集。设备驱动程序的DispatchDeviceControl例程通常通过打开在每个IRPI/O栈存储单元中的Parameters.DeviceIoControl.IoControlCode来开始处理这些请求。

l  较低层设备驱动程序应该检查传入请求的参数,并且如果有参数是无效的,那么就使用一个合适的错误来忽略这个IRP。大多数常见的检查这些请求参数的有效性有如下格式:

 

那里的状态值集是STATUS_BUFFER_TOO_SMALL或者STATUS_INVALID_PARAMETER之一。

l  每个设备驱动程序的DispatchDeviceControl或者DispatchInternalDeviceControl例程必须通过使用一个合适的NTSTATUS值设置I/O状态块来处理接收到一个未识别的I/O控制代码,设置其Information域为0,并且使用一个值为IO_NO_INCREMENTPriorityBoost来完成这个IRP

l  特定I/O控制代码的设备驱动程序处理必须包含任何设备类型特定的、用于相同类型设备的系统定义的I/O控制代码。查看WDK中的设备特定章节中更多信息,关于对于每个设备类型和相应头文件的系统需求,每个都是ntdd前缀开始,用于这些I/O控制代码的系统定义的结构声明。

l  紧密联系的类/端口驱动程序对的类驱动程序可以处理和完成一个设备控制请求子集,而不传递它们到底层端口驱动程序。然而,这样一个类驱动程序必须传递所有有效设备控制请求,要求一个用于设备的状态改变并且要求返回有关设备的不确定信息,比如当前的波特率,卷,或者视频模式。

10)        DIspatchPnp例程

驱动程序的DispatchPnp例程通过处理IRP_MJ_PNP I/O功能代码来支持即插即用。与IRP_MJ_PNP功能代码相关联的是许多次要的I/O功能代码,这些功能代码中的一些是所有驱动程序必须处理的,一些是可选择处理的。PnP管理器使用这些次要功能代码来指挥驱动程序启动、停止和移除设备以及查询关于他们设备的驱动程序。

设备的所有驱动程序必须有条件去处理用于设备的PnP IRPs,除非在功能或者过滤驱动程序被允许忽略这个IRP此类少数情况。

每个驱动程序的DispatchPnP例程必须依据这些规则:

l  功能或者过滤驱动程序必须向下传递PnP IRPs到设备栈中的下一个驱动程序,除非功能或者过滤驱动程序处理这个IRP并且遇到一个错误(比如资源不足)。

所有用于设备的驱动程序必须有条件处理PnP IRPs,除非这些驱动程序中的某一个遇到一个错误。PnP管理器发送IRPs到设备栈中的顶部驱动程序。功能和过滤驱动程序向下传递IRP到下一个驱动程序,并且父总线驱动程序完成这个IRP.

驱动程序可以忽略一个IRP,如果它尝试处理IRP并且遇到一个错误(比如资源不足)。如果驱动程序接收到一个带有其不能处理的代码的IRP,驱动程序则不能忽略这个IRP。并且它必须对IRP的状态不做修改的向下传递这样一个IRP到下一个驱动程序。

l  驱动程序必须处理某些PnP IRPs并且可以选择处理其他IRPs

每个PnP驱动程序都要求处理某些IRPs,比如IRP_MN_REMOVE_DEVICE,并且可以选择地处理其他IRPs

驱动程序可以使用一个合适的错误状态来忽略一个必须的PnP IRP,但是驱动程序不能为这样一个IRP返回一个STATUS_NOT_SUPORTED

l  如果驱动程序处理成功的处理了一个PnP IRP,驱动程序设置IRP状态为成功。它不依赖于在栈中的另一个驱动程序来设置这个状态。

驱动程序设置Irp->IoStatus.StatusSTATUS_SUCCESS来通知PnP管理器这个驱动程序处理IRP成功了。对于有些IRPs,一个非总线驱动程序可能会依赖其父总线驱动程序来设置状态值为成功。然而,这是一个危险的实践。出于一致性和健壮性考虑,驱动程序必须为每个它成功处理的PnP IRP设置IRP 状态为成功。

l  如果驱动程序忽略了一个IRP,驱动程序使用一个错误状态值来完成这个IRP,并且不再向下一个驱动程序传递这个IRP

为了忽略一个类似IRP_MN_QUERY_STOPIRP,驱动程序可以设置Irp-IoStatus.StatusSTATUS_SUCCESSFUL。额外的错误状态值用于其它IRPs,包括STATUS_INSUFFICIENT_RESOURCESSTATUS_INVALID_DEVICE_STATE

驱动程序不为他们处理的IRP设置STATUS_NOT_SUPPORTED。这个是由PnP管理器设置的最初状态。如果一个IRP被以这个状态完成,那就意味着栈中没有驱动程序处理这个IRP;所有驱动程序直接传递这个IRP到下一个驱动程序。

l  驱动程序必须在其派遣例程(在IRP的路径向下到设备栈)中处理一个PnP IRP,在一个IoCompletion例程(在IRP的路径倒退的设备栈),或者两者都有,就像IRP参考页面指定的那样。

有些PnP IRPs,比如IRP_MN_REMOVE_DEVICE,必须首先被在设备栈顶部的驱动程序处理,然后才是每个下一层驱动程序。其他的,比如IRP_MN_START_DEVICE,必须首先被父总线驱动程序处理。还有其他,比如IRP_MN_QUERY_CAPABILITIES,可以在设备栈一路向下处理也可以反向处理。

l  驱动程序必须添加信息到一个IRPIRP的路径往下到设备栈和修改或者移除信息在IRP的路径反向。

在回应一个PnP查询IRP返回信息时,驱动程序必须按照这个规则来使得有序的信息被设备的分层驱动程序传递。

l  除非文档明确指出,否则驱动程序不必信赖PnP IRPs的发送次序。

l  在驱动程序发送一个PnP IRP时,它必须发送IRP到设备栈的顶层驱动程序。

大多数PnP IRPsPnP管理器发送,但是有些可以被驱动程序发送(例如,IRP_MN_QUERY_INTERFACE)。驱动程序必须发送发送一个PnP IRP到设备栈顶层的驱动程序。调用IoGetAttachedDeviceRefrence来为设备栈顶部的驱动程序获取一个指向设备对象的指针。

应该使用一个操作系统的(Checked build)检查构建来测试驱动程序。系统的检查构建检验驱动程序是否符合上面所列的许多PnP规则。

11)        DispatchPower例程

驱动程序的DispatchPower例程通过处理用于IRP_MJ_POWER I/O功能代码的IRPs来支持电源管理。与IRP_MJ_POWER功能代码相关联的是许多用于电源管理的次要I/O功能代码。电源管理器使用这些次要的功能代码来指挥驱动程序来改变电源状态,等待和响应系唤醒事件,以及查询关于它们设备的驱动程序。

每个驱动程序的DispatchPower例程执行下面的任务:

l  处理IRP,如果可能。

l  传递IRP到设备栈中的下一个较低层驱动程序,使用PoCallDriver

l  如果是一个总线驱动程序,则在设备上执行请求的电源操作,并且完成这个IRP

所有用于设备的驱动程序必须有条件处理用于设备的电源IRPs,除非在很少情况,功能或者过滤驱动程序被允许忽略这个IRP。大多数功能和过滤驱动程序要么执行一些处理要么为每个电源IRP设置一个IoCompletion例程,然后不完成这个IRP而向下传递到下一个较低层驱动程序。

12)        DIspatchQueryInformation例程

例程用来处理IRP_MJ_QUERY_INFORMATION I/O功能代码的IRPs。驱动程序对这个I/O代码的支持是可选的,而且通常出现在高层或者文件系统驱动程序中。这个请求由I/O管理器和其他操作系统组件,还有其他内核模式驱动程序发送。例如,当一个用户模式应用程序调用GetFileInformationByHandle的时候,还有当一个内核模式组件调用ZqQueryInformationFile的时候将会发送这个请求。最后IRP到达总线驱动程序,如果需要则物理改变设备的电源状态,并且完成这个IRP

IRP被完成,I/O管理器调用任一IoCompletion例程。驱动程序是否需要设置一个完成例程取决于IRP类型和驱动程序的个别需求。

给设备上电的电源IRPs必须被设备栈中最底层的驱动程序首先处理(底层总线驱动程序),然后由栈中依次往上的驱动程序处理。给设备断电的电源IRPs必须被设备栈中顶层的驱动程序首先处理,然后由栈中依次往下的驱动程序处理。

用于可移动设备的特殊处理

在它们的DispatchPower例程中,可移动设备的驱动程序应该检查看看设备是否还在。如果设备已经移除了,驱动程序就不用向下传递这个IRP到下一个较低层驱动程序。反而,驱动程序应该做下面操作:

l  调用PsStartNextPowerIrp来开始处理下一个电源IRP

l  设置Irp->IoStatus.StatusSTATUS_DELETE_PENDING

l  调用IoCompleteRequest,指定IO_NO_INCREMENT,来完成这个IRP

l  返回STATUS_DELETE_PENDING

13)        DispatchSetInformation例程

此例程用来处理IRP_MJ_SET_INFORMATION I/O功能代码的IRPs。驱动程序对这个I/O功能代码的支持是可选的,而且通常出现在高层或者文件系统驱动程序中。这个请求由I/O管理器和其他操作系统组件发送,还有其他内核模式驱动程序。例如,当一个用户模式应用程序调用SetEndOfFile的时候,还有一个内核模式组件调用ZwSetInformationFile的时候将会发送这个请求。

14)        DispatchFlushBuffers例程

此例程用来处理IRP_MJ_FLUSH_BUFFERS I/O功能代码的IRPs。驱动程序对这个I/O功能代码的支持是可选的,但是维持内核数据缓冲区的所有文件系统或者过滤驱动程序都必须处理这个请求,是为了在系统关闭前能将文件数据或者元数据的改变保存下了。这个请求由I/O管理器发送或者其他操作系统组件,还有其他内核模式驱动程序,在需要将缓冲的数据转存至磁盘的时候。例如,一个用户模式应用程序调用FlushFileBuffers的时候就会发送这个请求。

15)        DispatchShutdown例程

此例程用来处理IRP_MJ_SHUTDOWN I/O功能代码的IRPs。具有内部数据高速缓冲区的大容量存储设备的驱动程序必须处理这个请求。如果底层驱动程序维护内部数据缓冲区,那么大容量存储设备的驱动程序和层叠其上的中间层驱动程序也必须处理这个请求。

16)        DispatchSystemControl例程

此例程用来处理IRP_MJ_SYSTEMCONTROL I/O功能代码的IRPs

所有的驱动程序必须提供DispatchSystemControl例程。这个例程的作用是提供对Windows管理规范(WMI)的支持。一个驱动程序不管是否支持WMI,这个例程都必须将这个IRP向下传递到下一层驱动程序。

8、            编写Unload例程

在系统正在运行的时候,任何驱动程序要能被替换或者卸载和重新载入,必须有一个Unload例程。所有WDM驱动程序都必须有Unload例程。

尽管Unload例程对非WDM驱动程序是可选的,但是驱动程序验证器将忽略任何没有提供Unload例程的驱动程序。

1)         Unload例程的情形

在驱动程序将被替换时或者驱动程序服务的所有的设备都被移除时,操作系统卸载一个驱动程序。如果驱动程序在其处理完一个IRP_MN_REMOVE_DEVICE请求之后再没有设备对象,那么PnP管理器将调用一个PnP驱动程序的Unload例程。

在卸载顺序的开始,I/O管理器或者PnP管理器标记驱动程序对象和它的设备对象为“Unload Pending(待卸载)”。在一个驱动程序被标记为“待卸载”之后,另外的驱动程序将无法再关联到那个驱动程序上,也不能创建驱动程序的设备对象的任何额外引用。驱动程序可以完成为解决的IRPs,但是系统将不会发送任何新的IRPs到这个驱动程序。

在下列所有情况都为真时,I/O管理器将调用一个驱动程序的Unload例程:

l  再也没有对驱动程序创建的设备对象的引用。换句话说,没有底层设备相关的文件能被打开,也没有用于任何驱动程序的设备对象的IRPs未解决。

l  没有其他驱动程序继续附加在这个驱动程序上。

l  驱动程序调用了IoUnregisterPlugPlayNotification来撤销注册先前注册的所有PnP通知。

要注意的是,如果驱动程序的DriverEntry例程返回一个错误状态,那么Unload例程将不会被调用。在这种情况下,I/O管理器简单的释放驱动程序占用的内存空间即可。

在系统关机期间,PnP管理器和I/O管理器都不会调用Unload例程。驱动程序要必须执行关闭处理的话应该注册一个DispatchShutdown例程。

2)         Unload例程的功能

驱动程序的Unload例程的职能取决于驱动程序是否支持PnP

正像PnP驱动程序的DriverEntry例程通常很简单那样,它们的Unload例程也一样,在MSDNA PnP Driver’s Unload Routine章节中描述。

一个非PnP驱动程序的Unload例程必须释放设备对象和驱动程序分配的资源。简单来说,它必须撤销其相应的DriverEntryReinitialize例程在初始化驱动程序时所作的工作,它的设备,它的资源。参看MSDNA Non-PnP Driver’s Unload Routine章节。

a)         PnP驱动程序的Unload例程

PnP驱动程序必须有一个Unload例程,用来移除任何驱动程序特定的资源,比如内存、线程和事件,这些都是在DriverEntry例程中创建的。

驱动程序的Unload例程可以在所有驱动程序的设备被移除之后的任何时候被调用。PnP管理器调用一个驱动程序的Unload例程,使用一个IRQL=PASSIVE_LEVEL的系统线程上下文。

PnP驱动程序在响应PnP设备移除IRPs时释放设备特定的资源和设备对象。PnP管理器发送这些IRPs,代表它枚举到的每个PnP设备,还使用IoReportDetectedDevice得到任何根枚举的传统设备的一个驱动程序报告。

所以,PnP驱动程序的Unload例程通常很简单的,经常仅仅有一个Return语句组成。然而,如果驱动程序在其DriverEntry例程中分配任何驱动程序范围的资源,它必须在其Unload例程中释放那些资源除非它已经做过了。一般来说,释放一个PnP驱动程序的处理是一个同步操作。

I/O管理器释放驱动程序对象和任何驱动程序对象扩展,驱动程序使用IoAllocateDriverObjectExtension分配的。

b)        PnP启动程序的Unload例程

早期的驱动程序和高级文件系统驱动程序,它们不处理PnP设备移除请求,所以必须在其Unload例程中释放资源,删除设备对象,和从设备栈分离出来。

如果早先还没那么做,那么一个传统设备驱动程序在其Unload例程中要做的第一件事是禁止来自设备的中断。否则,在Unload例程正在释放在设备扩展中ISR需要处理这个中断的资源时,它的ISR可能会被调用来处理一个设备中断。甚至如果在这些情况下它的ISR成功的运行 ISR队列的DpcForIsr或者CustomDpc例程,还有可能运行在IRQL>=DISPATCH_LEVEL下的其他驱动程序例程,将在Unload例程重新获得控制之前执行,因此增加了Unload例程删除另一个驱动程序例程引用的资源的可能性。

在禁止中断之后,文件系统和传统驱动程序必须释放资源和对象。

释放驱动程序分配的资源

驱动程序如何使用注册表的细节,在其设备扩展、控制器扩展或者驱动程序分配的非分页池中建立系统对象和资源,不同于驱动程序到驱动程序。然而,任何Unload例程必须释放驱动程序在场景中使用的资源。

任何驱动程序的Unload例程必须确保在它释放资源之前没有其他驱动程序例程当前在使用或者即将要使用的特别的资源。

一般来说,Unload例程在以下阶段释放所有驱动程序分配的资源:

1、  如果驱动程序还没有这样做,那么就禁止任何物理设备上的中断,如果可能,然后一旦中断被禁止就调用IoDisconnectInterrupt

2、  确保没有其他驱动程序例程能引用Unload例程准备释放的资源。

例如,如果驱动程序用于一个特别的设备对象的IoTimer例程当前是使能的状态,那么Unload例程必须调用IoStopTimer。还必须确保没有线程在等待任何驱动程序的派遣对象,还有它的定时器对象在它释放用于其派遣对象的存储前没有被加入调用它的CustomTimerDpc例程的队列中。如果它有一个ISR可能已经加入队列的customDpc例程,那么它必须调用KeRemoveQueueDpc,等等。

如果驱动程序调用IoQueueWorkItem,那么它必须确保其作业项目已经被完成。IoQueueWorkItem获取一个关联设备对象的引用;如果任何这样的引用还存在,那么驱动程序就不能被卸载。

如果驱动程序调用了PsCreateSystemThread,那么Unload例程也必须处使驱动程序创建的线程运行起来,这样以来线程它自己可以在驱动程序被卸载之前调用PsTerminateSystemThread。驱动程序不能释放驱动程序创建的系统线程,要通过使用PsCreateSystemThread返回的Thread Handle调用ZwClose

3、  释放任何驱动程序分配的设备特定的资源。这样做要涉及到调用下列系统支持例程:

l  IoDeleteSymbolicLink:如果DriverEntry或者Reinitialize例程调用过IoCreateSymbolicLink或者IoCreateUnprotectedSymbolicLinkIoDeassignArcName:如果驱动程序调用过IoAssignArcName

l  ExFreePool:如果DriverEntry或者任何其他驱动程序例程调用过ExAllocatePoolWithTag并且驱动程序还没有释放分配的内存。

l  MmUnmapIoSpace:如果DriverEntry或者Reinitialize例程调用过MmMapIoSpace

l  MmFreeNonCachedMemory:如果DriverEntry或者Reinitialize例程调用过MmAllocateCachedMemory

l  MmFreeContiguousMemory:如果DriverEntry或者Reinitialize例程调用过MmAllocateContiguousMemory

l  FreeCommonBuffer:如果DriverEntry或者Reinitialize例程调用过AllocateCommonBuffer

l  IoAssignResources或者IoReportResourceUsage:如果DriverEntry或者Reinitialize例程调用过这些支持例程中的一个或者HalAssignSlotResources来为它自己和/或者它的独立物理设备获取在配置注册表中的硬件资源。

4、  释放系统对象和资源,就是那些DriverEntry或者Reinitialize例程建立在设备对象的设备扩展中的或者在控制器对象(如果创建了)的控制器扩展中的资源。尤其是,驱动程序必须在它尝试删除设备对象(IoDeleteDevice)或者控制器对象(IoDeleteController)之前做下面操作:

l  调用IoDisconnectInterrupt来释放到存在相应设备或者控制对象扩展中的中断对象指针。

l  使用指向下一个较低层的驱动程序的文件对象的指针来调用ObDerefrenceObject,如果它调用过IoAttachDevice或者IoAttachDeviceToDeviceStack并且将这个指针存储在一个设备或者控制器扩展中。

l  使用指向较低层驱动程序的设备对象来调用IoDetachDevice,如果它调用过IoAttachDevice或者IoAttachDeviceToDeviceStack并且将这个指针存储在一个设备或者控制器扩展中。

5、  释放硬件资源,就是那些DriverEntry或者Reinitialize例程获取的用于驱动程序的物理设备资源,如果有的话,位于注册表中/Registry/Machine/Hardware/ResourceMap树下面。

6、  移除任何用于其设备的名字,同样,DriverEntry或者Reinitialize例程存储在注册表中/Registry../DeviceMap树下面。

在驱动程序释放设备、系统和硬件资源之后,它就可以删除它的设备和控制器对象可,就像下面章节中描述的那样。

释放设备和控制器对象

在驱动程序删除一个设备或者控制对象之前,它必须先释放它对外部资源的引用,比如其他驱动程序对象指针和/或者中断对象指针,这些资源被存储在相应的设备或者控制器扩展中。然后可以为驱动程序创建的每个设备对象调用IoDeleteDevice。一个先前调用过IoCreateController的非WDM驱动程序必须还要调用IoDeleteController

任何内核定义的对象,驱动程序为其在设备扩展中提供存储空间,是在Unload例程使用相应的设备对象调用IoDeleteDevice时被自动释放的。一般来说,任何由DriverEntry或者Reinitialize例程通过调用KeInitializeXxx建立的对象可以通过调用IoDeleteDevice而被释放,如果驱动程序为那个对象在其设备扩展中提供存储空间的话。例如,如果驱动程序有一个CustomTimerDpc例程,而且为必需的DPC和定时器对象在其设备扩展中提供存储空间,那么调用IoDeleteDevice来释放这些系统资源。

同样地,任何内核定义的对象,驱动程序为其在一个控制器扩展中提供存储空间,是在Unload例程使用相应的控制器对象调用IoDeleteController时被自动释放的。

如果DriverEntry或者Reinitialize例程曾调用IoGetConfigurationInformation来增加一个特定类型设备的计数,那么Unload例程还必须调用IoGetConfigurationInformation并且减少在I/O管理器的全局配置信息结构中其设备的计数,就像它删除相应的设备对象那样。

在它返回控制之前,Unload例程还负责释放任何其他驱动程序分配的资源,就是那些还没有被其他驱动程序例程释放的资源。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值