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对象时可以看到。
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结构体指针
);
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并不是一般意义上的分区,它对应整个物理硬盘设备
对象。
开始驱动程序设计
很好
[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]