信息来源:邪恶八进制信息安全团队(
www.eviloctal.com)
原始连接:
http://www.longen.org/L-R/detaill-r/NTDriver.htm
含图PDF格式在末尾可以下....
一.Windows NT的结构
Windows NT的系统结构决定了NT下访问设备的特殊性。NT是建立在Mach和Vax思想上的一种客户/服务器模型操作系统,由一个特权执行体以及一系列被称为保护子系统的非特权服务器组成。整个操作系统被分为用户态模式和核心态模式。所谓特权,是指处理器的操作方式,大多数的处理器都有一种甚至若干种特权方式。在特权方式方式下,所有机器指令都可执行并且系统内存可存取。在非特权方式方式下,某些机器指令不能执行并且系统内存不可存取。在Windows NT下,核心态就是指处于特权处理器方式下,而用户态总处于非特权处理器方式。
核心态模式又被称为NT执行体,包括了系统服务和硬件描述层(HAL),它们运行于CPU的特权层Ring0(在驱动程序内部,在不同部分还分为不同权限层)。系统服务包含了一个操作系统的所有应有服务,文件系统,进程控制,内存管理,设备管理等等。NT中除了微内核外,另外还包含了一些独特的部分,如对象管理器,配置管理器,执行体支持,本地过程调用,安全监视器,所有这些都建立在HAL之上。IO管理器实现对设备的管理,包含了文件系统,中间介质和设备驱动。通过HAL,NT可以防止内核和NT执行体的其它部分受硬件平台不同的影响。另外NT还将网络管理器加入了核心态模式。用户态模式包括了一些保护子系统,如OS/2子系统,POSIX子系统,安全子系统,当然还有最常用的Win32子系统,NT都称之为服务器。它们运行于CPU的用户层Ring3,建立在服务器上的各类应用程序被称为客户。
二.NT中的设备驱动
在NT核心态包含了两种基本类型的驱动:用户模式驱动和核心态驱动。用户态驱动包含了Win32多媒体驱动,支持MS-DOS应用程序的VDD(虚拟设备驱动)和其它保护子系统的驱动.用户态驱动是一种特殊的子系统,详细的可以参见DDK中的多媒体驱动和虚拟DOS驱动文档。核心态驱动有针对逻辑的,虚拟的以及物理的设备.我们称之为NT驱动,它们为NT执行体的一部分,所谓的新技术(new technology)和基于微内核的操作系统,就是能支持一个或多个保护子系统,在这篇文章里,NT都只代表Windows NT执行体。NT包含了一些各自独立地定义了自己的功能的组件。对于设备驱动开发者来说,主要感兴趣的内核,I/O管理器,执行体支持,进程结构。另外可能还有对象管理器和安全监视器。对NT文件系统感兴趣的还包含Cache管理器。象NT一样,NT驱动由一些按照需求设计的离散的功能模块组成。所有NT驱动都有系统定义的标准驱动例程和一些由设计者决定的内在例程。
有三种类型的NT驱动,每种类型都有略微不同的结构和完全不同的功能。
设备驱动,例如直接控制物理设备的键盘和磁盘驱动.也称其为低级的驱动,因为它们都特别地处于分层的NT驱动链的最低层。
中间介质驱动,例如虚拟磁盘,镜像,或特殊类型的设备驱动,这些都依靠下层的设备驱动提供支持。
文件系统驱动,例如系统支持的FAT,HPFS,NTFS,CDFS,MSFS,NPFS或RDR驱动,它们一样需要低层驱动的支持。
Windows NT网络驱动也可归类于这三种驱动类型的一种,例如,一个NT服务器或重定向器是一种特殊的文件系统,传输驱动是一种中间介质的NT驱动,而物理网卡(有时也称为介质存取控制器MAC)驱动是一个NT设备驱动,然而NT提供特殊的接口支持网络驱动,例如针对物理网卡的NDIS 3.0(Network Device Interface Specification)。NT驱动是基于下列目标设计的:可移植性,硬件和软件可配置性,总是抢占和可中断的,在多处理平台上的安全性,基于对象,可重用的IO请求包,支持异步传输。由于NT运行于核心模式,NT驱动只能使用系统提供的RtlXxx运行库,文件系统的驱动还可以使用FsRtlXxx运行库。在NT核心态不支持浮点运算。大部分NT元件都用C语言编写,只有一小部分HAL和核心基于效率的原因用汇编语言写成,NT驱动同样也是使用C写.这样可以在不同平台上保证原码级兼容。驱动开发者不能使用系统不支持的C库,所以最好使用ANSI C标准。NT驱动开发者必需避免使用定义取决于系统的数据类型。
至于可配置性,NT配置管理器提供了一个数据库,叫做注册库,它包含了硬件,外围设备和给定机器的驱动信息。NT驱动可以使用注册库信息获得硬件配置信息。还有也可以通过NT的HAL原件,这是一个动态连接库,它响应系统中所有硬件层,包括NT驱动。HAL隐藏了平台硬件的特殊性,如caches,I/O总线,中断,等等.NT设备驱动只要调用HAL提供的例程,就可以获得一些硬件信息。
NT的抢先多任务不仅表现在用户界面上,在设备驱动级也是抢占和可中断的.在系统中每一个线程都被赋予优先属性,大部分的优先属性都是可变的,除非是实时优先,这种优先只有自己放弃控制.无论优先属性如何,当硬件或某种软件中断产生时,系统中的一些线程将被抢占.核心态对于给定平台的指派中断都定义了中断请求层IRQL,NT核心区分硬件和软件中断优先,一些核心态代码运行于较高的IRQL,例如NT驱动.一般的,若不带中断向量,线程运行于PASSIVE_LEVEL_IRQL,软件中断被分配于相对较低的IRQL(APC_LEVEL,DISPATCH_LEVEL,或核心调试,WAKE_LEVEL).设备中断具有较高的IRQL值,核心为系统临界中断保留最高级的IRQL,例如系统时钟或总线中断。一些核心态例程运行于PASSIVE_LEVEL_IRQL,这是因为核心态组件能设定自己的线程,或按照分页代码使用。大部分NT驱动例程运行于DISPATCH_LEVEL,设备驱动运行于设备IRQL(也叫DIRQL)。
如果是在多处理平台上,必须保证以下条件:所有处理器都是平等的,如果有协处理器的话,协处理器也得平等。所有处理器共享内存,并且统一存取内存。如果是在对称平台,每个处理器都能存取内存,获取中断,获取I/O控制注册。在非对称平台,主处理器获得辅助处理器的所有中断。Windows NT是设计成不用修改就既可以在单一处理器也可以在对称多处理器上运行的,所以NT的驱动也必须是一样的。为了在对称多处理器上安全地运行,操作系统就必须保证运行在一个处理器上的代码不能同时修改或存取正被另一个处理器使用着的数据。例如,NT设备驱动的对应于一个处理器的ISR必须独占临界区,驱动定义数据,设备注册数据。NT核心提供了一个spin lock的机制。用做保护数据。
Windows NT是基于对象的系统,在执行体内部,不同的组成部分定义为一个或多个对象类型。每一个NT元素导出各自的核心态模式工作列程,通过操作各自的对象类型调用列程。NT驱动和它们的设备也是基于对象的。对于系统中的其它元素,包括用户态代码设备表现为I/O管理器中的一个文件对象。在I/O系统中,每一个驱动的逻辑的,虚拟的,或者物理的设备都表现为设备对象。在I/O管理器中,每一个NT驱动的载入影像都表现为驱动对象,I/O管理器为文件对象,设备对象,驱动对象定义对象类型,同样adapter对象对应为系统DMA控制器和总线DMA控制卡,controller对象对应为相应设备的物理控制器。
控制操作格式:
PrefixOperationObject
前缀 操作符 对象
where
Prefix前缀
识别NT元素导出列程,定义对象类型。一般前缀为两个文字。
Operation操作符
描述对象的作用
Object对象
定义对象类型
例如,当NT驱动初始化时将调用IoCreateDevice一次或数次,为物理,逻辑或虚拟设备创建设备对象。
I/O管理器的主要工作是接收I/O请求(通常来自于用户模式的应用程序),创建I/O请求包,将IRP传递给合适的NT驱动。并且跟踪它们直到完成。并且为每个I/O操作的原始请求者返回状态。I/O管理器使用IRP和NT驱动通讯,并且允许NT驱动互相之间通讯。要注意的是一些IRP将传递给不止一个NT驱动。例如,在磁盘中打开文件这个请求将将手先传到文件系统驱动,经过中间介质的镜像驱动最终传到物理磁盘驱动。因此每一个IRP有一个固定的部分,还有一个或多个I/O的位置栈。在固定的部分,I/O管理器保持原始请求信息,例如调用者参数,关于哪一个文件打开的设备对象地址,等等。另外在固定的部分还包含I/O状态块,其中包含了请求操作的驱动信息。在高级驱动的I/O本地栈中,I/O管理器设置特殊的参数。
I/O管理器提供异步I/O,这样IRP的请求者能够继续执行,而不是等待IRP完成。NT驱动没有必要按照它们传递给I/O管理器的顺序处理I/O请求。I/O管理器或高级驱动在接收时可以重新排列I/O请求或将大数据的传输请求分离为小的传输请求。
NT保护子系统,例如WIN32子系统,通过I/O系统服务传递I/O请求到相应的核心态子系统。如上图所示。通过NT的I/O管理器提供的文件对象,子系统可以存取NT驱动设备或储存设备。在NT系统中,所有驱动对象以符号链接表示。
子系统调用NT的IO系统服务打开命名文件。
NT的IO管理器调用对象管理器,查询命名文件,并且帮助解决文件对象的符号连接。同时调用安全参照监视器,检查子系统是否具备打开文件句柄的正确权限。
如果NT文件系统不认识文件对象IO管理器挂起请求。调用多个文件系统直到识别出文件对象才继续请求。
IO管理器负责为打开的请求分配内存和初始化IRP。对于NT驱动,打开请求等同于创建请求。
IO管理器调用文件系统驱动,将IRP传递给它们。文件系统存取它们的IRP中本地IO栈,决定必须进行哪一种操作。检查参数,确定请求文件是否在缓存中。如果不是,设置下一个IRP中驱动的IO栈。
无论是驱动处理IRP还是完成IO请求操作。都调用IO管理器和其它NT元素提供的核心态例程。
驱动设置返回给IO管理器的IRP中的IO状态块表示请求操作是成功还是失败。
IO管理器通过获取IRP中的IO状态,将信息同过保护子系统返回给原始调用者。
IO管理器释放已完成的IRP。
如果打开操作成功,IO管理器返回文件句柄给子系统。反之返回错误状态。
注意在设计NT驱动时必须牢记以下几点:
NT驱动是分层的,不只一个的NT驱动可以处理单一的IRP.NT驱动使用IRP中的IO状态栈进行IO操作,处理请求成功还是失败的通讯.而IO管理器将这一结果传递给用户态请求者。NT驱动不需要也没必要设计成提供支持特殊应用.保护子系统或其特殊的子系统,用户态驱动已经提供了这些支持.但有一个例外:建立在专门应用设备上的MS-DOS应用程序可以请求NT驱动控制这一设备和相近的WIN32用户模式虚拟设备驱动.更多有关VDD的信息请参见DDK中的Virtual DOS Drivers文档.
上面的图表示了驱动是如何利用IO支持例程处理IRP读写请求.同时也详细表示了低级设备IRP的本地IO栈的细节.例如物理磁盘驱动。
IO管理器使用子系统读写请求分配的IRP调用文件系统驱动(FSD)。FSD存取它的IO位置栈。FSD存取IRP中IO位置栈决定哪一个操作将被调用。
FSD可以通过多次调用IO支持例程(IoAllocateIrp)分配IRP,将原始请求分成更小的请求。这样FSD可以得到已被全添成零的低级驱动IO位置栈,FSD可以根据判定重新使用原始IRP,甚至如图所示,通过设置在原始IRP中设置下一个低级驱动的IO位置栈,分配附加的IRP,并传递给低级驱动。
对于每一个已分配的驱动的IRP,FSD调用一个IO支持例程注册FSD提供的完成例程,用来判定低级驱动是否满意请求,并当低级驱动完成时释放每一个已分配的驱动IRP。接下来FSD调用IO支持循环存取在IRP中的下一个低级驱动IO位置栈。设置下一个低级设备的请求。当调用IRP时,物理设备驱动检查IO位置栈决定对目标设备进行何种操作(由IRP_MJ_XXX函数代码表示),
IO管理器判定设备驱动是否正在忙于处理目标设备的另一个IRP。如果是这样就排入队列并返回。否则就开始调用驱动支持例程处理设备的IO操作。
当设备被中断时。驱动的ISR停止设备并保存操作所必须的上下文,然后ISR调用IO支持循环将驱动DPC循环排队。完成优先级高的低级硬件请求操作。
当驱动的DPC获得控制。将使用保存的上下文完成IO操作(传递ISR的调用到IoRequestDpc)。DPC调用一个支持循环将下一个IRP出队,并将其传递给驱动,开始设备的IO操作。DPC然后设置已完成操作的IRP的IO状态块通过IoCompleteRequest将其返回给IO管理器。
IO管理器将低级驱动IRP中的IO位置栈清零并调用文件系统注册FSD分配IRP的完成的循环。完成循环检查IO状态块决定是重试请求还是更新原始请求保持的通用状态,并释放驱动分配的IRP。当完成IRP后,IO管理器返回NT状态给IO操作请求者。
NT设备使用以下基本请求:
IRP_MJ_CREATE – 打开目标设备对象,指明使用的IO操作.
IRP_MJ_READ – 从设备传输数据
IRP_MJ_WRITE – 传输数据到设备
IRP_MJ_DEVICE_CONTROL – 设置(或重设)设备,根据系统定义设备特殊的IO控制代码.
IRP_MJ_CLOSE – 关闭目标设备对象.
NT的IO管理器提供在系统已存在的驱动链中增加新驱动的支持.在驱动中设置线程将影响系统性能.NT驱动通常在驱动的设备对象部分(也称为设备扩展)保持其IO操作状态.
在设计NT驱动时牢记以下几点:
一个新的NT驱动必须处理一些系统支持的驱动中设置相同的IRP_MJ_XXX,如果驱动未在入口点定义IRP_MJ_XXX,否则IO管理器将对给定的针对目标设备的IO请求返回STATUS_INVALID_DEVICE_REQUEST。通过IRP_MJ_DEVICE_CONTROL设备驱动同样必须处理所需的IO控制代码。一个新的插入已存在的驱动链的NT中间驱动必须识别所替换的IRP_MJ_XXX。这些驱动只是简单地传递请求的IRP到下一个低级驱动,自己并不处理。一个低级的设备驱动只能存取其发送的IRP中自己的IO位置栈。一个高级驱动能存取自己和下一个低级驱动的IO本地栈。每一个NT驱动仅仅通过IRP的IO状态栈和高级驱动(通常是经过IO管理器的用户态应用)进行通讯。IO管理器在每一个驱动完成IRP后将IO本地栈清零。NT驱动的一部分使用IRP_MJ_INTERNAL_DEVICE_CONTROL定义了特殊的IO控制代码,用于将请求从高级部分传递给低级部分。
IO管理器定义驱动对象类型,并且使用驱动对象注册和跟踪载入的NT驱动镜像的信息。注意在驱动对象指定的入口点和主要的函数代码(IRP_MJ_XXX)保持通讯。
每一个IRP的IO管理器例程首先到达驱动指定的入口点,设备驱动的入口例程调用IoStartPacket将每个IRP排队,通过StartIo循环启动IO请求操作。高级NT驱动通常没有StartIo循环。
当NT驱动被载入时,DriverEntry被指向驱动的对象调用。DriverEntry在输入驱动对象中设置多个Dispatch入口点,IO管理器可以处理恰当的Dispatch循环。同时DriverEntry设置驱动的StartIo和Unload入口点。
三.核心态中的一些基本概念
1.I/O请求包(IRP)
NT下所有的I/O都是包驱动的。
IRP是I/O管理器在响应一个I/O请求时从非分页系统内存中分配的一块可变大小的数据结构内存。
IRP包括两部分:IRP标头和I/O堆栈。
a.IRP标头包含I/O请求的各种信息,其中有些驱动可以直接访问,另一些是IO管理器特有属性。以下是允许驱动访问的部分:
IO_STATUS_BLOCK IoStatus包含IO操作的最后状态,Status域被设置成STATUS_XXX,Information域设置成0或函数代码特定值
PVOID AssociatedIrp.SystemBuffer指向有缓冲IO驱动的系统缓冲区的指针
PMDL MdlAdress 指向直接IO设备的用户空间缓冲区描述符列表的指针
PVOID UserBuffer IO缓冲区的用户空间地址
BOOLEAN Cancel IRP是否被取消
b.IO堆栈主要存放IO请求的函数指针和参数:
UCHAR MajorFunction 指定操作的IRP_MJ_XXX函数
UCHAR MinorFunction有文件系统和SCSI驱动程序
union Parameters MajorFunction代码的联合类型
struct Read IRP_MJ_READ的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write IRP_MJ_WRITE的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceControl IRP_DEVICE_CONTOL和IRP_MJ_INTERNAL_DEVICE_CONTOL的参数
ULONG OutoutBufferLength
ULONG InputBufferLength
ULONG IocontrolCode
struct Others
DEVICE_OBJECT DeviceObject此IO请求的目标设备
PFILE_OBJECT FileObject此IO请求的文件对象
对于低级驱动,对应的IRP只有一个IO堆栈,高级驱动可能有若干IO堆栈。
处理IRP的函数有如下一些:
IoStartPacket发送IRP到Start IO例程,Dispatch调用
IoCompleteRequest指示所有处理已完成,DpcForIsr调用
IoStartNextPacket发送下一个IRP到Start IO例程,DpcForIsr调用
处理IRP堆栈的函数有如下一些:
IoGetCurrentIrpStackLocation得到调用者堆栈单元的指针,Dispatch调用
IoMarkIrpPending标记调用者的堆栈单元需要进一步处理,Dispatch调用
2.中断请求级(IRQL)
为了将不同CPU体系中不同的处理硬件优先级方法统一起来,NT使用了抽象的CPU优先级方案。即中断请求级。
IRQL是一个数,定义了CPU当前活动的重要性。
HIGHEST_LEVEL机器检查和总线错误(硬件)
POWER_LEVEL电源失效中断(硬件)
IPI_LEVEL多处理器系统处理器之间的门铃(硬件)
CLOCK2_LEVEL内部时钟2(硬件)
CLOCK1_LEVEL 内部时钟1(硬件)
PROFILE_LEVEL轮廓文件定时器(硬件)
DIRQLs IO设备中断的平台相关的级数(硬件)
DISPATCH_LEVEL线程调度器和延迟过程调用执行(软件)
APC_LEVEL异步过程调用执行(软件)
PASSIVE_LEVEL一般的线程执行级(软件)
当一个中断到达CPU时,处理器比较请求中断的IRQL和CPU当前的IRQL,如果等于或小于CPU当前IRQL,请求被堆栈挂起,直到CPU的IRQL低于请求时才进行。
延迟过程调用(DPC)
DPC就是对将要在较低一级IRQL上运行的代码进行延迟调用,改善系统事件响应。
DPC使用软件中断,推迟对时间要求不高的代码的执行,直到较高的IRQL活动结束。
当运行于较高IRQL的一段代码要在较低的IRQL上继续工作时,将被加入DPC队列,请求DPC中断。而此DPC中断不会被马上执行,只有在CPU的IRQL低于原来IRQL时才进行。
驱动对象DriverObject
当IO管理器寻找除DriverEntry例程以外的其它函数时,它使用于设备相关的Driver对象。这个对象是一个目录,含有指向各驱动函数的指针。
IO管理器在载入一个驱动时建立Driver对象,DriverEntry将指向各驱动函数的指针装入Driver对象。当IRP发送到特定设备时,IO管理器使用Driver对象找到正确的Dispatch例程。Driver对象还含有指向设备对象链表的指针。
5.设备对象DeviceObject和Device Extension
设备对象记录设备的特征和状态信息。驱动程序可访问以下域:
PVOID DeviceExtension指向Device Extension结构的指针
PDRIVER_OBJECT DriverObject指向这一设备的Driver对象的指针
ULONG Flags指定这一设备的缓冲策略
DO_BUFFERED_IO(IO管理器在每个IO操作开始,从非分页池分配缓冲区,并将地址传递给驱动程序,在传输大于一页数据时会降低速度,并占用太大非分页池)
DO_DIRECT_IO(提供对内存物理页的直接访问)
PDEVICE_OBJECT NextDevice指向驱动中下一个设备
UCHAR StackSize IRP所需IO堆栈的最小数目
ULONG AlighmentRequirement缓冲区要求的内存对齐
处理设备对象的一些函数有:
IoCreateDevice创建Device对象(DriverEntry)
IoCreateSymbolicLink使Device对象Win32可见(DriverEntry)
IoDeleteSymbolicLink从Win32名字空间删除Device对象(Unload)
IoDeleteDevice从系统删除Device对象(Unload)
Device Extension是IO管理器分配的Device对象的非分页池。必须将全局和静态变量放入Device Extension中。
Controller对象和Controller Extension
保证使用相同控制寄存器管理多个物理设备的适配卡正常工作。保证某一时刻对设备的唯一占有。
Controller对象中唯一可见的域是PVOID ControllerExtension。
Adapter对象
协调硬件对系统DMA的争夺。
Adapter对象对用户完全不可见。
Interrupt对象
Interrupt对象指定中断服务例程的位置。
Interrupt对象对用户完全不可见。
Interrupt对象的调用函数有:
HalGetInterruptVector将于总线有关的中断向量转化为系统范围的值(DriverEntry)
IoConnectInterrupt将ISR和系统中断向量联系起来(DriverEntry)
KeySynchronizeExecution同步在不同IRQL运行的驱动程序例程
IoDisconnectInterrupt删除Interrupt对象(Unload)
9.同步中断请求
当在不同IRQL上的代码试图同时访问相同数据结构,将出现中断同步问题。可以用中断阻塞和DPC来处理此问题。
中断阻塞可以临时提高CPU的IRQL,完成处理后再将IRQL恢复。
使用DPC将避免对数据结构的修改冲突。
10.主要例程一览
DriverEntry:IO管理器装入驱动时调用,执行初始化,建立例程指针,宣告资源,使设备名对系统可见。
Reinitialize:除DriverEntry以外的初始化例程。
Unload:解除资源,删除驱动对象。
ShutDown:将硬件恢复到已知状态。
StartIO:当调用IoStartPacket时,将启动StartIO例程。
ISR:中断服务例程。
DPC:处理设备操作后的清理工作,将完成的IO请求送回IO管理器,开始下一个设备操作。
ControllerControl:在一个外设卡支持多个设备时使用。
AdapterControl:拥有DMA使用权时调用。
SynchCritSection:由于ISR在DIRQL,而其它部分在DISPATCH_LEVEL或较低级执行,这些低级IRQL代码只能通过SynchCritSection接触ISR的资源。
Timer:跟踪时间的驱动例程。另外还有CustomTimeDpc。
IoCompletion:低级驱动在完成高级驱动的请求时,以此例程调用形式通知高级驱动。
CancelIo:若驱动在很长时间将等待请求,必须将此例程挂接到请求,,当请求被取消时负责清理操作。
11.资源的检测
设备驱动必须知道要管理的设备的控制寄存器,DMA能力,中断的IRQL,以及所需特定的内存。
在NT启动时,引导组件NTDETECT(RISC上是硬件ARC)搜集系统硬件信息,将其写入到Registry的\HARDWARE\DESCRIPTION下。
对于ISA设备,自动检测不能获得信息,只能使用其它方法找到硬件。
对于PCI和EISA设备就要好的多,可以用IoQueryDeviceDecription函数在注册中查找,它对匹配的请求信息调用ConfigCallBack扫描硬件信息,在IoQueryDeviceConfigurationData下的CM_PARTIAL_RESOURCE_DECRIPTOR包含了硬件的实际信息。包括口地址,中断向量,DMA,内存和设备特定信息。
当将数据取出后,必须将它们转换成系统范围内的等价值。
HalTranslateBusAddress可以将内存和寄存器从总线相关值转换成系统范围内的等价值。
HalGetInterruptVector将总线相关的中断信息转换成系统分配的向量,DIRQL和相似掩码值。
HalGetAdapter找到对特定设备进行DMA操作的Adadpter对象。
若是没有自动检测信息的ISA设备,可以将信息在用户态放进注册表,供设备驱动查询。
其它可以得到硬件信息的函数有HalGetBusData,它允许对特定总线的特定槽位进行查询。但只对PCI和EISA总线有效。
如果以上都无法使用,只好通过探询控制寄存器来查找硬件。
12. 资源的申请和释放
在注册库的\HARDWARE\RESOURCEMAP下维护了所有当前硬件的数据。
在驱动的资源中有两个.Raw和.Translated的值,前者包含了设备资源总线特定信息,后者为资源转换后的系统范围值。
在申明资源前必须构造一个要分派的资源列表,这是一个CM_RESOURCE_LIST类型的结构,通过将其传递给IoReportResourceUsage,请求CM_RESOURCE_LIST中项目的拥有权,并检查任何冲突。
对于动态分配资源的设备,可以使用HalAssignSlotResources来获得资源信息。
资源的释放可以构造一空的资源列表,然后调用IoReportResourceUsage。
13.映射设备内存
若设备使用专用的内存地址范围,必须调用HalTranslationBusAddress将总线相关的物理地址转换成系统值,然后用MmMapIoSpace将设备内存映射到相同虚拟地址空间。
在卸出驱动时调用MmUnmapIoSpace。
14.IO操作,扩展IO操作和IOCTL
常用IO操作有如下一些:
IRP_MJ_CREATE CreateFile
IRP_MJ_CLEANUP CloseHandle
IRP_MJ_CLOSE CloseHandle
IRP_MJ_READ ReadFile
IRP_MJ_WRITE WriteFile
IRP_MJ_DEVICE_CONTROL DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL没有Win32调用
IRP_MJ_QUERY_INFORMATION GetFileSize
IRP_MJ_SET_INFORMATION SetFileSize
IRP_MJ_FLUSH_BUFFER FlushFileBuffer FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN InitialSystemShutdown
IO管理器不允许用户增加新的IRP功能,只提供了IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL两个特定操作,前者既可以在用户态调用也可在其它驱动通过构造IRP调用,后者只在驱动中被调用。
IOCTL是一个特定结构,其包括四部分:
DeviceType指定IoCreateDevice的fILE_DEVICE_XXX值0x0000到0x7fff为MS保留0x8000的0xffff可用
ControlCode驱动定义的IOCTL代码0x000的0x7ff为MS保留0x800到0xfff可用
TransferType METHOD_BUFFER使用非分页池缓冲区处理数据,将地址放在IRP的AssociatedIrp.SystemBuffer上
METHOD_IN_DIRECT输入使用直接IO,输出使用缓冲,将地址放在IRP的MdlAddress上(内存描述表)
METHOD_OUT_DIRECT 输出使用直接IO,输入使用缓冲,同上
METHOD_NEITHER将用户空间地址放到IRP的UserBuffer
RequiredAccess在CreateFile时必须请求的访问权
FILE_ANY_ACCESS
FILE_READ_DATA
FILE_WRITE_DATA
FILE_READ_DATA|FILE_WRITE_DATA
15.同步例程
当一个低IRQL例程在使用共享资源时,可能到达一个中断,可以通过在一个SynchCritSection中放入接触这些共享资源的代码来解决这个同步问题。KeSynchCritSection可以将IRQL提升到Interrupt对象的DIRQL,获得该对象的Interrupt旋转锁,在SynchCritSection运行时保证代码不被Interrupt对象关联设备中断。例程结束后,KeSynchCritSection释放旋转锁,将IRQL降为其原来级别。
16.系统线程
系统线程是运行于内核模式中的线程,没有用户模式的上下文环境,不能访问用户地址空间。一般在以下情况下可以考虑使用线程:
设备速度很慢,并且不经常访问。
设备要花很长时间进行状态转换,并且驱动必须等待该转换的发生。
设备为了完成单个操作,必须进行多个状态的转换,并且必须轮询该转换一段时间。
需要执行只在PASSIVE_LEVEL_IRQL上的操作。
通常在DriverEntry中调用PsCreateSystemThread建立线程,在卸出时调用PsTeminateSystemThread。
线程的同步有时间同步和一般同步。
17.缓存IO和直接IO
缓存IO是IO管理器分配的一个非分页池,它足够大地存放调用者的输入输出数据。IO管理器将其地址放在IRP的AssoicatedIrp.SystemBuffer中。
当驱动申明直接IO时,IO管理器根据需要分配缓冲区,并将其锁定在物理内存中,为其创建内存描述表(MDL),将MDL的指针放在IRP的MdlAddress中。
18.IO空间寄存器和内存映射寄存器
IO空间寄存器是将设备寄存器映射到称为IO空间的一组地址。这些IO空间地址(即端口)不是CPU看到的内存空间的一部分,只能通过特定机器指令访问:
READ_PORT_XXX
WRITE_PORT_XXX
READ_PORT_BUFFER_XXX
WRITE_PORT_BUFFER_XXX
如果将设备寄存器映射到物理内存地址,就可以使用内存操作的装入和存储指令:
READ_REGISTER_XXX
WRITE_ REGISTER _XXX
READ_ REGISTER _BUFFER_XXX
WRITE_ REGISTER _BUFFER_XXX
四.PSPNT中的NT设备驱动
我们以杭州照排机为例子,讲述NT核心态的组成结构以及于用户态的作用关系。
DriverEntry部分
当启动相应服务时,系统创建驱动对象DriverObject, 调用DriverEntry。在DriverEntry中进行的工作有:
调用HalGetBusData遍历系统所有总线和槽位,寻找实际设备,若找不到则返回设备不存在。否则保存总线,槽位和PCI配置数据在PCI_COMMON_CONFIG结构中。(见三.11)
调用HalAssignSlotResources宣告在指定总线和槽位上的资源,在这里有六个资源。所有资源信息保存在CM_RESOURCE_LIST结构中。(见三.12)
若正确则调用HalGetInterruptVector映射中断向量。
在IRP中定义调用例程位置。定义DriverObject->MajorFunction[…]。
调用IoCreateDevice创建设备对象。清理Device对象的扩展结构,调用IoCreateSymbolicLink使Device对象Win32可见,记住要先调用RtlInitUnicodeString将字符Unicode。
保存设备资源到设备对象扩展结构中。从CM_RESOURCE_LIST结构获得IO,内存以及中断信息。并将其保存到设备对象扩展结构中。
调用IoInitializeDpcRequest注册DPC例程。
调用IoConnectInterrupt将中断向量和设备例程挂接。
调用IoCreateSynchronizationEvent创建同步信息。注意要先调用RtlInitUnicodeString将字符Unicode。
使用pDeviceObject->Flags |= DO_DIRECT_IO语句,将设备设置为直接IO。
调用HalTranslateBusAddress映射设备IO和内存。IO使用BufferIO。内存使用直接IO,调用MmMapIoSpace将物理地址映射到非分页系统空间。
分配核心态非分页内存,作为核心态内存链。
分配核心态非分页内存,作为核心态内存链。
分配核心态非分页内存,作为核心态内存链。
DPC部分
调用KeSetEvent设置内存链空事件,由于事件是传递给用户态的,按照规则要求,KeSetEvent必须工作在<=DISPATCH_LEVEL层。(见三.2)
中断工作在DIRQLs
线程调度器和延迟过程调用执行在DISPATCH_LEVEL
异步过程调用执行在APC_LEVEL
一般的线程工作在PASSIVE_LEVEL
线程调度器Dispatch
将IRP要求的服务例程指向各个函数。
在这里有IRP_MJ_CREATE,IRP_MJ_CLOSE,IRP_MJ_READ,IRP_MJ_WRITE以及IRP_MJ_DEVICE_CONTROL。IRP_MJ_DEVICE_CONTROL包含用户自定义的IO操作。
DriverObject->DriverStartIo例程StartIO是在当调用IoStartPacket时启动的,在这里我们是在发IRP_MJ_WRITE指令时调用IoStartPacket,启动StartIO的。基于安全的考虑,在IRP_MJ_WRITE中首先将IRP挂起,然后启动设备IO。在StartIO中,由于是直接IO,我们首先调用MmGetSystemAddressForMdl,将用户态传递的数据地址空间(IRP中的MDL)转换成系统空间(见三.1)。然后从IRP的本地堆栈中取出用户态传递的数据长度。由于使用中断的原因,必须保证同步向核心态送数据和ISR。在这里我们调用KeSynchronizeExecution完成此项工作。(见三.15)KeSynchronizeExecution指向同步函数。最后继续下一个IRP的传递(IoStartNextPacket),并且告诉IO管理器完成此IRP操作(IoCompleteRequest)。
同步函数
将用户层的数据转移到核心态数据链储存起来,并且检查此数据链是否有空的部分,若有就向用户态发送送数据事件。
ISR
当中断到来时,首先检查中断,然后将核心态数据发送到数据总线,即物理设备地址空间。并通知DPC向用户态发送送数据事件。(不能在DIRQLs设置事件)最后清理中断源。
卸出例程
处理清扫工作,关闭事件句柄,解除中断联系,释放核心态内存,释放资源,端口和内存映射,删除Win32符号联接,删除设备。
附注:在自定义的初始化设备例程中还要设置卡的中断源和一些初始化工作,如设置寄存器,清内存链,清事件等等。在ISR中最后记住清中断。