毕业设计论文usb接口通信(驱动)的设计与实现.doc_第1页
毕业设计论文usb接口通信(驱动)的设计与实现.doc_第2页
毕业设计论文usb接口通信(驱动)的设计与实现.doc_第3页
毕业设计论文usb接口通信(驱动)的设计与实现.doc_第4页
毕业设计论文usb接口通信(驱动)的设计与实现.doc_第5页
已阅读5页,还剩53页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

引言WDM是“Windows驱动程序模型”的简称,即“Windows Driver Model”。实际上它是一系列集成在操作系统之中的常规系统服务集,用于简化硬件驱动程序的编写,并保证它们在Windows 98/Me/2000中的二进制兼容,WDM(Windows Driver Model)模型是从WinNT3.51和WinNT4的内核模式设备驱动程序发展而来的。WDM主要的变化是增加了对即插即用、电源管理、Windows Management Interface(WMI)、设备接口的支持。WDM模型的主要目标,是实现能够跨平台使用、更安全、更灵活、编制更简单的Windows设备驱动程序。WDM采用了“基于对象”的技术,建立了一个分层的驱动程序结构。WDM首先在Windows98中实现,在Windows2000中得到了进一步的完善,并在后续开发的Windows操作系统中都将存在,比如Windows Me和Windows XP。微软在通过WDM模型的引入,希望减轻设备驱动程序的开发难度和周期,逐渐规范设备驱动程序的开发,应该说,WDM将成为以后设备驱动程序的主流。USB技术的全称是通用串行总线,是英文Universal Serial Bus的缩写。它是一种应用在PC领域的新型接口技术,虽然USB2.0已经被广泛应用,但是初始的Windows 2000是支持USB1.0协议的,如果希望支持USB2.0协议,需要在微软网站上下载升级包。实际上,对于键盘或者鼠标来说,传输的速度非常小,使用USB1.0或者是USB2.0的区别并不大。闪存盘之类的存储设备,则需要重视传输速度。USB1.0版本主要应用在鼠标,键盘等HID设备上,这就是本驱动程序中引用的头文件版本是USB1.0的原因。本毕业设计的目的是希望对Windows 2000操作系统体系结构和驱动程序开发以及调试等方面的问题有一个比较深入的了解,对USB协议和USB体系有做一个比较深入的了解。并开发出一个USB键盘驱动。这个USB键盘驱动程序应当可以替代系统原有的键盘驱动程序,并可以正常工作。本论文设计的驱动程序在Windows 2000下运行,开发环境为VC6.0和DDK2000。1 WDM驱动程序模型概述驱动程序在任何操作系统下都和系统内核有着密切的关系。设备驱动程序是一个包含了许多操作系统可调用例程的容器,这句Walter Oney曾说过的话,抽象的描述了设备驱动程序的本质。1.1 Windows 2000概述图11中概括了Windows 200系统中的组件,Windows 2000操作系统是由不同层次的模块共同组成的。该图着重描述了驱动程序开发者所关心的特征。工作在Windows 2000操作系统平台上的软件要么执行在用户模式中,要么执行在内核模式中。当用户模式程序需要读取设备数据时,就调用Win32 API函数,如ReadFile. Win32子系统模块通过调用平台相关的系统服务接口实现API,而平台相关的系统服务将调用内核模式支持例程。在ReadFile调用中,调用首先到达系统DLL(NTDLL.DLL)中的一个入口点,NtReadFile函数。然后这个用户模式的NtReadFile函数接着调用系统服务接口,最后由系统服务接口调用内核模式中的服务例程,该例程同样名为NtReadFile。应用程序Win32子系统设备驱动硬件抽象层硬件IO管理器用户模式内核模式Win32API调用系统服务接口传递IRP给驱动程序派遣函数HAL调用平台相关操作图11 Windows组件模型系统中还有许多与NtReadFile相似的服务例程;它们同样运行在内核模式中,为应用程序请求提供服务,并以某种方式与设备交互。这些服务例程首先检查从用户态传递给它们的参数以保护系统安全或防止用户态程序非法存取数据,然后创建一个称为“I/0请求包(IRP)”的数据结构,并把这个数据结构送到某个驱动程序的入口点。驱动程序完成一个I/0操作后,通过调用一个特殊的内核模式服务例程来完成该IRP。完成操作是处理IRP的最后动作,它使等待的应用程序恢复运行。1.2 Windows 2000中的驱动程序类型虚拟设备驱动程序(VDD)内核模式驱动程序文件系统驱动程序遗留设备驱动程序PnP驱动程序显示驱动程序WDM驱动程序类驱动程序微型(mini)驱动程序图12 Windows 2000中的设备驱动程序种类Windows 2000系统可以使用多种驱动程序,图1-2显示了其中几种。虚拟设备驱动程序(VDD)可以使DOS应用程序访问x86平台上的硬件。VDD通过屏蔽I/O权限掩码来捕获端口存取操作,它基本上是模拟硬件操作,这对于那些直接对裸机硬件编程的应用程序特别有用。尽管这种驱动程序在Windows 98和Windows 2000中共享一个名称并且有相同的功能,但实际上它们的工作方式完全不同。我们用VDD缩写代表这种驱动程序,用VxD缩写代表Windows 98中的虚拟设备驱动程序以示区别。内核模式驱动程序的分类包含许多子类。PnP驱动程序就是一种遵循Windows 2000即插即用协议的内核模式驱动程序。WDM驱动程序是一种PnP驱动程序,它同时还遵循电源管理协议,并能在Windows98和Windows 2000间实现源代码级兼容。WDM驱动程序还细分为类驱动程序(classdriver)和微型驱动程序(minidriver),类驱动程序管理属于己定义类的设备,微型驱动程序向类驱动程序提供厂商专有的支持。显示驱动程序是用于显示和打印设备的内核模式驱动程序。文件系统驱动程序在本地硬盘或网络上实现标准PC文件系统模型(包括多层次目录结构和命名文件概念)。遗留设备驱动程序也是一种内核模式驱动程序,它直接控制一个硬件设备而不用其它驱动程序帮助。这种驱动程序主要包括Windows NT早期版本的驱动程序,它们可以不做修改地运行在Windows 2000中。1.3 WDM驱动程序类型WDM(Windows Driver Model)模型是从WinNT3.51和WinNT4的内核模式设备驱动程序发展而来的。WDM主要的变化是增加了对即插即用、电源管理、Windows Management Interface(WMI)、设备接口的支持。WDM模型的主要目标,是实现能够跨平台使用、更安全、更灵活、编制更简单的Windows设备驱动程序。WDM采用了“基于对象”的技术,建立了一个分层的驱动程序结构。WDM首先在Windows98中实现,在Windows2000中得到了进一步的完善,并在后续开发的Windows操作系统中都将存在,比如Windows Me和Windows XP。微软在通过WDM模型的引入,希望减轻设备驱动程序的开发难度和周期,逐渐规范设备驱动程序的开发,应该说,WDM将成为以后设备驱动程序的主流。在WDM模型中,每个硬件设备至少有两个驱动程序:一个功能驱动程序(function driver)和一个总线驱动程序(bus driver)。一个设备还可能有过滤驱动程序(filter driver),用来变更标准设备驱动程序的行为。这些服务于同一个设备的驱动程序组成了一个链表,称为设备栈。详细的描述见图1-3。可选的上层过滤驱动程序功能驱动程序可选的底层过滤驱动程序可选的总线过滤驱动程序总线驱动程序设备驱动程序总线驱动程序图1-3驱动程序的种类总线驱动程序总线驱动程序为实际的IO总线服务,比如IEEE 1394。在WDM的定义中,一个总线是这样的设备,它用来连接其他的物理的、逻辑的、虚拟的设备。总线包括传统的总线SCSI和PCI,也包括并口、串口、以及i8042端口。微软已经为Windows操作系统提供了总线驱动程序。总线驱动程序已经包含在操作系统里了,用户不必安装。一个总线驱动程序负责以下的工作:枚举总线上的设备;向操作系统报告总线上的动态事件;响应即插即用和电源管理的IO请求;提供总线的多路存取(对于一些总线);管理总线上的设备;功能驱动程序功能驱动程序是物理设备的主要驱动程序,它实现设备的具体功能,一般由设备的生产商来编写。功能驱动程序的主要功能是:提供对设备的操作接口;操作对设备的读写;管理设备的电源策略;过滤驱动程序过滤驱动程序是一个可选项,当一个用户需要改变或新添一些功能到一个设备、一类设备或一种总线时,就可以编写一个过滤驱动程序。在设备栈里,过滤驱动程序安装在一个或几个设备驱动程序的上面或下面。过滤驱动程序拦截对具体设备、类设备、总线的请求,做相应的处理,以改变设备的行为或添加新的功能。但过滤驱动程序只处理那些它所关心的IO请求,对于其他的请求可以交给其他的驱动程序来处理,这样可以非常灵活改变设备的行为,至少用户会这样看。比如:一个USB键盘的上层过滤驱动程序可以强制执行附加的安全检查。一个鼠标的低层过滤驱动程序,通过对鼠标移动的数据做非线性的转换,可以得到一个有加速效果的鼠标轨迹。功能驱动程序的组成功能驱动程序由类驱动程序和微型驱动程序(Minidriver)组成。类驱动程序实现了某一类设备的常用操作,由微软提供,驱动程序的开发者可以只编写非常小的微型驱动程序,去处理具体设备特殊的操作,而对于其他大量的常规操作,可以调用该类的类驱动程序,这也是WDM驱动程序的优点之一。微软提供的类驱动程序处理常用的系统任务,比如,即插即用功能和电源管理。类驱动程序保证了操作系统在处理类似的任务时的一致性,从而提高了系统的稳定性。设备生产商提供微型驱动程序,以实现自己设备的特殊功能,同时调用合适的类驱动程序完成其他的通用工作。将大量的标准操作的代码通过各种类驱动程序来实现,并集成在操作系统中,这样的方式可以有效的减少具体设备的微型驱动程序的大小,也就减小了程序出错的可能。如果某一类设备存在着工业标准,微软就会提供一个该类设备的WDM类驱动程序。这个类驱动程序实现了该类设备所有必须的任务,但不实现任何具体设备所特有的东西。比如,微软提供的HID(人工输入设备)类驱动程序的实现,是根据USB HID 类规范v.11的规定,但并不实现任何一种具体设备的特殊功能,比如,USB键盘、鼠标、游戏控制等等。本文所设计的驱动程序就是一个功能驱动程序,它是将USB驱动程序与微型驱动程序(Minidriver)结合起来,驱动USB键盘的一个驱动程序.微软支持的WDM总线和类驱动程序图1-4 微软支持的WDM总线和类驱动程序对于图1-4,本文只描述其中的人工输入设备(HID)和USB部分。因为这是在USB键盘驱动程序设计中所涉及到的两个方面。USB总线驱动程序枚举和控制低速的USB总线。USB客户驱动程序使用各种IOCTL通过USB类驱动程序访问它们的设备。人工输入设备(HID)类驱动程序管理多种总线(如USB)间的数据与指令语法翻译。大多数时候,本类驱动控制由用户交互接口传来的数据,如键盘,鼠标和游戏杆等。1.4 驱动程序的分层结构FiDOFDOFiDOPDO上层过滤驱动程序功能驱动程序低层过滤驱动程序总线驱动程序IRP图15 WDM中设备对象和驱动程序的层次结构WDM模型使用了如图15的层次结构。图中左边是一个设备对象堆栈。设备对象是系统为帮助软件管理硬件而创建的数据结构。一个物理硬件可以有多个这样的数据结构。处于堆栈最底层的设备对象称为物理设备对象(physical device object),或简称为PDO。在设备对象堆栈的中间某处有一个对象称为功能设备对象(functional device object),或简称FDO。在FDO的上面和下面还会有一些过滤器设备对象(filter device object)。位于FDO上面的过滤器设备对象称为上层过滤器,位于FDO下面(但仍在PDO之上)的过滤器设备对象称为下层过滤器。操作系统中的即插即用管理器(PnP Manager)根据设备驱动程序的指令来建立这个数据对象堆栈。前面我们已经知道,总线驱动程序的作用之一是枚举总线上的设备,当总线驱动程序检测到一个设备时,PnP管理器就马上建立一个PDO。当建立好PDO之后,PnP管理器通过查找注册表来找到其他的过滤驱动程序和功能驱动程序。设备的安装程序负责建立这些注册表里的表项,驱动程序的安装,是根据INF文件中的指令进行的。注册表中的表项指明了各种驱动程序在数据对象堆栈中的位置,于是PnP管理器开始装载最低层的过滤驱动程序,并调用该驱动程序的AddDevice函数。该函数在数据对象堆栈中建立一个FiDO,同时也将前面建立的PDO和这个FiDO联系在一起。PnP管理器反复的实现该过程,装载其他位置靠上的低层过滤驱动程序、功能驱动程序、上层过滤驱动程序,直到该堆栈完成。应用程序对设备的存取通过提交IO请求包(IRP)来进行。在操作系统中,对设备的存取过程是这样的:操作系统中的IO管理器接受IO请求(通常是由用户态的应用程序发出的),建立相应的IRP来描述它,将IRP发送给合适的驱动程序,然后跟踪执行过程,当操作完成后,将返回的状态通知请求的发起者。操作系统中的IO管理器、即插即用管理器、电源管理器都使用IRP来与内核模式驱动程序、WDM驱动程序进行通信,并且,各驱动程序之间的通信也是依靠IRP。在WDM驱动程序中,IRP首先从最上层进入,如图15里右手边的箭头,然后,依次往下传送。在每一层,驱动程序自行决定对IRP的处理。有时,一个驱动程序除了把继续IRP向下传递外,并不做任何事情。有时,一个驱动程序会完全接管IRP,不再把它向下传递了。当然,一个驱动程序也可以处理IRP后,再把它继续向下传递。这取决于驱动程序的功能和IRP的含义。从这里,可以知道微软在WDM模型中使用分层的驱动程序结构的原因了,通过分层的方法,在处理对设备的IO请求时,利用添加合适的驱动程序层的方法,从而非常灵活的改变设备的行为,以实现不同设备的功能。1.5 IO请求包(IRP)操作系统使用I/0请求包(IRP)数据结构与内核模式驱动程序通信。这个数据结构很重要,需要了解它的创建、发送、处理,以及最后的销毁。可以说,IO请求包(IRP)才是WDM驱动程序结构的最重点,只有真正了解处理IRP的过程,才算是真正懂得了设备驱动的原理。1.5.1IRP结构图16 I/O请求包数据结构MdlAddress(PMDL)域指向一个内存描述符表(MDL),该表描述了一个与该请求关联的用户模式缓冲区。如果顶级设备对象的Flags域为DO_DIRECT_IO,则I/O管理器为IRP_MJ_READ或IRP_MJ_WRITE请求创建这个MDL。如果一个IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式,则I/O管理器为该请求使用的输出缓冲区创建一个MDL。MDL本身用于描述用户模式虚拟缓冲区,但它同时也含有该缓冲区锁定内存页的物理地址。为了访问用户模式缓冲区,驱动程序必须做一点额外工作。Flags(ULONG)域包含一些对驱动程序只读的标志。但这些标志与WDM驱动程序无关。AssociatedIrp(union)域是一个三指针联合。其中,与WDM驱动程序相关的指针是AssociatedIrp.SystemBuffer。 SystemBuffer指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中。对于IRP_MJ_READ和IRP_MJ_WRITE操作,如果顶级设备指定DO_BUFFERED_IO标志,则I/O管理器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作,如果I/O控制功能代码指出需要缓冲区(见第九章),则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲区,这也是创建IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据,或者是DeviceIoControl调用中所谓的输入数据。对于读请求,设备驱动程序把读出的数据填到这个缓冲区,然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了METHOD_BUFFERED的I/O控制操作,驱动程序把所谓的输出数据放到这个缓冲区,然后I/O管理器再把数据复制到用户模式的输出缓冲区。IoStatus(IO_STATUS_BLOCK)是一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构。IoStatus.Status域将收到一个NTSTATUS代码,而IoStatus.Information的类型为ULONG_PTR,它将收到一个信息值,该信息值的确切含义要取决于具体的IRP类型和请求完成的状态。Information域的一个公认用法是用于保存数据传输操作,如IRP_MJ_READ,的流量总计。某些PnP请求把这个域作为指向另外一个结构的指针,这个结构通常包含查询请求的结果。RequestorMode将等于一个枚举常量UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。PendingReturned(BOOLEAN)如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。完成例程通过参考该域来避免自己与派遣例程间的潜在竞争。Cancel(BOOLEAN)如果为TRUE,则表明IoCancelIrp已被调用,该函数用于取消这个请求。如果为FALSE,则表明没有调用IoCancelIrp函数。取消IRP是一个相对复杂的主题,我将在本章的最后详细描述它。CancelIrql(KIRQL)是一个IRQL值,表明那个专用的取消自旋锁是在这个IRQL上获取的。当你在取消例程中释放自旋锁时应参考这个域。CancelRoutine(PDRIVER_CANCEL)是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接修改该域。UserBuffer(PVOID) 对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求,该域包含输出缓冲区的用户模式虚拟地址。该域还用于保存读写请求缓冲区的用户模式虚拟地址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驱动程序,其读写例程通常不需要访问这个域。当处理一个METHOD_NEITHER控制操作时,驱动程序能用这个地址创建自己的MDL。Tail.Overlay是Tail联合中的一种结构,它含有几个对WDM驱动程序有潜在用途的成员。由于篇幅有限,这里不再讨论。1.5.2IRP处理的“标准模型”(1)创建IRPIRP开始于某个实体调用I/O管理器函数创建它。在上图中,我使用术语“I/O管理器”来描述这个实体,尽管系统中确实有一个单独的系统部件用于创建IRP。事实上,更精确地说,应该是某个实体创建了IRP,并不是操作系统的某个例程创建了IRP。例如,你的驱动程序有时会创建IRP,而此时出现在图中第一个方框中的实体就应该是你的驱动程序。可以使用下面任何一种函数创建IRP: IoBuildAsynchronousFsdRequest 创建异步IRP(不需要等待其完成)。该函数和下一个函数仅适用于创建某些类型的IRP。 IoBuildSynchronousFsdRequest 创建同步IRP(需要等待其完成)。 IoBuildDeviceIoControlRequest 创建一个同步IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL请求。 IoAllocateIrp 创建上面三个函数不支持的其它种类的IRP。 前两个函数中的Fsd表明这些函数专用于文件系统驱动程序(FSD)。虽然FSD是这两个函数的主要使用者,但其它驱动程序也可以调用这些函数。DDK还公开了一个IoMakeAssociatedIrp函数,该函数用于创建某些IRP的从属IRP。WDM驱动程序不应该使用这个函数。(2)发往派遣例程创建完IRP后,你可以调用IoGetNextIrpStackLocation函数获得该IRP第一个堆栈单元的指针。然后初始化这个堆栈单元。在初始化过程的最后,你需要填充MajorFunction代码。堆栈单元初始化完成后,就可以调用IoCallDriver函数把IRP发送到设备驱动程序:PDEVICE_OBJECT DeviceObject; /给定的设备对象PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp); /获得指针stack-MajorFunction = IRP_MJ_Xxx; NTSTATUS status = IoCallDriver(DeviceObject, Irp);IoCallDriver函数的第一个参数是你在某处获得的设备对象的地址。我将在本章的结尾处描述获得设备对象指针的两个常用方法。在这里,我们先假设你已经有了这个指针。IRP中的第一个堆栈单元指针被初始化成指向该堆栈单元之前的堆栈单元,因为I/O堆栈实际上是IO_STACK_LOCATION结构数组,你可以认为这个指针被初始化为指向一个不存在的“-1”元素,因此当我们要初始化第一个堆栈单元时我们实际需要的是“下一个”堆栈单元。IoCallDriver将沿着这个堆栈指针找到第0个表项,并提取我们放在那里的主功能代码,在上例中为IRP_MJ_Xxx。然后IoCallDriver函数将利用DriverObject指针找到设备对象中的MajorFunction表。IoCallDriver将使用主功能代码索引这个表,最后调用找到的地址(派遣函数)。派遣函数要对IRP的处理做出决定,有三种选择:派遣函数立即完成该IRP。 把该IRP传递到处于同一堆栈的下层驱动程序。 排队该IRP以便由这个驱动程序中的其它例程来处理。 每处理一个IRP,I/O管理器就调用一次StartIo例程:StartIo例程在DISPATCH_LEVEL级上获得控制,这意味着该函数不能生成任何页故障。另外,设备对象的CurrentIrp域和Irp参数都指向I/O管理器送来的IRP。StartIo的工作是就着手处理IRP。如何做要完全取决于你的设备。通常你需要访问硬件寄存器,但可能有其它例程,如你的中断服务例程,或者是驱动程序中的其它例程也需要访问这些寄存器。实际上,有时着手一个新操作的最容易的方式是在设备扩展中保存某些状态信息,然后伪造一个中断。由于这些方法的执行都需要在一个自旋锁的保护之下,而这个自旋锁与保护你的ISR所使用的是同一个自旋锁,所以正确的方法是调用KeSynchronizeExecution函数。对于图中的中断服务例程,当设备完成数据传输后,它将以硬件中断形式发出通知。DpcForIsr例程在DISPATCH_LEVEL级上获得控制。通常,它的工作就是完成IRP(导致最近的中断发生)。但一般情况下,它通过调用IoCompleteRequest函数把剩余的工作交给完成例程来做。图17 I/O请求包处理流程1.5.3完成I/O请求派遣函数也可以在下面这两种情况下完成IRP: 如果请求是错误的(可以以容易的检测方式查明,例如要求打印机倒纸请求或卸载键盘请求),则派遣例程应以失败方式完成该请求并返回适当的出错代码。 如果请求要求得到的仅是派遣函数可以容易确定的信息(例如一个询问驱动程序版本号的控制请求),则派遣例程应立即给出回答并完成请求,返回成功状态码。 完成机制是这样的,完成一个IRP必须先填充IoStatus块的Status和Information成员,然后调用IoCompleteRequest例程。Status值就是NTSTATUS.H中定义的状态代码。表中简要地列出了常用的状态代码。而Information值要取决于你完成的是何种类型的IRP以及是成功还是失败。通常情况下,如果IRP完成失败(即,完成的结果是某种错误状态),你应把Information域置0。如果你成功地完成了一个数据传输IRP,通常应该把Information域设置成传输的字节量。表1-1 状态代码状态代码描述STATUS_SUCCESS正常完成STATUS_UNSUCCESSFUL请求失败,没有描述失败原因的代码STATUS_NOT_IMPLEMENTED一个没有实现的功能STATUS_INVALID_HANDLE提供给该操作的句柄无效STATUS_INVALID_PARAMETER参数错误STATUS_INVALID_DEVICE_REQUEST该请求对这个设备无效STATUS_END_OF_FILE到达文件尾STATUS_DELETE_PENDING设备正处于被从系统中删除过程中STATUS_INSUFFICIENT_RESOURCES没有足够的系统资源(通常是内存)来执行该操作为了了解低级驱动程序的I/O请求的结果,需要安装一个完成例程,调用IoSetCompletionRoutine函数:IoSetCompletionRoutine(Irp, CompletionRoutine, context, InvokeOnSuccess, InvokeOnError, InvokeOnCancel);Irp就是你要了解其完成的请求。CompletionRoutine是被调用的完成例程的地址,context是任何一个指针长度的值,将作为完成例程的参数。InvokeOnXxx参数是布尔值,它们指出在三种不同的环境中是否需要调用完成例程: InvokeOnSuccess 你希望完成例程在IRP以成功状态(返回的状态代码通过了NT_SUCCESS测试)完成时被调用。 InvokeOnError 你希望完成例程在IRP以失败状态(返回的状态代码未通过了NT_SUCCESS测试)完成时被调用。 InvokeOnCancel 如果驱动程序在完成IRP前调用了IoCancelIrp例程,你希望在此时调用完成例程。IoCancelIrp将在IRP中设置取消标志,该标志也是调用完成例程的条件。一个被取消的IRP最终将以STATUS_CANCELLED(该状态代码不能通过NT_SUCCESS测试)或任何其它状态完成。如果IRP以失败方式完成,并且你也指定了InvokeOnError参数,那么是InvokeOnError本身导致了完成例程的调用。相反,如果IRP以成功方式完成,并且你也指定了InvokeOnSuccess参数,那么是InvokeOnSuccess本身导致了完成例程的调用。在这两种情况中,InvokeOnCancel参数将是多余的。如果你省去InvokeOnSuccess和InvokeOnError中的任何一个参数或两个都省去,并且IRP也被设置了取消标志,那么InvokeOnCancel参数将导致完成例程的调用。 这三个标志中至少有一个设置为TRUE。注意,IoSetCompletionRoutine是一个宏,所以你应避免使用有副作用的参数。这三个标志参数和一个函数指针参数在宏中被引用了两次。IoSetCompletionRoutine将把完成例程地址和上下文参数安装到下一个IO_STACK_LOCATION中,即下一层驱动程序将在那个堆栈单元中找到这些参数。因此,最底层的驱动程序不应该安装一个完成例程。1.5.4向下级传递请求WDM使用分层设备对象结构的目的就是使IRP能方便地从一层驱动程序传递到下一层驱动程序。有两种情况,有时候我们需要考虑IRP传递到下层驱动程序之后的事情,这时需要复制堆栈单元。这里一般不考虑。1.5.5取消I/O请求程序有时会取消它们原来请求的IRP。应用程序可能发出某些需要长时间才能完成的请求,然后这个应用程序结束执行,而这个IRP仍然是未完成的。这种情况在WDM模型中尤为常见,例如当新硬件插入系统时,驱动程序必须停止执行以等待配置管理器重新分配硬件资源,设备电源关闭时也是这样。为了在内核模式中取消一个请求,IRP的创建者需调用IoCancelIrp函数。如果某线程终止时,它发出的请求仍然未完成,则操作系统自动为每个IRP调用IoCancelIrp。用户模式应用程序调用CancelIo函数可以取消给定线程发出的所有未完成的异步操作。IoCancelIrp仅仅是简单地设置IRP的Cancel标志位然后调用IRP的取消例程。即:它并不知道是否修改过IRP指针,也不知道是否正在处理这个IRP,所以它必须依靠一个提供的取消例程来做大部分IRP取消工作。1.6 本设计开发的驱动程序描述根据前面的描述,本论文所涉及的驱动程序,是一个功能驱动程序,它涉及到USB和HID两个类。这个驱动程序之上并没有过滤驱动程序,功能驱动程序将调用总线驱动程序的一些功能来完成自己的功能。从功能方面来说,一个驱动程序可以做的工作有:初始化它自己创建和删除设备处理Win32打开和关闭文件句柄的请求处理Win32输入/输出(I/O)请求串行化对设备的访问访问硬件调用其他驱动程序取消I/O请求超时I/O请求处理一个可热插拔的设备被加入或删除的情况处理电源管理请求使用WMI(Windows Management Instrumentation)和NT事件向系统管理员报告只有“初始化”模块是必不可少的,本设计中,只用到了其中部分模块。因为对一个键盘驱动来说,许多功能是用不到的。2 设计方案及设计工具、环境选择2.1 键盘驱动程序设计方案及设计工具键盘驱动有很多种设计工具,除了用DDK开发之外,还可以用Windriver,DriverStudio等开发工具开发。一般的来说,使用封装的更高层的工具象Windriver,开发起来周期较短,也更容易些,但是出了问题也更难调试。作为一个毕业设计,为了更深入的了解Windows驱动模型,应该选择使用DDK开发。了解到键盘首先是一个HID设备,Windows系统是将键盘作为HID设备处理。因此,在开发键盘驱动的时候,是在一个HID minidriver的框架下来实现的,HID类驱动中有一些IOCTL(输入输出控制),我们所做的就是要建立起来这个框架,并且填充这些IOCTL,来实现驱动程序的完全功能。 那么怎么来实现读写键盘的功能成为下一步考虑的问题,因为键盘是一个USB键盘,这时候我们用到了USB类的IOCTL,在第一章里提到了Windows包含的各种类驱动,可以看到USB类和HID类都列在其中。事实上在这里它们结合起来组成了一个完整的USB键盘驱动程序。通过调用USB类的USBDI,我们可以实现读写键盘,启动键盘(USB设别),停止键盘(USB设备),移除键盘(USB设备)等一系列必须的事件的响应。将实现的代码添加到HID minidriver中。这样,HID类的接口得以实现,对下面的一层则使用了USB驱动程序接口(USBDI)。一个完整的驱动程序的设计方案大致如上。事实上,如果只是做一个简单的访问键盘的程序,而不是将其嵌入在系统中,作为驱动程序的话,只需要USB类的特性就够了。那样的话,整个题目的难度会降低一些。2.2 环境设置2.2.1DDK的安装DDK是驱动程序开发工具包,不同的操作系统有不同的版本,本论文设计的驱动程序是在Windows 2000环境下,因此使用Windows 2000DDK。安装DDK之后,需要把DDK的bin目录加入到VC的目录列表中,这样一些使用到DDK头文件的客户程序,可以方便的找到它们要用的头文件。而不用专门拷贝出来。DDK当中有一个setenv.bat,它来为VC使用DDK进行开发做一些环境设置,并且检查开发工具VC的版本,是否安装。DDK2000支持VC5和VC6,这也是为什么作者一开始选择VC.NET作为开发工具,但是后来又转向VC6.0的原因。2.2.1makefile构造环境当创建新的Makefile项目时,Visual Studio缺省提供两个build配置“Win32 Debug”和“Win32 Release”,build命令行中的设置,根据程序所在驱动器位置的不同而需要改变。build命令行运行MakeDrvr.bat批处理文件,使用DDKROOT环境变量,如果在Visual Studio中请求一个完整的重新构造,把选项-nmake /a添加到这个命令行。设置输出文件名,使得在build菜单中显示正确的名字。代码清单21 Win32自由配置设置build命令行 MakeDrvr %DDKroot% e:lcsDriver checked全部重新构造选项 -nmake /a输出文件名 lcsDriver.sy浏览信息文件名 objchki386lcsDriver.bsc如果在以上配置中,checked改为free,那么在浏览信息文件名中,应改为objfre i386lcsDriver.bsc。makefile文件时必不可少的,它是一个标准文件,激活DDK inc目录中的标准构造文件makefile.def。不要试图来编辑这个文件,为了把它添加到我们的工程中去,在SOURCES文件的SOURCES宏中,写入了它。代码清单22 Makefile文件内容# DO NOT EDIT THIS FILE! Edit .sources. if you want to add a new source# file to this component. This file merely indirects to the real make file# that is shared by all the driver components of the Windows NT DDK#!INCLUDE $(NTMAKEENV)makefile.def2.2.2build目录与普通的生成程序不同,在Windows2000中,build分开保存自由构造版本和检查构造版本文件。如果TARGETPATH(目标路径)是OBJ,自由构造x86目标文件和最终的驱动程序进入OBJFREi386目录中,检查构造目标文件和驱动程序进入OBJCHKi386目录中,在本论文的驱动程序中,使用的是后者。2.2.3MakeDrvr在要求Visual Studio构造驱动程序时,批处理文件MakeDrvr.bat运行,它总是至少传递四个参数:DDK基目录,源驱动器,源目录和构造类型(“free”或“checked”)。任何其他的参数直接传递给build。MakeDrvr先对传递的参数进行一些基本的检查,然后调用DDK setenv命令为build目录正确设置环境变量,改变目录为源驱动器和目录,最后调用build。-b选项保证显示完全的错误文本,-w选项保证在屏幕输出上出现警告,可以在Visual Studio的build Output窗口中发现它们。MakeDrvr命令文件的屏幕输出出现在Visual Studio Output窗口中。代码清单23 MakeDrvr文件echo offif %1= goto usageif %3= goto usageif not exist %1binsetenv.bat goto usagecall %1binsetenv %1 %4%2cd %3build -b -w %5 %6 %7 %8 %9goto exit:usageecho usage MakeDrvr DDK_dir Driver_Drive Driver_Dir free/checked build_optionsecho eg MakeDrvr %DDKROOT% C: %WDMBOOK% free -cef:exit2.2.4DebugPrint的使用驱动程序没有任何保护,因为它是内核的一部分,要特别仔细地完全测试驱动程序,否则会丢失数据。驱动程序出错的方式有以下几种。崩溃内核转储驱动程序不启动挂起资源遗漏时间依赖性DebugPrint软件用来允许我们使用格式化的打印语句跟踪驱动程序的执行。详细的信息来自于DebugPrint。首先我安装了DebugPrint驱动程序,安装方法于安装驱动程序类似。它的用户态监视程序监视测试驱动程序的打印跟踪事件。在程序中使用DebugPrint的方法如下:首先我们要把DebugPrint.c和DebugPrint.h这两个标准文件复制到驱动程序项目中,然后在驱动程序的主头文件中包括DebugPrint.h。修改SOURCES文件,使得DebugPrint.c被构造。驱动程序中调用DebugPrint函数,只能在DISPATCH_LEVEL或更低的IRQL调用这些函数,这意味着可以在DriverEntry例程,主IRP分发例程和StartIo及延迟过程调用(DPC)例程中调用DebugPrint函数,但不能在中断处理例程中调用。DebugPrintInit例程必须在PASSIVE_LEVEL IRQL调用。DebugPrint调用在驱动程序的执行中只引起很小的延迟,DebugPrint调用的主要工作在以低实时优先级在后台运行的系统线程中发生。在本论文的驱动程序代码中一个典型的例子如下:代码清单24 DebugPrint调用实例NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject , IN PUNICODE_STRING RegistryPath)#if DBG DebugPrintInit(Lcs_Kbd checked );#elseDebugPrintInit(Lcs_Kbd free );.3 使用USB3.1 USB类概述图31 主要的USB类图31列出了主要的USB设备类,由图中我们可以看出人工输入设备类(HID类)与USB类是有关系的,在下一章中,可以看出HID类和USB类的关系远不止如此。事实上,HID协议本来就是USB协议的一个子部分,最初的时候HID是因USB而生,但是现在它的作用不止如此。当然HID设备是不一定在USB上运行的,但是因为一些特定的关系,它们能够很好的适合于USB设备模型。3.2访问USB键盘的实现 3.2.1USBDI的IOCTL使用Windows USB驱动程序接口(USBDI),可以编写访问USB键盘的驱动程序,并读取原始键盘输入数据。并可以对键盘进行控制,使得键盘上的LED灯发光或是熄灭。Windows 2000使用的是USBDI 2.00版,因为这个驱动程序中并没有用到WMI(Windows Management Instrumentation)数据,所以受版本的影响不大。表31中是USB驱动程序接口的内部IOCTL。表31 USB驱动程序接口的内部IOCTLIOCTL_INTERNAL_USB_SUBMIT_URB 发出URB停止等待结果IOCTL_INTERNAL_USB_RESET_PORT 复位并重新启用一个端口IOCTL_INTERNAL_USB_GET_PORT_STATUS 得到状态位: USBD_PORT_ENABLED USBD_PORT_CONNECTEDIOCTL_INTERNAL_USB_ENABLE_PORT 重新启用一个被禁止的端口IOCTL_INTERNAL_USB_GET_HUB_COUNT 集线器驱动程序内部使用IOCTL_INTERNAL_USB_CYCLE_PORT模拟设备拔出和再次插入IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO集线器驱动程序内部使用IOCTL_INTERNAL_USB_GET_HUB_NAME 得到集线器驱动程序的设备名IOCTL_INTERNAL_USB_GET_BUS_INFO 获得USB总线信息IOCTL_INTERNAL_USB_GET_CONTROLLER_NAME 得到主机控制器设备名最重要的内部IOCTL是IOCTL_INTERNAL_USB_SUBMIT_URB,它发出USB请求块(URB)由USB类驱动程序处理。有30多个不同的URB功能代码。USB客户使用URB做它们大多数的工作。URB结构是一个联合,含有16个不同的_URB_*结构。3.2.2使用USBDI访问USB键盘 对一个USB键盘来说,它有这么几种操作,复位设备,配置设备,和读写设备。在使用USB设备之前,第一件事就是要复位它,来确保它可用。然后主机负责配置设备。配置设备的过程与物理特性相关。在USB键盘的配置中,我们所要做的是获得设备的配置描述符,然后找出接口,设备接口的每个管道的最大传输数据包为8,创建配置设备的URB请求发送给USBDI。 具体的配置过程在第五章中描述,在这里主要涉及的是,如何使用USBDI。USB设备驱动程序从不直接与硬件对话。它仅靠创建URB(USB请求块)并把URB提交到总线驱动程序就可完成硬件操作。系统当中的文件USBD.SYS,是接受URB的实体。向USBD的调用被转化为带有主功能代码为IRP_MJ_INTERNAL_DEVICE_CONTROL的IRP。然后USBD再调度总线时

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论