如何写驱动程序.docx

上传人:b****7 文档编号:10456852 上传时间:2023-02-11 格式:DOCX 页数:15 大小:68.43KB
下载 相关 举报
如何写驱动程序.docx_第1页
第1页 / 共15页
如何写驱动程序.docx_第2页
第2页 / 共15页
如何写驱动程序.docx_第3页
第3页 / 共15页
如何写驱动程序.docx_第4页
第4页 / 共15页
如何写驱动程序.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

如何写驱动程序.docx

《如何写驱动程序.docx》由会员分享,可在线阅读,更多相关《如何写驱动程序.docx(15页珍藏版)》请在冰豆网上搜索。

如何写驱动程序.docx

如何写驱动程序

我这里重点的介绍如何写驱动程序,对于一些应用程序我就不做介绍了,因为我对于那些高层的东西写得很少。

倘若再讲,有班门弄斧之嫌,呵呵!

作为WIN98和WIN2K推荐的一项新技术来说,USB的驱动程序和以往的直接跟硬件打交道的WIN95的VXD的方式的驱动程序不同,它应该是WDM类型的。

USB的WDM接口框图如下(这个图可以说是USB软件总体框图)

对于HID的设备,就可以采用上图左上边的结构,其它类的话采用右上的结构,其实右边的结构可以又细分成两层,一层是ClassDriver,一层是MiniportDriver。

而倒数第三行的UHCD和OpenHCI分别是由INTEL和COMPAQ两位老大定的一个和硬件有关的底层驱动程序标准,各位可以根据所需要的选择。

对于USB的驱动程序,大家还得去了解WDM驱动程序的写法,或者早些时候的NT驱动程序,其实WDM驱动程序可以看做是NT驱动程序的一个update,只是增加了一些新的特性。

“写驱动程序是一个很漫长和繁琐的工作,在此之前,你最好要熟悉硬件,熟悉C/C++,还要用过DDK,会用一些调试程序,如SOFTICE和WINDBG之类。

如果一切就绪,你就可以开始写驱动程序,工作的进程有时侯会取决于你的运气”。

(这是一位留美的朋友对我说的,我写出来和大家共享)

下面是我从一个朋友那里得到的一篇文章的摘要:

NT驱动程序的分层结构

驱动程序是指管理某个外围设备的一段程序代码。

NT采用更灵活的分层驱动方法,允许杂应用程序和硬件之间存在几个驱动程序层次。

分层机制允许NT更加广泛地定义驱动程序,包括文件系统、逻辑卷管理器和各种网络组件,各种物理设备驱动程序等等。

1、设备驱动程序

这些是管理实际数据传输和控制特定类型的物理设备的操作的驱动程序,包括开始和完成I/O操作,处理中断和执行特定的设备要求的任何差错处理。

2、中间驱动程序

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

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

这也是微软鼓吹的他们的系统灵活的一面!

实际上我觉得这样反而牺牲了一些效率上的东西。

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

FSD是一类比较特殊的驱动程序,通常负责维护各种文件系统所需要的磁盘结构。

注意我们并不能使用DDK来开发FSD,而必须使用Microsoft的文件系统开发人员工具包。

一般比较少写中间过滤驱动程序,过滤驱动程序它截获和修改高层发送给类驱动程序的请求。

这样就允许利用现有类驱动程序的功能,而不必从头开始写所有程序。

NT内核模式对象在我们的实际开发过程中的对象是设备,由于端口驱动程序已经隐藏了硬件控制操作,因此我在这里不讲述跟硬件相关的部份。

如果今后的开发对象不同,需要对硬件进行操作的时候,可能会对中断、DMA等有比较详细的了解,这些内容可以参考DDK帮助。

NT使用对象技术管理所有的数据,下面分别对一般驱动程序所涉及的一些对象做一介绍。

不过在介绍这些对象之前,有必要先对驱动程序的结构做一介绍。

 

驱动程序结构

NT驱动程序和一般的DOS/WindowsC语言程序不一样,它没有main()或者WinMain()函数入口。

和DLL类似地,它向操作系统显露一个名称为DriverEntry()的函数,在启动驱动程序的时候,操作系统将调用这个入口。

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

我们知道,NT应用和设备驱动程序打交道主要是通过CreateFile、ReadFile、WriteFile和DeviceIoControl等Win32API来进行的。

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

