极速论坛's Archiver

niuboy 发表于 2007-6-18 00:29

Windows NT驱动程序设计基础

NT驱动程序设计当然远不是能在短短的一篇文章里说清楚的,

也不是我能说明白的,毕竟我也只是最近一段时间才开始接

触设计NT驱动程序。在本文里只是将自己在做驱动程序最开

始的时候比较模糊的东西组织到了一起。设计NT驱动程序,

最重要的资料当然是NT DDK文档。

NT驱动程序的分层结构

在大多书操作系统中,驱动程序是指管理某个外围设备的一

段代码。NT采用更灵活的方法,允许杂应用程序和硬件之间

存在几个驱动程序层次。这个分层允许NT更加广泛地定义驱

动程序,包括文件系统、逻辑卷管理器和各种网络组件,以

及各种物理设备驱动程序。

1、 设备驱动程序

这些是管理实际数据传输和控制特定类型的物理设备的操作

的驱动程序,包括开始和完成I/O操作,处理中断和执行特定

设备要求的任何差错处理。

2、 中间驱动程序

NT允许在物理设备驱动程序上分层任意树木的中间驱动程序。

这些中间层次提供扩展I/O系统的功能一种方法,而不必修改

底层的驱动程序。在我们所开始的光盘塔驱动程序中,就使

用了这种分层结构,在设计过程中,我们并不用关心如何去

与SCSI卡打交道,SCSI卡厂商和Microsoft提供的SCSI小端口

程序和SCSI端口程序为我们解决了这个问题。我们需要做的

就是在这些端口程序的基础上增加新的驱动程序层次,满足

我们对设备进行控制的要求。

3、 文件系统驱动程序(FSD)

FSD是一类比较特殊的驱动程序,通常负责维护各种文件系统

所需要的磁盘结构。注意我们并不能使用DDK来开发FSD,而

必须使用Microsoft的文件系统开发人员工具包,对此我们了

解甚少,但我们想要对光盘塔开发出独特而且更有价值的应

用,我们应该针对光盘塔开发我们自己的文件系统。

SCSI驱动程序

针对我们的实际光盘塔应用,有必要对SCSI驱动程序先做一些

介绍。SCSI驱动程序在整个NT设备驱动程序中都是比较特使的

一类驱动程序。NT SCSI体系结构使用分层的驱动程序分开特定

设备的管理与SCSI总线适配器(HBA)的控制。其结构如下所示。

SCSI端口程序是由Microsoft提供的组件,通常处理常见的SCSI

工作和隐藏本地操作系统的细节。SCSI小端口程序提供任何HBA

特定的控制操作的例程,一般由提供HBA产品的厂商提供。

SCSI类驱动程序城关特定类型的所有SCSI设备,而不管它们连

接到什么样的HBA上。例如有磁带机、磁盘、CD-ROM等的类驱

动程序。我们开发的jbChanger就是SCSI类驱动程序,管理所

有的媒质交换设备(光盘塔)。

一般比较少写SCSI过滤驱动程序,SCSI过滤驱动程序和NT的其

它类型的过滤驱动程序一样,它截获和修改高层发送给SCSI类

驱动程序的请求。这样就允许利用现有类驱动程序的功能,而

不必从头开始写所有程序。

NT内核模式对象

在我们的实际开发过程中的对象是SCSI设备,由于SCSI端口驱

动程序已经隐藏了硬件控制操作,因此我在这里不讲述跟硬件

相关的部分。如果今后的开发对象不同,需要对硬件进行操作

的时候,可能会对中断、DMA等有比较详细的了解,这些内容

可以参考DDK帮助。

NT使用对象技术管理所有的数据,下面分别对一般驱动程序所

涉及的一些对象做一介绍。不过在介绍这些对象之前,有必要

先对驱动程序的结构做一介绍。

驱动程序结构

NT驱动程序和一般的DOS/Windows C语言程序不一样,它没有

