大多数标准驱动程序例程及其使用的一些配置相关的对象是由 I/O 管理器定义。驱动程序对象是不透明的:只有定义的系统组件(这里是 I/O管理器)“知道”对象类型的内部结构,并能直接访问对象包含的所有数据。定义的系统组件通常输出支持例程,驱动程序和其他内核模式组件能调用它们以 操纵那些组件的对象。
内核模式驱动程序必须在它的驱动程序对象中定义下列的入口点:
所有的驱动程序必须有一个 DriverEntry 例程,,至少一个 Dispatch 例程,并且必须在其驱动程序对象中为驱动程序处理的每个IRP_MJ_XXX 定义发送入口点。驱动程序能拥有与它所能处理的IRP_MJ_XXX 功能同样数量的 Dispatch 例程。 如果在系统运行时驱动程序能被替换,驱动程序必须有 Unload 例程,并且在其驱动程序对象中定义一个 Unload 入口点。Unload 例程负责释放任何系统资源,诸如 Windows 2000 对象或者驱动程序分配的内存,这些资源通常在驱动程序本身被从系统卸载之前使用。
所有的 PnP 驱动程序必须有 AddDevice 例程,并且在驱动程序对象的驱动程序扩展中定义其入口点。AddDevice 例程负责为驱动程序控制的每一个 PnP 设备创建和初始化设备对象。
驱动程序可以有 StartIo 例程,并且能在其驱动程序对象中定义一个 Startlo 入口点。没有 StartIo 例程的任何最低层驱动程序必须创建并且管理发送到其 Dispatch 例程的内部 IRP 队列,除非它能在它的Dispatch 例程中完成它得到的所有 IRP。较高层驱动程序可以有 StartIo 例程,但是很少这样做,这是因为较高层驱动程序通常把IRP 从它们的 Dispatch 例程直接传递到低层驱动程序。
下面对一些驱动例程进行讨论:
DriverEntry 例程:
每个驱动程序必须有一个 DriverEntry 例程以用来初始化驱动程序范围的数据结构和资源。当 I/O 管理器装入驱动程序时,它调用 DriverEntry 例程。对于一个支持“即插即用”(PnP)的驱动程序,就象所有的驱动程序将要支持一样,DriverEntry 例程负责驱动程序的初始化,而 AddDevice 例程负责设备初始化(也可能是处理 PnP IRP_MN_START_DEVICE请求的 Dispatch 例程)。驱动程序初始化包括输出该驱动程序其他入口点,初始化该驱动程序使用的特定对象,并设置每个驱动程序系统资源。
每个 DriverEntry 例程都运行在位于 IRQL PASSIVE_LEVEL 的系统线程环境中。因此,只要驱动程序不控制持有系统页文件的设备,任何由 ExAllocatePool 在初始化过程中分配的专用内存都能从页式存储池中取得。在 DriverEntry 返回控制前,分配的内存必须由ExFreePool 释放。当然,设置 Reinitialize 例程的驱动程序可以在它调用IoRegisterDriverReinitialization 的时候传送一个指针到内存,从而使驱动程序的Reinitialize 例程负责释放已分配的内存。
DriverEntry 例程在以下阶段中初始化:
1. 为需要与其设备通信的硬件配置信息(如果有)分配内存。
2. 在驱动程序对象中设置驱动程序的 Dispatch、AddDevice、StartIo(如果有)和Unload(如果有)入口点。
3. 建立所有驱动程序对象或其他系统资源,例如自旋锁,使驱动程序可以用它们来处理I/O 请求。
4. 释放任何不再需要的已分配内存,可能调用 IoRegisterDriverReinitialization,并返回一个适当的 NTSTATUS 值。
在 Windows 2000 和 WDM 驱动程序中,每个设备对象应在 AddDevice 例程或者在处理 PnP
IRP_MN_START_DEVICE 请求的 Dispatch 例程中分配,而不是在 DriverEntry 例程中。
一个驱动程序可能需要为其他驱动程序范围的使用分配额外的系统存储空间。如果这样,DriverEntry 例程可以调用一个(或多个)下列例程:
. IoAllocateDriverObjectExtension,创建一个与驱动程序对象连接的上下文区间。
. ExAllocatePool,用于页式或非页式系统内存空间。
. MmAllocateNonCachedMemory 或 MmAllocateContiguousMemory,用于分配给高速缓存的非页式系统内存空间(用于 I/O 缓冲区)
如果一个驱动程序不能在其 DriverEntry 例程中完全初始化自己,它可以在 DriverEntry例程返回控制一段时间之后提供一个 Reinitialize 例程调用,这个Reinitialize 例程执行另一个驱动程序启动后必须要做的任务。
Dispatch 例程一般实现:
大多数驱动程序必须处理一些或者所有以下请求:
. IRP_MJ_PNP 表明一个包括 PnP 设备识别的请求,硬件配置,或者资源分配的请求。这类请求显然从 PnP管理器或者从一个与较高层驱动程序密切相关的设备发送到一个设备驱动程序。
. IRP_MJ_POWER 表明一个属于设备或者系统的电源状态请求。这类请求通过电源管理器或者一个密切连接的较高层驱动程序发送到设备驱动程序。关于这个例程的更多信息,
. IRP_MJ_CREATE 表明用户模式保护子系统,可能代表应用或者特定子系统驱动程序,已请求一个与目标设备对象有关的文件对象的控制,或者一个较高层的驱动程序把其设备对象连接或者附加到目标设备对象。
. IRP_MJ_CLEANUP 表明,为代表目标设备对象的文件对象的处理被关闭,因此任何在清除 IRP 的驱动程序 I/O 栈位置中找到的,在给定文件对象目标设备的当前队列中的IRP, 应该被取消。
. IRP_MJ_CLOSE 表明代表目标设备对象的文件对象句柄或指向目标设备对象的指针已经被关闭,还表明没有未完成的、对文件对象指针的引用。
. IRP_MJ_READ 表明 I/O 请求从下层物理设备向系统传输数据。
. IRP_MJ_WRITE 表明 I/O 请求从系统向下层物理设备传输数据。
. IRP_MJ_DEVlCE_CONTROL 表明一个带有系统定义的,指定一个被发送给设备驱动程序的具体设备操作的特定设备类型 I/O 控制码的请求.
. IRP_MJ_INTERNAL_DEVICE_CONTROL 表明一个传给设备驱动程序的请求,大多数情况下是来自一个紧密连接的较高层驱动程序,通常带有一个私有的、驱动程序特定的及特定设备类型的或特定设备的 I/O 控制码,该控制码请求一项特定设备类型的或特定设备的操作。
大多数 Dispatch 例程如下处理输入 IRP:
1.在 IRP 中检测驱动程序的 I/O 栈位置以决定做什么,并且如果有参数的话,检测参数的有效性。
2.尽可能满足请求并完成 IRP;否则,由较低层驱动程序或其他设备驱动程序例程传送它以做进一步处理。
如果一个输入 IRP 能被迅速完成,Dispatch 例程要做以下工作:
1.通常将 IRP 的 I/O 状态块中的 Status 与 Information 成员设置为恰当的值:
. Dispatch 例程将 Status 设置为 STATUS_SUCCESS 或者一个适当错误 STATUS_XXX,可以是调用一个支持例程所返回的值,或者对于某些的同步请求来说,是一个较低层驱动程序返回的值。 如果较低层驱动程序返回 STATUS_PENDING,较高层驱动程序不应当用 IRP 调用IoCompleteRequest,除非它首先用该 IRP 调用了IoMarkIrpPending。当然,较高层驱动程序的 Dispatch 例程未必为返回 STATUS_PENDING 的较低层驱动程序完成任何 IRP。
. 如果满足了一个传输数据的请求,例如一个读或写请求,则将 Information 设置为成功传输的比特数。
. 对其他以 STATUS_SUCCESS 完成的 IRP 的不同特定请求而将 Information 设置为不同的值。
. 对以警告性 STATUS_XXX 完成的 IRP 的不同特定请求而将 Information 设置为不同的值。例如,对于象STATUS_BUFFER_OVERFLOW 这样的警告,将 Information 设置为传输的字节数。
. 通常,对以一个错误 STATUS_XXX 完成的请求,将 Information 置为零。
2.用 IRP 和PriorityBoost IO_NO_INCREMENT 调用 IoCompleteRequest。
3.返回已在 I/O 状态块中设置的适当的 STATUS_XXX。注意,对 IoCompleteRequest 的调用使给定的 IRP不能被调用者访问,因此来自一个 Dispatch 例程的返回值不能从一个已完成的 IRP 的 I/O 状态块中设置。
以下是用 IRP 调用 IoCompleteRequest 的实现方针:
. 调用 IoCompleteRequest 之前,总是释放驱动程序持有的自旋锁。 完成一个 IRP 的时间是不确定的,尤其是在一系列分层的驱动程序中。另外,如果一个较高层驱动程序的 IoCompletion 例程向一个正持有自旋锁的较低层驱动程序发送一个 IRP ,就会产生死锁
通常,驱动程序不在它们的 Dispatch 例程中完成 IRP,除非给定请求的参数是无效的,或者在设备驱动程序中,特定 IRP_MJ_XXX 不要求设备 I/O 操作。 一个较高层驱动程序往往将它传送给较低层驱动程序来处理.
一个最低层驱动程序按照以下方针在它的 Dispatch 例程里完成 IRP:
1. 如果 Dispatch 例程确定它自己的 I/O 栈位置里有参数是无效的,它应当用一个适当的错误 STATUS_XXX 立即完成该 IRP。
2.如果 IRP 包含功能码 IRP_MJ_CLEANUP,DispatchCleanup 例程必须为给定的驱动程序清除 IRP 的 I/O 栈位置的文件对象,完成当前在目标设备对象排队的所有 IRP,并完成清除 IRP。
3.如果请求不要求设备 I/O 操作,Dispatch 例程应满足请求并完成 IRP。
4.否则,Dispatch 例程必须用 IRP 调用 IoMarkIrpPending,将 IRP 排队到其他驱动程序例程以做进一步处理,并返回 STATUS_PENDING。