而驱动程序除了DriverEntry以外,主要就是由这些Dispatch例程组成的。

例如调用Win32APICreateFile的时候,操作系统最终转化为对驱动程序IRP_MJ_CREATE功能代码所对应的Dispatch例程的调用,如果驱动程序没有提供该例程,CreateFile调用就会失败。

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

功能代码

说明

IRP_MJ_CREATE

备CreateFile

IRP_MJ_CLEANUP

备时,取消挂起的I/O请求CloseHandleIRP_MJ_CLOSE

备CloseHandle

打开设

在关闭设

关闭设

IRP_MJ_READ

获得数据ReadFile

IRP_MJ_WRITE

设备发送数据WriteFileIRP_MJ_DEVICE_CONTROL

模式客户程序可用的控制操作DeviceIoControlIRP_MJ_INTERNAL_DEVICE_CONTROL

控制操作

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;//XX对应的是你自己给你的驱动程序的命名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_OBJECTDeviceObject

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

ULONGFlagsPDRIVER_INITIALIZE

DriverInit

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

PDRIVER_STARTIODriverStartIo

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

PDRIVER_UNLOADDriverUnload

卸载驱动程序例程,如果想在控制面版的设备里停止该设备,应该提供本例程。

PDRIVER_DISPATCH

MajorFunction[IRP_MJ_MAXIMUM_FUNCTION

+1]

驱动程序的Dispatch例程表

在上面提到过驱动程序是管理同类型的所有设备,所以上面的结构中DeviceObject指向的就不是单个的设备对象,而是一个对象链表,这个链表的维护在下面介绍Device对象时可以看到。

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

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

Device对象数据项

说明

PVOID

DeviceExtension

指向DeviceExtension结构的指针

PDRIVER_OBJECT

DriverObject

指向这个设备的Driver对象的指针,IoCreateDevice会自动填写本数据。

ULONGFlags

指定这个设备的缓冲策略

PDEVICE_OBJECT

NextDevice

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

CCHARStackSize

发送到这个设备的IRP需要的I/O堆栈单元的最小数目一般对分层驱动程序来说,本数据应该比其下层设备的大1

Device记录着设备的特徵和状态信息,对系统上的每个虚拟的、逻辑的和物理的设备都有一个Device对象。

例如对一个硬盘驱动程序,对一个物理硬盘有一个名称为Partition0的Device对象,对应整个物理磁盘,同时对硬盘的每个分区,也都有一个Device对象,它们的名称分别为PartitionX(X从1开始,每个分区对应一个数字)。

DeviceExtension是连接到Device对象的一个很重要的数据结构,它的数据结构是由驱动程序设计者自己来确定的,在调用IoCreateDevice的时候应该指定它的大小,DeviceExtension其实是由操作系统在非份页内存池中为每个Device对象分配的一块内存。

由于驱动程序必须是完全可重入的,因此使用任何全局变量和静态变量都不是好的办法,一般来说和设备有关的任何需要保持的信息都应该放到DeviceExtension里去。

设备的缓冲策略也必须提一下,这里的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包括一个IRP头和一个IRPstack的区域。

由于WDM的模式下都是包驱动的,所里IRP可以说是一个非常重要的东东。

还有那个该死的URB(GoddamnURB!

)[人一辈子真的很过瘾,有些人或有些事你明明不喜欢或做不来,可是有时侯你又不得不硬着头皮去做,就像一大堆球迷围着一堆狗屎般的中国足球,那班傻儿真是要钱不要脸,丢咱中国人的脸]

IRP主要数据项

说明

IO_STATUS_BLOCKIoStatus

存放I/O请求的状态

PVOID

AssociatedIrp.SystemBuffer

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

否则为NULL

PMDLMdlAddress

如果设备执行直接I/O,指向用户空间缓冲区的

内存描述表的指针

PVOIDUserBuffer

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

BOOLEANCancel

指示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请求,我们先来看看WIN32APIDeviceIoControl函数的原型。