main()或者WinMain()函数入口。和DLL类似地,它向操作系

统显露一个名称为DriverEntry()的函数,在启动驱动程序的

时候,操作系统将调用这个入口。DriverEntry除了做一些必

要的设备初始化工作外,还初始化一些Dispatch例程入口。

我们知道,NT和设备驱动程序打交道主要是通过CreateFile、

ReadFile、WriteFile 和DeviceIoControl等Win32 API来进行

的。这些API其实都对应着驱动程序的一些Dispatch例程。而

驱动程序除了DriverEntry以外,主要就是由这些Dispatch例

程组成的。例如调用Win32 API CreateFile的时候,操作系

统最终转化为对驱动程序IRP_MJ_CREATE功能代码所对应的

Dispatch例程的调用,如果驱动程序没有提供该例程,

CreateFile调用就会失败。

NT中一些常用的功能代码和Win32 API的对象关系如下所示。

功能代码 说明

IRP_MJ_CREATE 打开设备CreateFile

IRP_MJ_CLEANUP 在关闭设备时,取消挂起的I/O请求CloseHandle

IRP_MJ_CLOSE 关闭设备CloseHandle

IRP_MJ_READ 从设备获得数据ReadFile

IRP_MJ_WRITE 向设备发送数据WriteFile

IRP_MJ_DEVICE_CONTROL

对用户模式或内核模式客户程序可用的控制操作

DeviceIoControl

IRP_MJ_INTERNAL_DEVICE_CONTROL

只对内核模式客户程序可用的控制操作

没有对应的Win32 API

IRP_MJ_QUERY_INFORMATION 得到文件的长度GetFileLength

IRP_MJ_SET_INFORMATION 设置文件的长度SetFileLength

IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或丢弃输入缓冲区

FlushFileBuffers

FlushConsoleInputBuffer

PurgeComm

IRP_MJ_SHUTDOWN 系统关闭InitialSystemShutdown

和上面的驱动程序支持的功能代码相对应,一般的驱动程序

看起来就象下面的样子。

DriverEntry(…) // 驱动程序入口

{



DeviceObject->MajorFunction[IRP_MJ_CREATE] = XXDriverCreateClose;

DeviceObject->MajorFunction[IRP_MJ_CLOSE] = XXDriverCreateClose;

DeviceObject->MajorFunction[IRP_MJ_READ] = XXDriverReadWrite;

DeviceObject->MajorFunction[IRP_MJ_WRITE] = XXDriverReadWrite;



}

XXDriverCreateClose(…) // 对应IRP_MJ_CREATE和IRP_MJ_CLOSE的例程

{

//……….

}

XXDriverDeviceControl(…)// 对应IRP_MJ_DEVICE_CONTROL的例程

{

//……….

}

XXDriverReadWrite(…) // 对应IRP_MJ_READ和IRP_MJ_WRITE的例程

{

//……….

}

一个驱动程序并不需要支持所有的功能代码,比如如果一个

驱动程序根本就不必要与用户模式客户程序交互,那么就不

用支持IRP_MJ_CREATE和IRP_MJ_CLOSE。又如设备不支持设备

读写,就不用支持IRP_MJ_READ和IRP_MJ_WRITE。

驱动程序对象

驱动程序对象是在操作系统启动驱动程序、在调用驱动程序

入口DriverEntry之前就已经创建好了的,并且作为DriverEntry

函数的参数传递给驱动程序。如果驱动程序启动失败,操作

系统将删除该对象。该对象的数据结构如下。注意下表并不

是完整地列出了ntddk.h中的DEVICE_OBJECT结构体的所有数

据项,这里仅列出了一般驱动程序可能使用到的数据项。

Driver对象数据项 说明

PDEVICE_OBJECT DeviceObject

由本驱动程序创建的Device对象的链表

ULONG Flags

PDRIVER_INITIALIZE DriverInit

驱动程序初始化例程(一般较少用)

