对于HID的设备,就可以采用上图左上边的结构,其它类的话采用右上的结构,其实右边的结构可以又细分成两层,一层是Class Driver,一层是Miniport Driver。而倒数第三行的UHCD和OpenHCI分别是由INTEL和COMPAQ两位老大定的一个和硬件有关的底层驱动程序标准,各位可以根据所需要的选择。
对于USB的驱动程序,大家还得去了解WDM驱动程序的写法,或者早些时候的NT驱动程序,其实WDM驱动程序可以看做是NT驱动程序的一个update,只是增加了一些新的特性。
写驱动程序是一个很漫长和繁琐的工作,在此之前,你最好要熟悉硬件,熟悉C/C++,还要用过DDK,会用一些调试程序,如SOFTICE和WINDBG之类。如果一切就绪,你就可以开始写驱动程序,工作的进程有时侯会取决于你的运气。(这是一位留美的朋友对我说的,我写出来和大家共享)
下面是我从一个朋友那里得到的一篇文章的摘要:
NT驱动程序的分层结构
驱动程序是指管理某个外围设备的一段程序代码。NT采用更灵活的分层驱动方法,允许杂应用程序和硬件之间 存在几个驱动程序层次。分层机制允许NT更加广泛地定义驱动程序,包括文件系统、逻辑卷管理器和各种网络组件,各种物理设备驱动程序等等。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)1、 设备驱动程序
这些是管理实际数据传输和控制特定类型的物理设备的操作的驱动程序,包括开始和完成I/O操作,处理中断和执行特定的设备要求的任何差错处理。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)2、 中间驱动程序
NT允许在物理设备驱动程序上分层任意数目的中间驱动程序。这些中间层次提供扩展I/O系统的功能一种方法,而不必修改底层的驱动程序。这也是微软鼓吹的他们的系统灵活的一面!实际上我觉得这样反而牺牲了一些效率上的东西。
3、 文件系统驱动程序(FSD)
FSD是一类比较特殊的驱动程序,通常负责维护各种文件系统 所需要的磁盘结构。注意我们并不能使用DDK来开发FSD,而必须使用Microsoft的文件系统开发人员工具包。
一般比较少写中间过滤驱动程序,过滤驱动程序它截获和修改高层发送给类驱动程序的请求。这样就允许利用现有类驱动程序的功能,而不必从头开始写所有程序。NT内核模式对象在我们的实际开发过程中的对象是设备,由于端口驱动程序已经隐藏了硬件控制操作,因此我在这里不讲述跟硬件相关的部份。如果今后的开发对象不同,需要对硬件进行操作的时候,可能会对中断、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 打开设备CreateFileIRP_MJ_CLEANUP 在关闭设备时,取消挂起的I/O请求CloseHandleIRP_MJ_CLOSE 关闭设备CloseHandleIRP_MJ_READ 从设备获得数据ReadFileIRP_MJ_WRITE 向设备发送数据WriteFileIRP_MJ_DEVICE_CONTROL 对用户模式或内核模式客户程序可用的控制操作DeviceIoControlIRP_MJ_INTERNAL_DEVICE_CONTROL 只对内核模式客户程序可用的控制操作IRP_MJ_QUERY_INFORMATION 得到文件的长度GetFileLengthIRP_MJ_SET_INFORMATION 设置文件的长度SetFileLengthIRP_MJ_FLUSH_BUFFERS 写输出缓冲区或丢弃输入缓冲区FlushFileBuffersFlushConsoleInputBufferPurgeCommIRP_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_OBJECT DeviceObject 由本驱动程序创建的Device对象的链表ULONG Flags PDRIVER_INITIALIZE DriverInit 驱动程序初始化例程(一般较少用) PDRIVER_STARTIO DriverStartIo StartIo例程入口,一般该例程对低层设备驱动程序用得较多, 高层驱动程序较少使用本例程。PDRIVER_UNLOAD DriverUnload 卸载驱动程序例程,如果想在控制面版的设备里停止该设备,应该提供本例程。PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1] 驱动程序的Dispatch例程表
在上面提到过驱动程序是管理同类型的所有设备,所以上面的 结构中DeviceObject指向的就不是单个的设备对象,而是一个对象链表,这个链表的维护在下面介绍Device对象时可以看到。 Device对象与Device Extension 驱动程序在调用IoCreateDevice函数成功后就创建了一个Device 对象。下面对Device对象几个比较重要的数据做一介绍。 Device对象数据项 说明 PVOID DeviceExtension 指向Device Extension结构的指针PDRIVER_OBJECT DriverObject指向这个设备的Driver对象的指针,IoCreateDevice会 自动填写本数据。ULONG Flags 指定这个设备的缓冲策略PDEVICE_OBJECT NextDevice 指向属于这个驱动程序的下一个设备对象,依靠本数据来维护设备对象链表CCHAR StackSize 发送到这个设备的IRP需要的I/O堆栈单元的最小数目,一般对分层驱动程序来说,本数据应该比其下层设备的大1ULONG AlignmentRequirement缓冲区要求的内存对齐,一般对分层驱动程序来说,本值应该 和其下层设备的对齐一致
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包括一个IRP头和一个IRP stack 的区域。由于WDM的模式下都是包驱动的,所里IRP可以说是一个非常重要的东东。还有那个该死的URB(God damn URB!)
IRP主要数据项说明 IO_STATUS_BLOCK IoStatus存放I/O请求的状态PVOID AssociatedIrp.SystemBuffer如果设备执行缓冲I/O,则为指向系统空间缓冲区的指针。 否则为NULLPMDL MdlAddress 如果设备执行直接I/O,指向用户空间缓冲区的内存描述表的指针PVOID UserBufferI/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,并把用户缓冲 区的数据拷贝到系统缓冲区中。