随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
4、 编写DriverEntry例程
每个驱动程序必须有一个DriverEntry例程,用来初始化驱动程序范围内的数据结构和资源。I/O管理器在加载驱动程序的时候调用DriverEntry例程。
在一个支持PnP的驱动程序中,和所有驱动程序一样,DriverEntry例程负责驱动程序的初始化,而AddDevice例程(而且,可能的话,派遣例程会处理一个PnP的IRP_MN_START_DEVICE请求)负责设备的初始化。驱动程序初始化包括输出驱动程序的其他入口点,初始化驱动程序使用的某些对象,还有设定各种个样每个驱动程序的系统资源。(非PnP驱动程序有显著的不同要求,在适用于微软WindowsNT 4.0和更早期版本的DDK中有描述。)
DriverEntry例程在具有IRQL=PASSIVE_LEVEL的系统线程的上下文中被调用。
DriverEntry例程可以是可分页的,并且应该在一个INIT段中,那样将来会被丢弃。要使用一个alloc_text编译指令。
1) DriverEntry的必须职责
1. 提供驱动程序标准例程的入口点。
驱动程序存储它的许多标准例程的入口点到驱动程序对象或者驱动程序扩展中。这些入口点包括驱动程序的AddDevice例程、派遣例程、StartIo例程和Unload例程。下面举个例子(Xxx是个占位符,使用自己的驱动程序前缀标识符):
另外的标准例程,比如ISRs或者IoCompletion例程,通过调用系统支持例程来说明。
2. 创建和/或者初始化各种驱动程序范围内的对象、类型或者驱动用到的资源。要注意的是,大多数标准例程使用在每个设备基础上的对象,因此驱动程序应该建立这样的对象,在它的AddDevice例程中或者在接收到一个IRP_MN_START_DEVICE请求后。
如果驱动程序有一个设备专用的线程或者在等待任何内核定义的派遣对象,DrvierEntry例程应该初始化内核派遣对象。(取决对于驱动程序如何使用这些对象,它都应该在其AddDevice例程中或者在接收一个IRP_MN_START_DEVICE请求后代替执行这个任务。)
3. 释放任何它分配的和不再需要的内存。
4. 返回NTSTATUS,指示驱动程序是否成功加载并且可以接受和处理来自PnP管理器的请求,这些请求包括配置、添加和启动它的设备。
2) DriverEntry的可选职责
依赖一个特定驱动程序在分层的驱动程序链中的位置、底层设备的特性以及驱动程序的设计而定,DriverEntry例程还可以有下列职能:
l 调用IoAllocateDriverObjectExtension来创建和初始化一个驱动程序对象扩展,如果驱动程序要存储驱动程序范围内的数据。驱动程序对象扩展是一个驱动程序特有的数据结构。例如,驱动程序可以用其驱动程序对象扩展来保存一个注册表路径或者其他全局信息。
l 调用PsCreateSystemThread来创建执行工作线程,如果驱动程序是一个高层驱动程序(比如一个文件系统驱动程序)可以使用此类线程。在这种情况下,驱动程序必须还有一个WORKER_THREAD_ROUTINE类型的回调例程,这个回调例程只有一个PVOID输入参数。
l 注册一个Reinitialize例程。
l 处理类型特定初始化的不同于这里讨论的那些需求,比如,那些与一个端口或者类型驱动程序一同工作的设备特定的微端口或者微类型驱动程序就会有。
为系统资源提供存储区
每一个设备对象应该在AddDevice例程或者在派遣例程中被分配,不是在DriverEntry例程中,用于处理PnP的IRP_MN_START_DEVICE请求。
然而,驱动程序还需要为其他驱动程序范围内需要的其他系统空间内存。如果是这样,DriverEntry例程可以调用一个或者更多下面例程:
l IoAllocateDriverObjectExtension,用于创建驱动程序对象相关联的一个上下文区域。
l ExAllocatePoolWithTag,用于分配分页或者不分页的系统空间内存。
l MmAllocateNonCachedMemory或者MmAllocateContiguousMemory,用于分配缓存排列的系统空间内存(用于I/O缓冲器)。
每个DriverEntry例程都运行在具有IRQL=PASSIVE_LEVEL级别的系统线程上下文中。因此,在初始化过程中独占使用的任何由ExAllocatePoolWithTag分配的内存可以来自分页池,只要驱动程序不控制拥有系统页面文件的设备。分配的内存必须在DriverEntry返回控制之前由ExFreePool来释放。然而,设置了一个Reinitialize例程的驱动程序可以传递一个指向这个内存的指针,在调用IoRegistryDriverReinitialization的时候,因此要使驱动程序的Reinitialize例程负责释放分配的内存。
要求硬件资源
较早的,非PnP驱动程序需要从注册表获取资源。PnP驱动程序,另一方面,既不需要从注册表获取设备资源也不直接向注册表写入资源需求。作为替代,这些驱动程序在对某些PnP IRPs的回应中报告需求,作为PnP管理器的枚举处理的一部分。PnP驱动程序在一个PnP IRP_MN_START_DEVICE请求中接收其被分配的资源。
不与PnP管理器直接交互的驱动程序,比如某些微端口驱动程序,可能有不同的被一个类型或者端口驱动程序强加的报告需求。
使用注册表
DriverEntry例程可以使用注册表来获取一些它需要用来初始化驱动程序的信息,或者它可以设置信息到注册表中为其他驱动程序或者保护的子系统来使用。信息的特性取决于设备类型。驱动程序可以访问注册表,使用ZwXxx和RtlXxx例程。DriverEntry例程的RegistryPath参数指向一个带计数的Unicode字符串,这个字符串指定了驱动程序的注册表键的路径,/Registry/Machine/System/CurrentControlSet/Services/DriverName。例程应该保存这个字符串的一个拷贝,不是它的指针,因为这个指针在DriverEntry返回后就不再有效了。
DriverEntry的返回值
DriverEntry例程返回一个NTSTATUS值,可以是STATUS_SUCCESS,也可以是一个恰当的错误状态值。
DriverEntry例程应该其返回STATUS_SUCCESS之前推迟调用IoRegistryDriverReinitialization。而且不能绝对执行这个调用除非其返回STATUS_SUCCESS。
如果DriverEntry例程返回了一个NTSTATUS值,不是一个成功的或者信息值,比如STATUS_SUCCESS,那么这个DriverEntry隶属的驱动程序就没有被加载。
初始化失败的DriverEntry例程必须释放任何其返回控制前已经创建的任何资源,包括系统对象、系统资源和注册表资源。而且还应该重新设置驱动程序在驱动程序对象中的IRP_MJ_FLUSH_BUFFERS和IRP_MJ_SHUTDOWN派遣入口点为NULL,如果驱动程序支持这些请求的话。
如果驱动程序在初始化中失败,DriverEntry例程还应该在返回控制前记录一个错误到日志中。
要注意的是,如果驱动程序的DriverEntry例程返回一个失败状态,那么驱动程序的Unload例程不会被调用。
5、 编写Reinitialize例程
任何驱动程序,需要在场景中初始化自己,那么可以包含一个Reinitialize例程。Reinitialize例程将被调用,在DriverEntry例程返回控制和其他驱动程序已经初始化他们自己后。通常,Reinitialize例程执行任务必须在其他驱动程序开始之后。
例如,系统的键盘类型驱动程序,kbdclass,支持PnP和早期的键盘端口。如果系统包含了一个或者多个早期端口,PnP管理器检测不到早期端口,键盘类型驱动程序必须仍然创建为每个端口创建一个设备对象,并且将自己层叠到较低层端口驱动程序上。所以,类型驱动程序有一个Reinitialize例程要被调用,在其DriverEntry和AddDevice例程被调用和其他驱动程序被加载之后。Reinitialize例程检测端口,创建这个端口的设备对象,并且层叠这个驱动程序到用于这个设备的其他较低层驱动程序上。
驱动程序的DriverEntry例程调用IoRegisterDriverReinitialization来排队一个要执行的Reinitialize例程。Reinitialize例程还可以自己调用IoRegisterDriverReinitialization,这将处使这个例程被重新排队。Reinitialize的一个参数指示了它已经被调用的次数。
调用IoRegisterDriverReinitialization可以包含一个驱动程序定义的上下文数据的指针,也就是系统提供的作为Reinitialize输入参数的。如果Reinitialize例程使用注册表,其上下文数据应该包含RegistryPath的指针,这个指针将被传入到DriverEntry例程,因为这个指针不是Reinitialize例程的一个输入参数。
如果DriverEntry没有返回STATUS_SUCCESS,那么Reinitialize例程将不会被调用。
通常,含有Reinitialize例程的驱动程序是一个高层驱动程序,可以控制PnP和早期的设备。除了为PnP管理器检测到的(或者是由于PnP管理器调用驱动程序的AddDevice例程)设备创建设备对象之外,驱动程序必须还为早期设备创建设备对象,PnP管理器无法枚举早期设备。Reinitialize例程创建那些设备对象,并且层叠这个驱动程序到紧邻的下一层用于底层设备的驱动程序上。
6、 编写AddDevice例程
任何支持PnPde 的驱动程序必须有一个AddDevice例程。AddDevice例程创建一个或者更多设备对象来代表物理、逻辑或者虚拟设备,为了驱动程序能处理I/O请求。而且它也将设备对象附加到设备栈上,因此设备栈将包含每个驱动程序相关联的设备的一个设备对象。
PnP管理器为驱动程序控制的每个设备调用驱动程序的AddDevice例程。在系统初始化期间(设备第一次被枚举的时候),和任何在系统运行时枚举到的新设备的时候,AddDevice例程将被调用。
1) 功能或者过滤驱动程序中的AddDevice例程
一个在功能或者过滤驱动程序中的AddDevice例程应该考虑下列步骤:
1. 调用IoCreateDevice来为将要添加的设备创建一个功能或者过滤设备对象(FDO或者过滤DO)。
不要为设备对象指定DeviceName,因为那样做会绕过PnP管理器的安全检查。如果一个用户模式组件需要一个设备的符号链接,那么就注册一个设备接口。如果一个内核模式组件需要一个早期的设备名,那么驱动程序必须命名设备对象,但是命名是不推荐的。
包含FILE_DEVICE_SECURE_OPEN在DeviceCharacteristics参数中。这个特性指示I/O管理器对设备对象的所有打开请求执行安全检查,包括相关打开和后缀文件名打开。
2. [可选]为设备创建一个或多个符号链接。
调用IoRegisterDeviceInterface来注册设备功能和创建应用程序或者系统组件可以用来打开设备的一个符号链接。驱动程序应该在其处理IRP_MN_START_DEVICE请求时通过调用IoSetDeviceInterfaceState来使能这个接口。
3. 存储设备的PDO指针到设备扩展中。
PnP管理器提供一个PDO的指针,作为AddDevice的PhysicalDeviceObject参数。驱动程序对例程的调用要使用这个指针,比如 IoGetDeviceProperty。
4. 在设备扩展中定义标记来跟踪设备当前PnP状态,比如设备被暂停、被移除和突然被移除。
例如,定义一个标记来指示传入的IRPs应该被保持着,当设备处于暂停状态时。创建一个队列用于保持IRPs,如果驱动程序还没一个排队IRPs的机制。
还应该分配一个IO_REMOVE_LOCK结构在设备扩展中,并且调用IoInitializeRemoveLock来初始化这个结构。
5. 如果必要的话,设置DO_POWER_INRUSH或者DO_POWER_PAGABLE标记用于电源管理。可被分页的驱动程序必须设置DO_POWER_PAGABLE标记。设备对象标记通常由总线驱动程序设置,在其为设备创建PDO的时候。然而,较高层驱动程序在它们的AddDevice例程中可能偶尔需要修改这些标记的值,在它们创建FDO或者过滤DO的时候。
6. 创建和/或者初始化任何其他软件资源,驱动程序使用来管理这个设备的,比如事件、自旋锁或者其他对象。(硬件资源,比如 I/O端口,将稍后被配置,在响应IRP_MN_START_DEVICE请求中。)
由于AddDevice例程在具有IRQL=PASSIVE_LEVEL级别的系统线程上下文中运行,任何由ExAllocatePoolWithTag分配的内存,用于在初始化期间独占时候的,可以从分页池中分配。然而,这个驱动程序不能控制持有系统分页文件的设备。这样分配的内存必须使用ExFreePool来释放,在AddDevice返回控制前。
7. 将设备对象附加在设备栈上(IoAttachDeviceToDeviceStack)。
指定一个设备的PDO指针在TargetDevice参数中。
存储由IoAttachDeviceToDeviceStack返回的指针。这个指针,指向紧邻的下一层驱动程序的设备对象,是一个必需的用于IoCallDriver和PoCallDriver的参数,在设备栈中轮流向下传递IRPs的时候。
8. 清除在FDO或者过滤DO中的DO_DEVICE_INITIALIZING标记,使用类似下面的语句:
FunctionalDeviceObject->Flags &=~DO_DEVICE_INITIALIZING;
9. 准备好了为设备处理PnP IRPs。(比如 IRP_MN_QUERY_RESOURCE_REQUIREMENTS和IRP_MN_START_DEVICE)。
驱动程序不得启动控制设备,直到其接收到一个IRP_MN_START_DEVICE的请求,此IRP包含了PnP管理器为设备分配的硬件资源列表。
2) 总线驱动程序中的AddDevice例程
PnP总线驱动程序有一个AddDevice例程,但是在总线驱动程序作为一个用于它的控制器或者适配器的功能驱动程序时才被调用。例如,PnP管理器调用USB hub总线驱动程序的AddDevice例程来添加一个hub设备。这个hub驱动程序的AddDevice例程不会因为hub的子设备而被调用。
3) 编写AddDevice例程的指导方针
在编写一个AddDevice例程是要考虑下面的设计指导方针:
l 如果一个过滤驱动程序决定其AddDevice例程可以为它不需要服务的设备而被调用,那么过滤驱动程序必须返回STATUS_SUCCESS以允许其他的设备栈可以为这个设备而被加载。过滤驱动程序不会创建一个设备对象也不会将其附加到设备栈上;过滤驱动程序仅仅需要返回成功标记,以允许其他的驱动程序可以被添加到栈上。
l 驱动必须提供存储区,通常在设备对象的设备扩展中,为其使用的任何内核定义对象和独占的自旋锁。驱动程序也必须为从I/O管理器或者其他系统组件获得的某些对象的指针提供存储区。
¡ ExAllocatePoolWithTag用于分页或者为分页系统空间的内存。
¡ ExInitializePagedLookasideList或者ExInitializeNPagedLookasideList用于初始化一个分页的或者未分页的lookaside列表。
l 如果驱动程序有一个设备专用线程或者在等待任何内核定义的派遣对象,其AddDevice例程可能需要初始化这些内核派遣对象。
l 如果驱动程序使用任何执行的自旋锁或者为一个中断自旋锁提供存储区,其AddDevice例程可能需要初始化这些自旋锁。
l 加强完善文件打开的安全性,在调用IoCreateDevice。
指定FILE_DEVICE_SECURE_OPEN特性在调用IoCreateDevice时。这个特性被Windows NT4.0 SP5和更近的版本所支持。它指引I/O管理器对设备对象的所有打开请求执行安全性检查。厂家应该指定这个特性在调用IoCreateDevice的时候,如果FILE_DEVICE_SECURE_OPEN特性没有在设备的类型安装INF或者设备的INF中设置,还有驱动程序不对打开执行他们自己的安全性检查的话。
如果驱动程序设置FILE_DEVICE_SECURE_OPEN特性在其调用IoCreateDevice的时候,I/O管理器应用设备对象的安全描述符到任何相关的打开或者后缀文件名打开。例如,如果为/Device/foo设置了FILE_DEVICE_SECURE_OPEN,而且如果/Device/foo只能被管理员打开,那么/Device/foo/abc也可以被管理员打开。而I/O管理器会阻止标准用户打开/Device/foo和/Device/foo/abc。
如果一个设备的驱动程序设置了这个特性,PnP管理器会将此特性应用到这个设备的所有设备对象。