PDRIVER_STARTIO DriverStartIo

StartIo例程入口,一般该例程对低层设备驱动程序用得较多,

高层驱动程序较少使用本例程。

PDRIVER_UNLOAD DriverUnload

卸载驱动程序例程,如果想在控制面版的设备Applet里停止该设

备,应该提供本例程。

PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]

驱动程序的Dispatch例程表

Driver对象的结构图如下图所示。

在上面提到过驱动程序是管理同类型的所有设备,所以上面的

结构中DeviceObject指向的就不是单个的设备对象,而是一个

对象链表,这个链表的维护在下面介绍Device对象时可以看到。

niuboy 发表于 2007-6-18 00:30

vDevice对象与Device Extension

驱动程序在调用IoCreateDevice函数成功后就创建了一个Device

对象。下面对Device对象几个比较重要的数据做一介绍。

Device对象数据项 说明

PVOID DeviceExtension

指向Device Extension结构的指针(见下面)

PDRIVER_OBJECT DriverObject

指向这个设备的Driver对象的指针,IoCreateDevice会

自动填写本数据。

ULONG Flags 指定这个设备的缓冲策略。

DO_DIRECT_IO

DO_BUFFERED_IO

PDEVICE_OBJECT NextDevice

指向属于这个驱动程序的下一个设备对象,依靠本数据来维护设

备对象链表。

CCHAR StackSize

发送到这个设备的IRP需要的I/O堆栈单元的最小数目,一般对

分层驱动程序来说,本数据应该比其下层设备的大1。

ULONG AlignmentRequirement

缓冲区要求的内存对齐,一般对分层驱动程序来说,本值应该

和其下层设备的对齐一致。

Device对象的结构如下图所示。

Device记录着设备的特征和状态信息,对系统上的每个虚拟

的、逻辑的和物理的设备都有一个Device对象。例如对一个

硬盘驱动程序,对一个物理硬盘有一个名称为Partition0的

Device对象,对应整个物理磁盘,同时对硬盘的每个分区,

也都有一个Device对象,它们的名称分别为PartitionX(X从

1开始,每个分区对应一个数字)。

Device Extension是连接到Device对象的一个很重要的数据

结构,它的数据结构是由驱动程序设计者自己来确定的,在

调用IoCreateDevice的时候应该指定它的大小,Device

Extension其实是由操作系统在非分页内存池中为每个Device

对象分配的一块内存。由于驱动程序必须是完全可重入的,

因此使用任何全局变量和静态变量都不是好的办法,一般来

说和设备有关的任何需要保持的信息都应该放到Device

Extension里去。

设备的缓冲策略也必须提一下,这里的Flag的缓冲策略主要

决定设备读写(功能代码IRP_MJ_READ和IRP_MJ_WRITE)时候的

缓冲策略,另外功能代码IRP_MJ_DEVICE_CONTROL时候的缓冲

策略是由IOCTL控制代码本身来决定的。两者不能混为一谈。

在下面我将专门用一节来讨论I/O的缓冲策略。

I/O请求包(IRP)

在上面的结构里面已经出现了IRP了,在这里对它做一说明。

在NT中,几乎所有的I/O都是包驱动的,可以说驱动程序和

操作系统其他部分都是通过I/O请求包来进行交互的。我们

来看看一个I/O请求的执行过程。

(1) 操作系统的I/O管理器从非分页内存分配一个IRP,响应

一个I/O请求。基于由客户指定的I/O函数,I/O管理器将该

IRP传递给合适的驱动程序的Dispatch例程。

(2) Dispatch例程检查请求的参数是否有效,如果有效,

驱动程序根据请求的内容进行一系列的操作。否则设置错

误状态信息直接返回。

(3) 操作完成时,将数据(如果有)和状态信息存放到IRP中

并返回给I/O管理器。

(4) I/O管理器对返回的IRP进行适当的处理后将最后状态和

数据(如果有)返回给用户。

