潇湘溪苑五鼠同人:windows WDM驱动程序设计

来源:百度文库 编辑:中财网 时间:2024/05/05 02:44:58

windows WDM驱动程序设计

分类: windows 驱动编程 2011-12-05 19:56 25人阅读 评论(3) 收藏 举报

      回顾微软10年,驱动开发模型从VxD->WDM->WDF,开发工具从VtoolsD-->DDK-->WDK,在这个过程中,出现了一些优秀的开发工具,windriver,driver studio 3.2 等。其中windriver适合用来调试硬件,driver studio 3.2 采用C++开发框架,用户只要简单的写几个回调函数就可以完成驱动的编写,但是该软件又微软新的驱动模型WDF的冲击,不在对它进行开发了,将最终版本定格在3.2,我最先学习驱动程序就是通过driver studio学习的。

     从windows 2000开始,开发驱动程序以WDM为基础,但开发难度大,为了改善这种局面,微软推出WDF,它以WDM为基础进行建模和封装,降低了开发的难道。

    (一)WDF驱动模型到windows WDF驱动程序设计中进行讲解:下面主要讲讲WDM驱动模型和WDF驱动模型的区别;

    1)WDF采用基于对象的技术对WDM进行封装,对象包括,属性,方法和事件,属性好比C++中类中的成员变量,而方法好比C++类中的成员函数,事件就是回调函数;

    2)无论内核模式的驱动程序或者用户模式的驱动程序,都采用同一套对象模型构建,采用同一个基础承载。这个基础就是WDF。 WDF包括KMDF和UMDF,两者都是继承了WDF,无论哪种框架,内部封装的方法还是WDM。

    3)WDF驱动模型封装了很多通用的功能,比如Pnp管理和电源管理,在WDM驱动模型下,Pnp和电源管理是很复杂的,最多有300种状态,但是WDF驱动模型下,WDF框架实现了Pnp和电源管理的功能,驱动开发者几乎不要写一行代码就可以实现Pnp和电源管理,举个不恰当的例子,WDF驱动框架就是对WDM驱动的一次抽象,好像C++中抽象类,其中以纯虚函数的形式实现了PNp和电源管理,如果用户没有特殊的要求直接使用框架提供的电源管理和Pnp管理就行了。如果要实现特殊的功能,框架提供了回调函数,驱动开发者实现回调函数的功能就可以了,这个回调函数就好比C++中的override,将基类提供的虚函数覆盖掉,实现自己的函数;

    4)WDF 采用了基于队列的I/O派遣队列,这样的好处是可以指定哪些IO在哪个队列上使用;这样我个人认为 好处就是,会减少IO同步操作,减少系统资源的占用,比如说锁的占用;

 

  (二) 讲了WDM驱动模型和WDF驱动模型的区别后,我们下面讲解WDM驱动模型,首先从WDM驱动模型中重要的数据结构谈起:

   1)驱动对象(DRIVER_OBJECT)

     每个驱动程序会有唯一的驱动对象与之对应,它作为驱动的一个实例在内核的I/O管理器负责加载。  typedef struct   {   PDEVICE_OBJECT DeviceObject;   PUNICODE_STRING HardwareDatabase;   PFAST_IO_DISPATCH FastIoDispatch;   PDRIVER_INITIALIZE DriverInit;   PDRIVER_STARTIO DriverStartIo;   PDRIVER_UNLOAD DriverUnload;   PDRIVER_DISPATCH MajorFunction[IRP_MJ_NUM+1];   } DRIVER_OBJECT,*PDRIVER_OBJECT; DeviceObject:指向驱动程序创建的设备对象。这个驱动程序调用IoCreateDevice的时候会自动赋予正确的设备对象指针。每个驱动程序会有一个或多个设备对象。其中,每个设备对象都有一个指针指向下一个驱动对象,最后一个设备对象指向空。此处的 DeviceObject指向驱动对象的第一个设备对象。通过 DeviceObject,就可以遍历驱动对象里的所有设备对象。 MajorFunction[IRP_MJ_NUM+1]:函数指针的数组 ,每一个指针指向一个函数,这个函数就是处理IRP的派遣函数。 对于驱动程序来说,每个驱动都有唯一的驱动对象与之对应,驱动对象由IO管理器负责加载;         将完驱动对象后,另外一个重要的数据结构就是设备对象了,在DRIVER_OBJECT中也包含DeviceObject对象的一项成员,一般在驱动的DriverEntry中调用IoCreateDevice创建设备对象,然后将设备对象和驱动对象联系起来,下面先看看设备对象的数据结构吧。      2)设备对象(DEVICE_OBJECT) typedef struct _DEVICE_OBJECT { .....   struct _DRIVER_OBJECT *  DriverObject;   struct _DEVICE_OBJECT *  NextDevice;   struct _DEVICE_OBJECT *  AttachedDevice; ....   PVOID                       DeviceExtension; ..... } DEVICE_OBJECT, *PDEVICE_OBJECT; DriverObject:指向驱动程序中的驱动对象; NextDevice:指向下一个设备对象,这里指的下一个设备对象是同属于一个驱动对象的设备,每个设备对象根据NextDevice域形成链表,从而可以枚举每个设备对象; AttachedDevice:指向下一个设备对象,如果有更高层的驱动附加到这个驱动的时候, AttachedDevice指向那个更高层的驱动。 DeviceExtension:指向的是设备的扩展对象,也就是程序员自己定义的结构体。  通过AttachedDevice可以将上层的驱动附加到下层驱动;而DeviceExtension数据结构也是很重要的数据结构,一般保存全局的信息,这样这些信息便于管理,比如同步加锁管理等等;增加驱动的可重入性。在DeviceExtension 结构中可以提供一个指向下层设备的指针,这样从可以从上层设备找到下层设备,同时也可以通过AttachedDevice从下层设备找到上层设备。这样方便查找设备  (三)介绍完WDM驱动模型重要的数据结构后,我们下面来看看,Windows应用程序怎么和驱动程序进行通信,对于windows 平台来说,应用程序和驱动程序进行通信,就是通过windows提供的api,如:CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseFile等等。  那么这些api是怎么联系到驱动程序的呢,举个简单的例子来说吧,CreateFile,当应用程序调用这个函数时,通过注册表,进入内核调用NtCreateFile,然后在内核用系统的IO管理器创建和发送IRP,对于IRP,就是输入输出请求包,驱动程序都是IRP驱动的,对于IO管理,Pnp管理,电源管理,都会转化成对应的IRP,然后转发给驱动程序,而相对驱动程序,主要的功能就是实现这些IRP的回调函数,而在WDF驱动模型下,IO是由IO管理器的IRP和队列共同作用的。这一点在WDM驱动模型和WDF驱动的差别中进行了讲解。 同样对于ReadFile和WriteFile的流程和CreateFile相似,都是通过IRP转发给驱动的。  (四)对于驱动人员来说,最关心的就是IO操作了,在WDM和WDF驱动中,驱动程序所创建的设备一般有2种读写方式:1)缓冲区方式;2)Direct方式 ;3)Neither缓冲区方式:在系统内核中创建和用户模式相同的缓冲区,用于发送或接收硬件的数据,然后将缓冲区的数据copy给应用程序; Direct方式:和缓冲区方式读写设备不同,直接方式读写设备,操作系统会将用户模式下得缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。Neither读写方式在此不在进行介绍。   对于windows驱动开发的难点,在于在驱动程序中减少缓冲区的copy,因为memcpy是很耗时间和cpu的。  (五)Irp派遣函数,派遣函数是windows驱动程序中的一个重要概念,驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的。 用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为IRP的数据结构,不同的IRP数据会被派遣到不同的派遣函数。 IRP定义:     在windows内核中,有一种数据结构叫做IRP(I/O Request Package),即输入输出请求包。它是与输入输出相关的重要数据结构,上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求,操作系统将I/O请求转换为相应的IRP数据,不同类型的IRP会根据类型传递到不同的派遣函数中。   (六)驱动的同步处理:和windows应用程序多线程同步类似,内核也有信号量,互斥,自旋锁,事件等同步机制,而这些同步机制 的函数就是在应用程序的函数前面加一个ke,比如对于事件来说:SetEvent对于KeSetEvent,WaitForSingleObject对应 KeWaitForSingleObject。我们知道应用程序多线程怎么同步,驱动程序内部怎么同步,那么驱动程序和应用程序之间如果同步呢,有一种方法,在应用程序中调用DeviceIoControl传递一个Event句柄给驱动程序,在驱动程序中锁定这个句柄,然后在驱动中调用KeSetEvent,在应用程序中通过WaitForSingleObject就可以同步了。其实对于DDK和WDK里面提供的函数也是这样操作的;下面在讲解下DeviceIoControl的流程: DeviceIoControl 分为同步和异步操作2种,主要是看最后一个参数,这个参数和CreateFile中一个参数配合使用,就可以实现同步操作和异步操作,对于DeviceIoControl同步操作而言,当应用程序调用DeviceIoControl时,其实里面会有一个等待的函数,当驱动程序通过 IoCompleteRequest 完成对应的这个Irp时,其实他就是发送一个信号给应用程序,基本原理和上面讲的应用程序和驱动程序同步类似。 同理当DeviceIoControl 异步调用时,也是类似的。  (七)在驱动程序中有分层的概念,比如说过滤驱动程序,在Ndis驱动中,过滤驱动是很重要的,比如你要实现一个防火墙或杀毒软件,最可靠的方法就是实现一个Ndis中间层驱动程序,在多层驱动程序框架下,RP请求一般会被传送到设备栈的最顶层的设备对象,顶层的设备对象可以选择直接结束IRP(IoCompleteRequest),也可以选择将IRP请求向下层的设备对象转发(IoCallDriver),如果是向下层设备对象转发IRP请求,IRP结束时,IRP会顺着设备栈的反方向原路返回。当得知下层驱动程序已经结束IRP请求时,本层设备对象可以选择继续讲IRP向上返回,或者选择将IRP再次传递给底层设备驱动。  这样防火墙原理就是做一个过滤驱动,将恶意程序过滤掉,不然它提交给驱动程序;杀毒软件也类似,在内核就讲病毒识别出来,在杀掉!   (八)Pnp即插即用即插即用体系结构的主要目标是不需要用户介入,而直接对设备安装、拆除操作进行自动配置。 (1)系统能够检测到新设备的插入,也能检测到设备被拔出; (2)如果总线接口容许,设备可以实现热插拔,并保证系统可以正常工作,如usb设备,当设备插入时,系统会自动加载其驱动,并且保证usb总线上其它设备能正常工作,另外当系统运行时,拔掉usb设备不会引起系统的崩溃。 状态转换图: 1)当一个设备被添加到系统时,Windows查找正确的驱动程序,并调用它的DriverEntry例程; 2)Pnp管理器然后调用驱动程序的AddDevice例程,告诉它要添加一个新设备,此时驱动程序创建自己的设备对象;即FDO。 3)在处理的过程中,驱动程序会收到IRP_MN_START_DEVICE的IRP,开始与硬件进行合适的对话; 4)如果一个设备要被拔出,Windows使用IRP_MN_REMOVE的IRP查询驱动程序设备。如果驱动程序不希望设备被删除,它将拒绝删除请求,然后发送IRP_MN_CANCEL_REMOVE的IRP,使它回到开始状态; 5)如果用户突然拔掉一个设备,在Windows中会发送IRP_MN_SUPPRISE_REMOVE的IRP,驱动必须处理好中断了的传输。   (九)电源管理 (省略)