最近一段时间我将《Windows驱动开发详解》翻阅了一遍,个别章节进行了精读,还是很有体会的,在此想对Window设备驱动开发的一些思想做一下总结。由于这几年在Linux驱动开发方面做了很多工作,因此会将Linux驱动与Windows驱动做一下简单的比较。
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
2003年时做本科毕业设计,那时候首次涉及Windows驱动程序,由于课题的需要,因此直接采用DriverStudio对驱动程序进行了开发,那时阅读了武安河先生编著的Windows驱动程序开发教材,对很多概念还是很模糊的。采用DriverStudio可以直接生成PCI、USB设备驱动程序的框架,然后在DS类库的基础上进行驱动程序的开发,最后采用SoftICE进行驱动程序的调试。DriverStudio工具做的一件事情就是将Microsoft提供的DDK进行类库封装,并且做了一个代码框架自动生成的工具,简化驱动程序开发过程。专业的驱动开发都会直接采用DDK进行开发,最新的Vista操作系统需要WDK进行开发。
WDM设备驱动模型是在NT驱动模型基础上发展起来的,基本框架与NT驱动模型一致,只是引入了即插即用机制。NT驱动需要手动加载,WDM驱动在设备被扫描之后自动加载,因此添加设备的时刻发生了变化。WDM增加了添加、删除设备的例程函数接口。WDM驱动是一种分层驱动模型,所有驱动可以分成总线驱动、功能驱动和过滤驱动。过滤驱动最简单,其通常架构在其他驱动之上,进行IRP的过滤处理和转发;总线驱动用来处理总线协议,例如PCI总线驱动、USB总线驱动,这种驱动往往由操作系统厂商提供,会涉及到大量总线协议的处理;功能驱动通常称之为设备驱动,用来实现设备的具体功能,其实现与具体的硬件设备相关。个人认为这种分层驱动能够带来很多好处,第一,可以很灵活的修改驱动的行为,为现有驱动程序打补丁;第二,分离了系统总线和设备功能,系统总线作为一种标准能够固化,因此其可以作为一种可重用资源使用,设备功能与具体的设备相关,分离总线与功能可以使得系统框架更加清晰,并且简化程序的开发;第三,分层驱动可以堆叠驱动,如果每层驱动的功能定义清晰,那么通过不同驱动模块的堆叠可以实现不同的系统功能。
在Windows驱动中,IRP是最重要的一个概念,其类似于Linux块设备驱动中的BIO请求,IRP可以理解成一个消息报文,这个报文记录了IO请求的所有属性,包括访问地址和长度等等。上层应用程序在发起读写请求时通过I/O管理器向底层驱动程序发送IRP,同时会根据设备栈分配IO堆栈,这个IO堆栈大小通常情况下与设备栈大小相同。在Linux块设备驱动中同样存在设备栈,但是并没有明确的IO堆栈。实际上,这个IO堆栈就是Linux中通常所说的IO请求上下文(Linux通常会动态分配IO_Context),其保存了IRP请求的执行情况和回调函数等情况,在设备驱动中可以在对应IO堆栈中设置IRP的回调处理函数,当底层驱动处理完成IRP之后,IRP会沿着IO堆栈向上返回,每达到一层之后都会调用相应的回调处理函数。这种机制与Linux中的BIO机制如出一辙。
Buffer_IO与Direct_IO是Windows驱动中非常重要的概念,在Linux中也有同样的机制。Buffer_IO机制在用户态与内核态进行数据交互时需要进行数据拷贝操作,如果应用涉及到大量数据传输任务,那么Buffer_IO的效率将很低。由于Buffer_IO的实现简单,因此在Windows驱动中得到大量应用。Direct_IO机制应用了虚拟内存映射机制,可以将用户态虚拟内存映射块重映射到内核地址空间,这样用户态的某段地址和内核态的某段地址映射到了相同的物理块,内核空间和用户空间在交互数据时就无需数据拷贝了,这种模式在IO的效率上最高。除上述两种机制外,Windows驱动程序还可以直接应用用户空间的虚拟地址,但是这种操作方式必须保证虚拟地址应用点必须处于那个正确的用户上下文,这种模式实在不推荐使用。
分层驱动之间可以互相堆叠,Windows提供了一些内核态的文件读写函数,通过这些文件操作函数可以直接访问内核中的其他驱动程序,实现驱动程序与驱动程序之间的堆叠。内核文件访问的接口比较高级,DDK提供了其他更加底层的接口,通过设备指针可以将分层驱动联系在一起,从而形成设备栈,维护设备栈的结构封装在了设备对象中。在Linux驱动中可以通过文件接口打开底层驱动,并且获取底层设备的BDEV指针,从而将两个驱动程序堆叠在了一起。
驱动程序需要解决设备的串行化访问,在Windows中提供了串行化访问的机制,这种机制实际上很简单。系统维护了一条设备访问的链表,如果访问的设备处于BUSY状态,那么访问的IRP会直接挂载到访问链表中,在内核会存在一个线程去处理在链表上等待的IRP,从而实现IRP的串行化。Windows为了简化编程,为用户提供了设备访问串行化的链表和相关机制,在编程过程中,用户只需向系统注册StartIO函数即可,其他过程无需关心。但是如果一条等待链表无法满足用户需求时,例如将读写请求分开处理,那么用户将需要通过DDK函数自己维护等待链表。通常,每个等待的IRP都会设置一个timeout函数,当IRP等待超时之后会调用用户注册的IRP_Cancel函数。我认为,这种串行化机制是一种比较简单的处理方式,Linux中虽然没有这种提法,但是我们在设计实现驱动过程中,常用这种思想。
Windows驱动在内存分配时需要小心,当例程运行的IRQL高于DISPATCH_LEVEL时,就不能采用分页内存了,因为在分页内存使用过程中可能会产生页故障中断,这种页故障中断是在APC_LEVEL的中断级别中运行的,这种级别低于DISPATCH_LEVEL,因此,会导致系统死锁。Windows的DPC中断下半部等过程都运行在DISPATCH_LEVEL,所以在分配内存页时需要小心。个人以为,内核程序就不需要使用分页内存了,第一,会影响内核程序的运行效率;第二,可以简化操作系统内存管理的实现。Windows驱动中的内存分配与Linux存在很多类似之处,同样提供了类似于Linux Mempool的内存池接口。
Windows驱动程序的编写应该不是什么难点,其最难的地方在于对一些概念和机制的理解,因此,只要做过一个驱动程序,其他的设备驱动就不在话下了。另外,Windows的驱动调试也是一个比较费时费力的过程,但是,Windows提供了很多驱动调试的工具,例如Windbg、IRPtrace、DeviceTree等,这些工具需要充分利用。总体来说,《Windows驱动开发详解》是本不错的开发用书,但是初学者在学习Windows驱动时,最好还是看一下WDM的驱动机制,这种机制在本书中介绍的并不是很多。
zengxiang11111_844991846 2009-9-28 14:12
huotingtu_505472073 2009-8-31 19:29