一个IRP的主要数据项如下表所示。

IRP主要数据项 说明

IO_STATUS_BLOCK IoStatus

存放I/O请求的状态

PVOID AssociatedIrp.SystemBuffer

如果设备执行缓冲I/O,则为指向系统空间缓冲区的指针。

否则为NULL。

PMDL MdlAddress

如果设备执行直接I/O,指向用户空间缓冲区的内存描述表的指针

PVOID UserBuffer

I/O缓冲区的用户空间地址

BOOLEAN Cancel 指示IRP已被取消

关于AssociatedIrp.SystemBuffer、MdlAddress和UserBuffer将在

下面的I/O缓冲区策略里面更详细地讨论。

NT还有更多其他的对象,例如中断对象、Controller对象、定时器

对象等等,但在我们开发的光盘塔驱动程序中并没有用到,因此在

这里不做介绍。

I/O缓冲策略

很明显的,驱动程序和客户应用程序经常需要进行数据交换,但我

们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操

作系统必须解决两者之间的数据交换。这就就设计到设备的I/O缓

冲策略。

读写请求的I/O缓冲策略

前面说到通过设置Device对象的Flag可以选择控制处理读写请求的

I/O缓冲策略。下面对这些缓冲策略分别做一介绍。

1、 缓冲I/O(DO_BUFFERED_IO)

在读写请求的一开始,I/O管理器检查用户缓冲区的可访问性,然

后分配与调用者的缓冲区一样大的非分页池,并把它的地址放在

IRP的AssociatedIrp.SystemBuffer域中。驱动程序就利用这个

域来进行实际数据的传输。

对于IRP_MJ_READ读请求,I/O管理器还把IRP的UserBuffer域设置

成调用者缓冲区的用户空间地址。当请求完成时,I/O管理器利用

这个地址将数据从驱动程序的系统空间拷贝回调用者的缓冲区。对

于IRP_MJ_WRITE写请求,UserBuffer被设置为NULL,并把用户缓冲

区的数据拷贝到系统缓冲区中。

2、 直接I/O(DO_DIRECT_IO)

I/O管理器首先检查用户缓冲区的可访问性,并在物理内存中锁定

它。然后它为该缓冲区创建一个内存描述表(MDL),并把MDL的地址

存放在IRP的MdlAddress域中。AssociatedIrp.SystemBuffer和

UserBuffer都被设置为NULL。驱动程序可以调用函数

MmGetSystemAddressForMdl得到用户缓冲区的系统空间地址,从而

进行数据操作。这个函数将调用者的缓冲区映射到非分页的地址空

间。驱动程序完成I/O请求后,系统自动从系统空间解除缓冲区的

映射。

3、 这两种方法都不是

这种情况比较少用,因为这需要驱动程序自己来处理缓冲问题。

I/O管理器仅把调用者缓冲区的用户空间地址放到IRP的UserBuffer

域中。我们并不推荐这种方式。

IOCTL缓冲区的缓冲策略

IOCTL请求涉及来自调用者的输入缓冲区和返回到调用者的输出

缓冲区。为了理解IOCTL请求,我们先来看看WIN32 API

DeviceIoControl函数的原型。

BOOL DeviceIoControl (

HANDLE hDevice, // 设备句柄

DWORD dwIoControlCode, // IOCTL请求操作代码

LPVOID lpInBuffer, // 输入缓冲区地址

DWORD nInBufferSize, // 输入缓冲区大小

LPVOID lpOutBuffer, // 输出缓冲区地址

DWORD nOutBufferSize, // 输出缓冲区大小

LPDWORD lpBytesReturned, // 存放返回字节数的指针

LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped结构体指针

);

niuboy 发表于 2007-6-18 00:30

IOCTL请求有四种缓冲策略,下面一一介绍。

IOCTL请求有四种缓冲策略,下面一一介绍。

1、 输入输出缓冲I/O(METHOD_BUFFERED)