BOOLDeviceIoControl(

HANDLEhDevice,//设备句柄

DWORDdwIoControlCode,//IOCTL请求操作代码

LPVOIDlpInBuffer,//输入缓冲区地址

DWORDnInBufferSize,//输入缓冲区大小

LPVOIDlpOutBuffer,//输出缓冲区地址

DWORDnOutBufferSize,//输出缓冲区大小

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

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

);

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.TypeInputBuffer域中,把输出缓冲区的地址存放到IRP的UserBuffer域中。

这两个地址都是用户空间地址。

从上面的说明可以看出,在执行缓冲I/O时,I/O管理器将在非份页池中分配内存,如果调用者的缓冲区比较大时,分配的非份页池也将比较大。

非份页池是系统比较宝贵的资源,因此,如果调用者的缓冲区比较大时,我们一般采用直接I/O的方式(例如磁盘读写请求等),这样不仅节省系统资源,另一方面由于省去了I/O管理器在系统缓冲区和调用者缓冲区之间的数据拷贝,也提高了效率,这对存在大量数据传送的驱动程序尤其明显。

可以注意到DDK中的Samples下,几乎所有的例程的读写请求都是直接I/O的,而对于IOCTL请求则是缓冲区I/O的居多。

开始驱动程序设计

下面的文字是从Microsoft的DDK帮助中节选出来的,它让我们明白在开始设计驱动程序应该注意些什么问题,这些都是具有普遍意义的开发准则。

应该支持哪些I/O请求在开始写任何代码之前,应该首先确定我们的驱动程序应该处理哪些IRP例程。

如果你在设计一个设备驱动程序,你应该支持和其他相同类型设备的NT驱动程序相同的IRP_MJ_XXX和IOCTL请求代码。

如果你是在设计一个中间层NT驱动程序,应该首先确认你下层驱动程序所管理的设备,因为一个高层的驱动程序必须具有低层驱动程序绝大多数IRP_MJ_XXX例程入口。

高层驱动程序在接到I/O请求时,在确定自身IRP当前堆栈单元参数有效的前提下,设置好IRP中下一个低层驱动程序的堆栈单元,然后再调用IoCallDriver将请求传递给下层驱动程序处理。

一旦决定好了你的驱动程序应该处理哪些IRP_MJ_XXX,就可以开始确定驱动程序应该有多少个Dispatch例程。

当然也可以考虑把某些RP_MJ_XXX处理的例程合并为同一例程处理。

例如在ChangerDisk和VDisk里,对IRP_MJ_CREATE和IRP_MJ_CLOSE处理的例程就是同一函数。

对IRP_MJ_READ和IRP_MJ_WRITE处理的例程也是同一个函数。

应该有多少个Device对象?

一个驱动程序必须为它所管理的每个可能成为I/O请求的目标的物理和逻辑设备创建一个命名Device对象。

一些低层的驱动程序还可能要创建一些不确定数目的Device对象。

例如一个硬盘驱动程序必须为每一个物理硬盘创建一个Device对象,同时还必须为每个物理磁盘上的每个逻辑分区创建一个Device对象。

一个高层驱动驱动程序必须为它所代表的虚拟设备创建一个Device对象,这样更高层的驱动程序才能连接它们的Device对象到这个驱动程序的Device对象。

另外,一个高层驱动程序通常为它低层驱动程序所创建的Device对象创建一系列的虚拟或逻辑Device对象。

尽管你可以分阶段来设计你的驱动程序,因此一个处在开发阶段的驱动程序不必一开始就创建出所有它将要处理的所有Device对象。

但从一开始就确定好你最终要创建的所有Device对象将有助于设计者所要解决的任何同步问题。

另外,确定所要创建的Device对象还有助于你定义Device对象的DeviceExtension的内容和数据结构。

开始驱动程序开发

驱动程序的开发是一个从粗到细逐步求精的过程。

NTDDK的src\目录下有一个庞大的样板代码,几乎覆盖了所有类型的设备驱动程序、高层驱动程序和过滤器驱动程序。

在开始开发你的驱动程序之前,你应该在这个样板库下面寻找是否有和你所要开发的类似类型的例程。

例如我们所开发的驱动程序,虽然DDK对USB描述得不是很详细,我们还是可以在src\storage\class目录发现很多和USB设备有关的驱动程序。

下面我们来看开发驱动程序的基本步骤。

最简的驱动程序框架

1、写一个DriverEntry例程,在里面调用IoCreateDevice创建一个Device对象。

1、写一个处理I

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 军事

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1