随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
本节介绍内核模式驱动程序包含的标准例程。这些标准驱动程序例程的一些是必需的,一些是可选。本节也介绍驱动程序对象,其包含了指向每个驱动程序标准例程的指针。
有些驱动程序与系统提供的端口驱动程序或者类型驱动程序配合,定义了很多驱动程序需要的功能。例如,一个SCSI微端口驱动程序主要与SCSI端口驱动程序相配合。
1、 标准驱动程序例程简介
每个内核模式驱动程序都是围绕着一套系统定义的标准驱动程序例程创建的。内核模式驱动程序在这些标准例程中处理I/O请求包(IRPs),通过调用系统提供的驱动程序支持例程。
所有的驱动程序,不管其在附属的驱动程序链中的层次,必须有一个基本的标准例程集,以便处理IRPs。驱动程序是否必须实现其他的标准例程取决于驱动程序是否控制一个物理设备或者是一个物理层设备驱动程序,也取决于底层物理设备的特性。控制物理设备的底层驱动程序需要的例程比高层驱动程序多,通常要传递IRPs给底层驱动程序处理。
标准驱动程序例程可以分成两组:一些是每个内核模式驱动程序必须有的,另一些是可选,这些取决于驱动程序类型和在设备栈中的位置。
Required standard driver routines | Purpose |
DriverEntry | 初始化驱动程序和它的驱动程序对象 |
AddDevice | 初始化设备和创建设备对象 |
Dispatch Routines | 接收和处理IRPs |
Unload | 释放驱动程序获得的系统资源 |
Optional standard driver routines | Purpose |
Reinitialize | 完成DriverEnty不能完成的驱动程序初始化 |
StartIo | 启动一个物理设备上的I/O操作 |
Interrupt Service Routine | 当设备发生中断时,保存设备状态, |
Deferred Procedure Calls | 在ISR保存设备状态后完成设备中断的处理 |
SynchCritSection | 同步访问驱动程序的数据 |
AdapterControl | 初始化DMA操作 |
IoCompletion | 完成驱动程序的IRP处理 |
Cancel | 撤销驱动程序的IRP处理 |
CustomTimerDpc, IoTimer | 定时和同步事件 |
当前IRP和目标设备对象都是要传入许多标准例程的参数。每一个驱动程序通过其标准例程集处理每个阶段的IRP。
按照约定,系统提供的驱动程序预先计划了一个标识、驱动程序特定的或者设备特定的每个标准例程名字的前缀,除了DriverEnty。举个例子,本文使用“DD”,在 driver object illustration章节中所示的那样。按照这个约定可以使得调试和维护驱动程序简单一些。
2、 标准驱动程序例程的要求
在设计内核模式驱动程序的时候,一定要铭记下面几点:
l 每个驱动程序必须有一个DrvierEntry例程,这个例程初始化驱动程序范围内所有数据结构和资源。I/O管理器在加载驱动程序的时候调用DriveEntry例程。
l 每个驱动程序必须有至少一个派遣例程用来接收和处理I/O请求包(IRPs)。每个驱动程序必须放置一个派遣例程的入口点在DRIVER_OBJECT结构中,为了驱动程序能接收到每个IRP major function code(IRP主要的功能代码)。驱动程序还可以为每个IRP主要的功能代码都创建一个单独的派遣例程,或者驱动程序可以有一个或者多个派遣例程来处理各种功能代码。
l 每个WDM驱动程序必须由一个Unload例程。驱动程序必须放置这个Unload例程的入口点在驱动程序的驱动对象中。PnP驱动程序的Unload例程的职责极少,但是非PnP驱动程序的Unload例程的职责要负责释放驱动程序使用的任何系统资源。
l 每个WDM驱动程序必须由一个AddDevice例程,并且在驱动程序对象的驱动扩展中明确它的入口点。AddDevice例程负责为驱动程序控制的每个PnP设备创建和初始化设备对象。
l 驱动程序可以由一个StartIo例程,I/O管理器调用其来开始对IRPs的I/O操作。这些IRPs都是驱动程序已经排队到系统提供的IRP队列中。任何没有StartIo例程的驱动程序必须建立和管理用于接收到的IRPs的内部队列,或者必须在其派遣例程中完成每个IRP。高层驱动程序可以不需要有一个StartIO例程,如果他们只是简单的直接从派遣例程传递IRPs到低一层的驱动程序。
l 某些微端口驱动程序是上述要求的例外。
l 驱动程序是否有任何其他类型的标准例程取决于他们的功能,还取决于其如何适应到系统(例如,是否与系统提供的驱动程序交互。)。
3、 驱动程序对象简介
I/O管理器创建一个驱动程序对象,为已经安装并加载的每个驱动程序。驱动对象由DRIVER_OBJECT结构定义。
当I/O管理器调用一个驱动程序的DriverEntry例程,它会提供驱动程序的驱动程序对象的地址。驱动程序对象包含了一个驱动程序的许多标准例程的入口点的存储域。驱动程序负责填充这些入口点。
下图说明了一个驱动对象,包含有一套系统定义的标准例程,这些都是底层和高层驱动程序可能或必须有的。
每个名字旁边标有星号的标准例程接受一个输入的I/O请求包(IRP)。这些标准例程的每一个还接受一个指针,指向了I/O请求的目标设备对象。
I/O管理器定义驱动程序对象类型,并使用驱动程序对象来注册和跟踪有关驱动程序加载的镜像信息。要注意的是,在驱动程序对象中的派遣入口点(从DDDispatchXxx到DDDispatchYyy)与主要功能代码相对应(IRP_MJ_XXX),就是被传递到IRPs的I/O栈存储单元的那些代码。
I/O管理器路由每个IRP首先找到一个驱动程序提供的派遣例程。底层驱动程序的派遣例程通常调用一个I/O支持例程(IoStartPacket)来排队(或者传递)每个具有有效参数的IRP到驱动程序的StartIo例程。StartIo例程在特定的设备上开始请求的I/O操作。高层驱动程序通常没有StartIo例程,但是可以有。
1) 驱动程序对象中的驱动程序入口点
一个内核模式驱动程序必须指定其驱动程序对象中的以下入口点:
l 至少一个派遣例程的入口点,为了获取请求PnP、电源和I/O操作的IRPs。
l 它的AddDevice例程之口点,位于DriverObject->DriverExtension->AddDevice。
l 它的StartIo例程入口点,如果它管理自己拥有的IRPs队列。
l 如果驱动程序可以被动态的加载和/或者替换,Unload入口点为了释放任何驱动程序被分配的系统资源,比如系统对象或者内存。(不能在系统运行时被替换的驱动程序,比如键盘驱动程序,不需要提供Unload例程。)
这些要求不适用于某些微端口驱动程序,因为其对应的类型或者端口驱动程序已经在驱动层序对象中定义了入口点。更多信息参看特定设备类型的文档。
I/O管理在对应的驱动程序对象中维护驱动程序创建的设备对象信息。
当一个驱动程序被加载,它的DriverEntry例程将被调用,使用一个驱动程序对象指针。当驱动程序的DriverEntry例程被调用,它直接在驱动程序对象中设置Dispatch、StartIo(如果有)和Unload(如果有)入口点,如下:
DriverEntry例程也设置它的AddDevice例程的入口点,在它的驱动对象的DriverExtension中,如下:
DriverObject->DriverExtension->AddDevice = DDAddDevice;
DriverEntry或者可选的Reinitialize例程还可以使用驱动程序对象中的一个域来获取或者设置在配置管理器的注册表数据库中的信息。更多信息参看MSDN中的 Registry Keys for Drivers。
I/O管理器对外没有提供操作驱动程序对象的例程。驱动程序对象被I/O管理器用来保持对当前加载的驱动程序的联系。驱动程序对象的某些成员仅被I/O管理器使用。其他成员还被驱动程序编写者使用;例如,你必须知道某些定义AddDevice、Dispatch、StartIo和Unload入口点的成员名字。不要使用DRIVER_OBJECT结构中未公开的成员,也不要假定任何在本文中命名的驱动程序对象成员的位置。否则,你将不能保持驱动程序的在操作系统间的可移植性。
2) 其他标准驱动程序例程
就像驱动程序对象插图所示的那样,内核模式驱动程序除了那些之外还有其他标准例程,这些例程的入口点也可以设置到它们各自的驱动程序对象中。大多数标准驱动程序例程和某些配置相关的对象由I/O管理器定义。ISR的SynchCritSection例程和那些在驱动程序对象插图中所示名字包含单词“custom”的例程是由NT内核定义的。
大多数驱动程序使用它们创建的每个设备对象的设备扩展来维护有关它们I/O操作的设备特定状态,和保存任何系统资源的指针;为了拥有其他标准例程,那些都必须要分配的。例如,上图显示的DDCustomTimerDpc例程需要驱动程序为内核定义的定时器和DPC对象提供存储区。
上图所示的用于底层驱动程序的标准驱动程序集应该与高层驱动程序不同。上图所示的某些例程是设备相关或者配置相关需要的。其他是可选的:你可以选择实现这样一个例程依赖于驱动程序的设备的种类或者配置,或者依赖于驱动程序的设计,或者依赖于驱动程在分层驱动程序链中的位置。