I/O管理器首先分配一个非分页池,它足够大地存放调用者的输

入或输出缓冲区(不管哪个更大)。非分页缓冲区的地址放在IRP的AssociatedIrp.SystemBuffer域中,然后把IOCTL的输入数据拷贝

到这个非分页缓冲区中,并把IRP的UserBuffer域设置成调用者输

出缓冲区的用户空间地址。当驱动程序完成IOCTL请求时,I/O管理

器将这个非分页缓冲区中的数据拷贝到调用者的输出缓冲区。

注意这里同一个非分页池同时用于输入和输出缓冲区,因此驱动

程序在向缓冲区写东西之前应该把输入的所有数据读出来。

2、 直接输入缓冲输出I/O(METHOD_IN_DIRECT)

I/O管理器首先检查调用者输入缓冲区的可访问性,并在物理内存

中将其锁定。然后为该输入缓冲区创建一个MDL,并把指定该MDL的

指针存放到IRP的MdlAddress域中。

同时,I/O管理器还在非分页池中分配一输出缓冲区,并把这个缓冲

区的地址存放在IRP的AssociatedIrp.SystemBuffer域中,并把IRP

的UserBuffer域设置成调用者输出缓冲区的用户空间地址。当驱动

程序完成IOCTL请求时,I/O管理器将非分页缓冲区中的数据拷贝到

调用者的输出缓冲区。

3、 缓冲输入直接输出I/O(METHOD_OUT_DIRECT)

I/O管理器首先检查调用者输出缓冲区的可访问性,并在物理内存中

将其锁定。然后为该输出缓冲区创建一个MDL,并把指定该MDL的指针

存放到IRP的MdlAddress域中。

同时,I/O管理器还在非分页池中分配一输入缓冲区,并把这个缓冲

区的地址存放在IRP的AssociatedIrp.SystemBuffer域中, 同时把

调用者用户输入缓冲区中的数据拷贝到系统缓冲区中,并把IRP的

UserBuffer域设置为NULL。

4、 上面三种方法都不是(METHOD_NEITHER)

I/O管理器把调用者的输入缓冲区的地址放到IRP当前I/O堆栈单元的Parameters.DeviceIoControl.Type3InputBuffer域中,把输出缓冲

区的地址存放到IRP的UserBuffer域中。这两个地址都是用户空间地

址。

从上面的说明可以看出,在执行缓冲I/O时,I/O管理器将在非分页池

中分配内存,如果调用者的缓冲区比较大时,分配的非分页池也将

比较大。非分页池是系统比较宝贵的资源,因此,如果调用者的缓

冲区比较大时,我们一般采用直接I/O的方式(例如磁盘读写请求等),

这样不仅节省系统资源,另一方面由于省去了I/O管理器在系统缓冲

区和调用者缓冲区之间的数据拷贝,也提高了效率,这对存在大量

数据传送的驱动程序尤其明显。

可以注意到DDK中的Samples下,几乎所有的例程的读写请求都是直

接I/O的,而对于IOCTL请求则是缓冲区I/O的居多。

下面以changerDisk的IRP_MJ_DEVICE_CONTROL例程中的一段程序来

加强IOCTL缓冲策略的认识和用法。在该例中所有的IOCTL请求的缓

冲策略都是METHOD_BUFFERED。

NTSTATUS

ChangerDiskDeviceControl(

PDEVICE_OBJECT DeviceObject,

PIRP Irp

)

{

PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);

ULONG ControlCode;

ULONG InputLength, OutputLength;

PVOID InputBuffer, OutputBuffer;



ControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

// IOCTL请求功能代码存放在IRP当前I/O堆栈单元的

// Parameters.DeviceIoControl.IoControlCode中。;

InputLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;

// 输入缓冲区的大小存放在IRP当前I/O堆栈单元的

// Parameters.DeviceIoControl.InputBufferLength中。

OutputLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

// 输出缓冲区的大小存放在IRP当前I/O堆栈单元的

// Parameters.DeviceIoControl. OutputBufferLength中。

InputBuffer = OutputBuffer = Irp->AssociatedIrp.SystemBuffer;

// 由于本例中所有的IOCTL请求都是输入输出缓冲的,所以输入输出缓冲区

// 的地址就是IRP的AssociatedIrp.SystemBuffer域。

….

}

NT和Win 32设备名

NT设备有多个名称,在函数IoCreateDevice创建Device对象时

指定的名称是设备对Windows NT Executive知道的名字。如果

要时设备对Win32 子系统和DOS虚拟机可用,还必须给设备一个

DOS名字。

这两个名字位于对象管理器的名字空间的不同部分。NT设备名在

树的\Device下面。而Win32名则出现在\DosDevices下面。同一

Device对象的NT设备名和DOS设备名通过符号连接相联系。运行

Win32 SDK带的WINOBJ.EXE可以比较清楚地看到这种关系。

上图中我们看到的就是当前系统所有的NT设备名。请注意一下图

中右边列表中的Harddisk0、Harddisk1、Harddisk2和windfs,

它们并不是NT设备名,它们是目录对象,

在他们下面包含的才是一个个的NT设备名。这里稍微做一下解释,

图中的HarddiskX分别对象系统的第0、1和2个硬盘。对于硬盘来

说,驱动程序应该为它所管理的每个物理硬盘创建一个Device对

象,同时也必须为磁盘的每个逻辑分区创建一个Device对象,那

么每个物理硬盘会有好几个Device对象,为了更好地组织这种关

系,可以在NT的名字空间里创建一个目录对象,然后再在这个目

录下创建每个Device对象。上图所示的是所有的设备的Win32名

字。我们可以调用Win32 API CreateFile函数来打开列出的这些

名字的设备。例如图中的PhysicalDriverX所对应的就是系统中的

每个物理硬盘。而图中的c:,d:,。。。对应的就是硬盘的逻辑

分区了。这就是通过符号连接建立起来的联系,我们在下面的图

中可以看到这中联系。

上图是用WINOBJ查看WIN32设备名d:时的情况,它清楚地表明WIN32

设备名d:对应的NT设备名为\Device\Harddisk0\Partition2,说

明逻辑盘号D对应的是第0个硬盘的第2个分区。注意NT设备名

Partition0并不是一般意义上的分区,它对应整个物理硬盘设备

对象。

开始驱动程序设计

tong1429 发表于 2008-8-20 15:14

很好

[color=black]094◎在街上看[/color][url=http://www.igqb.cn/][color=black]传奇世界私服网[/color][/url][color=black]美女,目光高一点就是欣赏,目光低一点就是流氓。
095◎孩儿他娘,咱这辈子还有[/color][url=http://chuanqi.fsifu.com/][color=black]热血传奇私服[/color][/url][color=black]很多事要做呢,别耽误功夫和我玩捉迷藏了,赶紧蹦出来吧~~~
096◎女人一生喜欢[/color][url=http://moyu.fsifu.com/][color=black]新开魔域私服[/color][/url][color=black]两朵花:一是有钱花,二是尽量花!
097◎一炮走红——是形容女艺人的……
098◎这个世界不公平就在于:上帝说[/color][url=http://www.igxy.com/game/runescape/][color=black]runescape[/color][/url][color=black]:“我要光!”于是有了白天。美女说:“我要钻戒!”于是她有了钻戒。富豪说:“我要女人!”于是他有了女人。我说:“我要洗澡!”居然停水了!
099真不明白,女孩买很多很多漂亮衣服穿,就是为了吸引男孩的目光,但男孩想看的,却是不穿衣服的女孩。
100◎偶尔幽生活一默你会觉得[/color][url=http://bbs.fsifu.com/][color=black]私服论坛[/color][/url][color=black]很爽,但生活幽你一默就惨了……[/color]

页: [1]

Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.