WDM设备驱动程序开发平台
编写硬件设备驱动程序一直是一种具有很强挑战性的复杂工作,即便是编写过具有相当难度Win32程序的
开发人员,在编写设备驱动程序时也必须去应对种种不适,需要"洗脑"。编写设备驱动程序很象在执行一
项艰巨的任务:没有窗口、没有消息需要处理,很难对源代码进行调试设置,几乎所有支持库都无法调用
;更糟的是,由于设备驱动程序属于操作系统的信任部分,于是很容易伤害系统,对此开发人员却往往缺
乏一种保护手段去防止进程和Windows系统被损。
-
WDM(Windows driver model)是微软为开发人员提供的一种编写运行在Windows平台下新硬件驱动
程序的有效方法,此即所谓的Windows驱动程序模型。WDM提供了设备类(device classes),由此形成
的源代码可以运行在各类Windows平台:Win XP/2000/Me/98。需要指出的是,职业开发人员往往使用的
是更有深度的DDK,它除了可以开发WDM类型的驱动程序外,还可以开发非WDM类型Windows驱动程序。
WDM是一种便捷式的编程技术,它使开发人员写出的驱动程序可以跨平台运行,支持编写微软所支持
的WDM 总线驱动程序,在WDM中"总线"(Bus)的概念是一种附加在其它设备(包括物理设备、逻辑设备
、虚拟设备)上的设备协议,例如Windows XP内置的WDM技术支持的总线类型包括:PCI(Peripheral
Component Interconnect)、动态式即插即用串行I/O总线(如USB和IEEE 1394)、SCSI(Small
Computer System Interface)、NDIS(Network Driver Interface Specification)以及远程式NDIS
(RNDIS)。
新版WDM中的RNDIS属于新内容:如简化了对网络设备硬件的开发,减弱了网络设备对驱动程序的依
赖性,使最终用户对网络设备的设置安装更简便。WDM新版中对设备的支持主要包括:USB设备类,比如其
中的类HID(Human Interface Device);数码相机/扫描仪;通过IEEE 1394标准的视频捕捉设备;音
频;控制调制解调器的WinModem。在WDM 类代码中提供的port/miniport 驱动范式,支持第三方厂商为
其特殊设备编写"迷你型"驱动minidriver。Windows DDK其实是建立在WDM基础之上,它提供了:Driver
Verifier 及 Windows Hardware Compatibility Tests测试工具;被WHQL(Windows Hardware
Quality Labs)所识别的第三方驱动程序等集成环境。
WDM驱动程序的三种类型
WDM支持的驱动程序具有分层结构,换言之,对于一种设备而言,它可以具有三种类型的drivers:
总线driver,或者函数driver,或者过滤式驱动程序(它可以假定或修正设备的行为值)。为一台设备
服务的这些驱动程序链就是所谓驱动程序栈。一个驱动程序栈分阶段地处理用户的请求,这些驱动程序一
个个相互叠加在一起,低层的总线驱动程序可用于处理与硬件的所有基本联系,而中间的类驱动程序对整
个一类驱动程序提供共同的设施。
微软提供了针对Windows的总线驱动程序,并为第三方设备开发商提供有关服务,如枚举设备、对即
插即用和I/O所需电源的管理,并提供了独立于设备管理方式。设备开发商更多地是提供函数式驱动,其
基本内容包括:对设备的操作界面,对设备的读写句柄,对设备电源的管理策略。过滤式驱动程序安装在
驱动栈一个或多个设备之上或下端,它可以截获设备、或设备类、或总线的请求,判断这些请求,并可以
修改其内容或对其进行响应,例如USB键盘的高层过滤驱动程序可以增强加密检查,而适用于鼠标的低层
类过滤有助于提高鼠标性能。
函数式驱动是这样一种结构:属于某类设备的常规执行可以通过其类驱动实现,即是说驱动程序在开
发时,开发人员的工作只需要写出非常少的驱动代码minidriver去与硬件打交道,大部分工作可以通过
调用类驱动完成。微软提供的类驱动可以实现常见的系统任务,比如即插即用和电源管理。WDM class
drivers主要内容包括:
(1)流式类驱动,以内核模式支持多媒体内容;
(2)具有支持输入设备的HID类驱动;
(3)USB 和 IEEE 1394总线类驱动;
(4)支持串行和并行方式的存储协议。
Windows支持WDM驱动的各个系统内核组件包括:
(1)Kernel组件,指基本的同步、性能计算和及时、延缓与IRQ控制;
(2)Object Manager组件,对象说明;
(3)Executive执行组件,内存分配、互锁及列项操作;
(4)I/O 管理组件,包括I/O IRP(Request Packet)控制,设备对象,工作项目,注册表访问,
系统状态提示,DMA及中断;
(5)内存管理,虚拟到物理内存映像,物理内存托管和锁定,驱动程序映像内存锁定,机动I/O空间
;
(6)处理服务,系统线程生成和删除;
(7)Run-time Library,大容量外存,Unicode和数据类型转换;
(8)电源管理,电源状态改变,电源IRP控制,设备空闲检测;
(9)即插即用子系统,硬件检测和资源分配,PnP(Plug and Play) IRP控制以及硬件事件;
(10)WMI(Windows Management Instrumentation),用于支持设备测试以及检测指示数据的支撑结
构;
(11)内核式流,是连接流数据设备的支撑结构;
(12)硬件提取层,提取平台,访问和调用I/O端口及内存映像设备。
电源管理可以是系统级或设备级,前者可以请求整个系统关闭。系统电源有六种状态:不可完全开启
、完全关闭、三种休闲状态和一种休眠状态。设备级电源管理则有四种状态:完全开启、完全关闭,加两
种休眠状态。一个设备可自行关闭,即使系统其它部分正全速运行。
驱动程序组成原理与研制要点
一个设备驱动程序的模块组成主要包括:初始化自己、创建和删除设备、处理Win32打开和关闭文件
句柄的请求、处理Win32系统的I/O请求、对设备的串行化访问、访问硬件、调用其它驱动程序、取消I/O
请求、超时I/O请求、处理一个PnP设备被加入或删除的情况、处理电源管理请求、调用Windows管理诊断
WMI向系统管理员报告。
其中,"初始化"模块必不可少,所有驱动程序都需要通过分发例程处理用户的I/O请求。而WDM风格
的设备驱动程序需要有"即插即用(PnP)"模块以及安装标识的INF文件。在上述不同模块之间需要交互
,其中的一些交互可以直接通过函数调用,而大量信息需要通过数据结构传递(比如在"设备对象"之类的
数据结构中可以存储每个设备的信息)。
分发例程指的是"创建"、"读"、"写"、"关闭"等处理程序,它可以执行对IRP的初始化处理,检查参
数的合法性。在驱动程序编写中常遇到的非常棘手的所谓"同步问题",指的就是两个或更多的IRP分发例
程往往要"同时"运行,它不仅在多处理器系统中会遇到,而且在单处理器系统中有时也很尖锐:比如单处
理器系统上的一个分发例程在等待对低层驱动程序调用完成的同时,另一个IRP分发例程又被调用。处理
这种冲突的机制主要有两种:一种是临界段例程,依此保证代码不会被中断处理程序中断;另一种机制是
通过StartIo例程串行处理IRP,每个设备对象有一个内部的IRP队列,驱动程序的分发例程把IRP插入到
这个设备队列中,内核I/O管理器从该队列一个个取出IRP,并传递到驱动程序的StartIo例程,StartIo
例程串行处理IRP,以确保不与其它IRP处理程序冲突,但是StartIo例程仍然需要通过临界段例程避免与
硬件中断发生冲突。
中断是用于停止处理器对一个任务的执行,而被强制运行某个中断去处理代码。中断包括软件中断和
硬件中断。中断具有优先级,低优先级中断会被高优先级的中断所中断,以保证重要任务会优先执行。硬
件产生的中断总是比软件产生的中断优先级要高。硬件中断类型包括:设备中断请求级处理程序的执行、
配置文件定时器、时钟、同步器、处理器之间中断级、电源故障级;软件中断包括异步过程调用执行、线
程调度、延迟过程调用执行。至于常规线程执行则没有中断。
驱动程序的主要的初始化入口点是一个称为DriverEntry的例程。多数WDM设备对象是由PnP管理器调
用AddDevice入口点创建,该例程在插入新设备和安装INF文件指示相对应的驱动程序时被调用,之后,
一系列PnP IRP被发送到驱动程序,指示设备应何时启动和查询其功能。为了让应用程序认知驱动设备存
在,开发人员必须为每个设备对象创建使Win32可见的符号链接,这时可以有两种实现途径:一种是,采
用显式的"硬编码"符号链接名,对此,应用程序的源代码中也具有设备名的硬编码;还有一种是采用设备
接口,每个设备接口由一个GUID(全局唯一标识符)标识,对设备注册为具有某个特定的设备接口时就创
建了一个符号链接。用户程序可以搜索有特定GUID的所有设备。驱动程序的代码是由系统内核通过发送I/
O的IRP请求运行的。IRP是设备驱动程序的基本结构。
内核所调用的驱动程序中的其它多个例程就是所谓回调例程(Callback),每个回调例程具有一个
标准的函数原型,它适用于对应的环境。常用的回调例程包括:Unload(卸载驱动程序)、AddDevice(
添加一个新的PnP设备)、StartIo(串行处理IRP)、ISR(中断服务例程)、DpcForIsr(延时过程调用
例程)、临界段例程、撤消IRP的Cancel例程、一个低层驱动程序完成一个IRP处理时所调用的Completio
n例程、当DMA通道可用时调用的AdapterControl、Timer(秒级定时器回调例程)、低于1秒的超时例程C
ustomTimerDpc、用于处理工作队列的CustomDpc、Reinitialize(用于初始化耗时很长的驱动程序)。
硬件驱动程序应当具有容错性,比如连接电缆断开或缓存区过载,并能够将错误信息反馈到应用程序
。可以检查硬件状态位(如打印机缺纸指示),并能够处理快速I/O事件。
与其它Windows编程类似的是,在编写硬件驱动程序时如何分配或访问内存是一件很讲究细节的技术
活。我们知道,Windows中采用的虚拟内存意味着系统可以使用比物理内存更多的内存。虚拟内存的实现
方式是:将每个应用程序的可能地址空间划分成固定大小的块,这些块称为页(x86处理器的页大小为4KB
,Alpha处理器的页为8KB)。页可以驻留在物理内存,也可交换到硬盘。设备驱动程序分配的内存通常
有两种:可以是交换型的分页内存,也可以是永久驻留的非分页内存。如果试图在线程调度或更高中断级
访问分页内存,就会引起缺页故障,造成内核崩溃。即使是常规线程,如果访问非驻留的分页内存,内核
就会造成线程阻塞,直到内存管理器把内存装回内存。问题的关键是,在设计驱动程序时,一方面忌讳滥
用非分页内存,另一方面却不得不经常使用非分页内存。因为要在线程调度或更高中断级访问内存,必须
调用非分页内存。当完成内存调用后,应当对所有类型的内存通过ExFreePool释放。假如不释放内存,
将会减少内存,因为即使当驱动程序卸载后,内核并不收集这些分配了的内存。
没有编写过硬件驱动程序的开发人员,在听到硬件驱动程序编程时,他们从直觉上会联想到需要与硬
件资源打交道,包括I/O端口、中断、存储器地址和DMA(直接存储器访问)。其实,这是一种误解,因为
有很多驱动程序不需要直接调用任何低层硬件资源,比如USB客户端驱动程序就不需要任何硬件资源,所
有与硬件相关的琐细工作都有USB总线驱动程序完成,USB客户端驱动程序只是对总线驱动程序发出请求。
WDM的版本问题
虽然Windows XP/2000/98/Me都支持WDM,但是由于历史原因,不同版本WDM内容并不相同。当然,
新版WDM都是旧版WDM的超集。跨系统使用的WDM driver通常采用IoIsWdmVersionAvailable例程去判定
当前运行系统支持WDM的版本号。按照常理说,保证跨平台兼容性的最简单的方式应该是:写一个驱动程
序时仅包括最低版本WDM所支持的那些功能。但是,这种思路往往行不通,因为驱动程序不仅要适应不同
的OS,而且还应当具有发挥具体系统特色优势的附加代码。
最新编写出的内核模式的驱动程序应该属于WDM类型,其开发平台则应为Windows XP。任何WDM
drivers都必须支持PnP、电源管理,并能执行WMI。一般的原则是,即使是用于更低版本的驱动程序,也
最好首先在Windows XP下开发然后做移植,这种策略同样适用于那些并不完全适合WDM模式的硬件设备。
如果要写出WDM驱动程序,开发人员必须通过最新的Windows DDK了解不同Windows平台的差异,以及总线
和设备的相关问题。比如:其一,不同Windows平台的驱动程序代码执行会不同,主要由于WDM兼容了Win
dows XP/ 2000/ 98/Me中的不同系统结构。在其中一个平台工作正常的driver,到其它平台时需要全面
测试,尤其用于多处理器系统时要特别注意。其二,WDM并不支持所有类型硬件。其三,INF文件必须适应
平台之间的差异。
驱动程序分为2类,一个是Kernel模式驱动,另一个是Windows模式驱动,2种模式本质是相同,但细节不
同,本文介绍的是内核模式驱动和驱动程序的安装、使用。
驱动程序同普通的EXE,DLL一样,都属于PE文件,而且都有一个入口函数。但EXE中,入口函数是main()
/WinMain()和Unicode的wmain()/wWinmain(),DLL的入口函数则可有可无,它是DllMain()。驱动程序
也有入口函数,而且是必须的,它是DriverEntry(),再次提示,它是必须的,因为I/O管理器会首先调
用驱动程序的DriverEntry(),它的作用就像DllMain()--完成一些初始化工作。DriverEntry()一共有2
个参数:1PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针,我们操作驱动程序,全靠它,它
是由I/O管理器传递进来的;2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使
用并不多,但要注意,在DriverEntry()返回后,它可能会消失,所以如果需要使用,记住先要保存下来
。DriverEntry()的返回一个NTSTATUS值,它是一个ULONG值,具体的定义,请参见DDK中的NTSTATUS.H
头文件,里边有详细的定义。
既然要写驱动版的“Hello World”,就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,
IOCTL宏,或者直接用ReadFile()或WriteFile()进行读写,在本文里我就采用一种简单的、但又很常用
的IOCTL宏,它依赖的IRP派遣例程是IRP_MJ_DEVICE_CONTROL,Win32程序使用DeviceIoControl()与驱
动进行通信,根据不同的IOCTL宏,输出不同的调试信息。为了简便,我并没有使用ReadFile()将信息读
出来,而是直接用DbgPrint()输出,所以需要使用DbgView查看,其他调试工具也可以。PS:偷懒!
驱动程序与I/O管理器通信,使用的是IRP,即I/O请求包。IRP分为2部分:1)IRP首部;2)IRP堆栈。IR
P首部信息如下:
IRP首部:
IO_STATUS_BLOCK IoStatus 包含I/O请求的状态
PVOID AssociatedIrp.SystemBuffer 如果执行缓冲区I/O,这个指针指向系统缓冲区
PMDL MdlAddress 如果直接I/O,这个指针指向用户缓冲区的存储器描述符表
PVOID UserBuffer I/O缓冲区的用户空间地址
IRP堆栈:
UCHAR MajorFunction 指示IRP_MJ_XXX派遣例程
UCHAR MinorFunction 同上,一般文件系统和SCSI驱动程序使用它
union Parameters MajorFunction的联合类型
{
struct Read IRP_MJ_READ的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write IRP_MJ_WRITE的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl IRP_MJ_DEVICE_CONTROL参数
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject 请求的目标设备对象的指针
PFILE_OBJECT FileObject 请求的目标文件对象的指针,如果有的话
操作IRP。对于不同的IRP函数,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆栈;还有操作I
RP整体,下面是一些常用的函数:
IRP整体:
名称 描述 调用者
IoStartPacket 发送IRP到Start I/O例程 Dispatch
IoCompleteRequest 表示所有的处理完成 DpcForIsr
IoStartNextPacket 发送下一个IRP到Start I/O例程 DpcForIsr
IoCallDriver 发送IRP请求 Dispatch
IoAllocateIrp 请求另外的IRP Dispatch
IoFreeIrp 释放驱动程序分配的IRP I/O Completion
IRP堆栈:
名称 描述 调用者
IoGetCurrentIrpStackLocation 得到调用者堆栈的指针 Dispatch
IoMarkIrpPending 为进一步的处理标记调用者I/O堆栈 Dispatch
IoGetNextIrpStackLocation 得到下一个驱动程序的I/O堆栈的指针 Dispatch
IoSetNextIrpStackLocation 将I/O堆栈指针压入堆栈 Dispatc
在驱动程序,IRP派遣例程起着很重要的作用,每个IRP派遣例程,几乎都有对应的Win32函数,下面是几
个常用的:
IRP派遣例程:
名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或者丢弃输入缓冲区 FlushFileBuffers
FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown
=======================================================================================
=============
下面开始写我们的驱动版的“Hello World”,程序很简单,先介绍一下流程:
1,调用IoCreateDevice()创建一个设备,并返回一个设备对象。
2,调用IoCreateSynbolicLink()创建一个符号连接,使Win32程序可以使用驱动程序
3,设置IRP_MJ_DEVICE_CONTROL派遣例程HelloWorldDispatch()和卸载例程HelloWorldUnLoad()。
如果Win32程序使用DeviceIoControl(),则执行HelloWorldDispatch()函数
4,调用IoGetCurrentIrpStackLocation()得到当前调用者的IRP指针
5,取得IO控制代码,完成后使用IoCompleteRequest()完成IRP操作
如果使用ControlService()停止驱动程序,则执行HelloWorldUnLoad()函数
4,调用IoDeleteSymbolicLink()删除符号连接
5,调用IoDeleteDevice()删除已建立的设备
驱动入口DriverEntry()
//创建设备
IoCreateDevice(DriverObject, //驱动程序对象
0, //扩展设备的大小,由于不需要,所以置0
&DeviceNameString, //设备名称
FILE_DEVICE_UNKNOWN, //设备类型
0, //指示设备允许的操作
FALSE, //如果为TRUE,表示只能有一个线程使用该设备,为FALSE,
则没有限制
&lpDeviceObject); //返回的设备对象
//创建符号连接
IoCreateSymbolicLink(&DeviceLinkString, //存放符号连接的UNICODE_STRING
&DeviceNameString); //设备名称
//派遣例程和卸载例程
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;吧
DriverObject->DriverUnload=HelloWorldUnLoad;
IRP派遣例程HelloWorldDispatch()
IrpStack=IoGetCurrentIrpStackLocation(pIrp); //得到当前调用者的IRP堆栈
//获取IO控制代码,并执行指定操作,这里只是DbgPrint()
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes) {
......
IoCompleteRequest(pIrp,IO_NO_INCREMENT); //完成IRP操作
卸载例程HelloWorldUnLoad()
//删除符号连接和设备
IoDeleteSymbolicLink(&DeviceLinkString);
IoDeleteDevice(DriverObject->DeviceObject);
=======================================================================================
=============完整代码:
#ifndef __HELLOWORLD_C__
#define __HELLOWORLD_C__
#define DEBUGMSG
#include "HelloWorld.h"
//驱动入口
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
NTSTATUS ntStatus=STATUS_SUCCESS;
PDEVICE_OBJECT lpDeviceObject=NULL; //指向设备对象的指针
UNICODE_STRING DeviceNameString; //设备名称
UNICODE_STRING DeviceLinkString; //符号连接
//调试信息
#ifdef DEBUGMSG
DbgPrint("Starting DriverEntry()\n");
#endif
RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME); //初始化Unicode字符串
//创建设备
ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,
0,FALSE,&lpDeviceObject);
//使用NT_SUCCESS宏检测函数调用是否成功
if (!NT_SUCCESS(ntStatus))
{
#ifdef DEBUGMSG
DbgPrint("Error IoCreateDevice()\n");
#endif
goto Error;
}
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
//创建符号连接
ntStatus=IoCreateSymbolicLink(&DeviceLinkString,&DeviceNameString);
if (!NT_SUCCESS(ntStatus))
{
#ifdef DEBUGMSG
DbgPrint("Error IoCreateSymbolicLink()\n");
#endif
goto Error;
}
//设置IRP派遣例程和卸载例程
DriverObject->MajorFunction[IRP_MJ_CREATE]=//HelloWorldDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE]=//HelloWorldDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
DriverObject->DriverUnload=HelloWorldUnLoad;
return ntStatus;
Error:
#ifdef DEBUGMSG
DbgPrint("Error DriverEntry()\n");
#endif
return ntStatus;
}
NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
{
NTSTATUS ntStatus=STATUS_SUCCESS;
ULONG IoControlCodes=0; //I/O控制代码
PIO_STACK_LOCATION IrpStack=NULL; //IRP堆栈
//设置IRP状态
pIrp->IoStatus.Status=STATUS_SUCCESS;
pIrp->IoStatus.Information=0;
#ifdef DEBUGMSG
DbgPrint("Starting HelloWorldDispatch()\n");
#endif
IrpStack=IoGetCurrentIrpStackLocation(pIrp); //得到当前调用者的IRP
switch (IrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
#ifdef DEBUGMSG
DbgPrint("IRP_MJ_CREATE\n");
#endif
break;
case IRP_MJ_CLOSE:
#ifdef DEBUGMSG
DbgPrint("IRP_MJ_CLOSE\n");
#endif
break;
case IRP_MJ_DEVICE_CONTROL:
#ifdef DEBUGMSG
DbgPrint("IRP_MJ_DEVICE_CONTROL\n");
#endif
//取得I/O控制代码
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)
{
//启动
case START_HELLPWORLD:
DbgPrint("Starting \"Hello World\"\n");
break;
//停止
case STOP_HELLPWORLD:
DbgPrint("Stoping \"Hello World\"\n");
break;
default:
pIrp->IoStatus.Status=STATUS_INVALID_PARAMETER;
break;
}
break;
default:break;
}
ntStatus=pIrp->IoStatus.Status;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return ntStatus;
}
VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING DeviceLinkString;
PDEVICE_OBJECT DeviceObjectTemp1=NULL;
PDEVICE_OBJECT DeviceObjectTemp2=NULL;
#ifdef DEBUGMSG
DbgPrint("Starting HelloWorldUnLoad()\n");
#endif
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&DeviceLinkString); //删除符号连接
if (DriverObject)
{
DeviceObjectTemp1=DriverObject->DeviceObject;
//删除设备
while (DeviceObjectTemp1)
{
DeviceObjectTemp2=DeviceObjectTemp1;
DeviceObjectTemp1=DeviceObjectTemp1->NextDevice;
IoDeleteDevice(DeviceObjectTemp2);
}
}
}
#endif
=======================================================================================
=============
驱动程序的编译需要使用DDK中的build实用程序,它是一个命令行程序,使用不是很方便。VC知识库有
一篇在VC++ 6.0中编译驱动的文章,有兴趣可以去看看。
1,makefile
编译驱动程序,首先应该准备一个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.def
正如描述的那样,不要修改这个文件---它是通用的!
2,sources
准备的第二个文件就是sources,它描述了一些编译的细节。针对本文的程序,sources文件的内容是这
样的:
TARGETNAME=HelloWorld //驱动名称
TARGETPATH=. //编译后SYS的路径
TARGETTYPE=DRIVER //类型为驱动程序
SOURCES= HelloWorld.c //只有一个源文件
有了这2个文件后,就可以使用build进行编译了。进入「开始」菜单\程序\Development Kits\Windows
2000 DDK,
分别有3个CMD程序:1)Checked 64 Bit Build Environment,“Debug”的64位版本;2)Checked
Build Environment
“Debug”的32位版本;3)Free Build
Environment,“Release”的32位版本。不用说,肯定是使用Free Build Environment。
New or updated MSVC detected. Updating DDK environment....
Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation...Completed.
C:\NTDDK>cd\
C:\>cd HelloWorld
C:\HelloWorld>build
BUILD: Object root set to: ==> objfre
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading c:\NTDDK\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:\helloworld directory for files to compile.
c:\helloworld - 1 source files (127 lines)
BUILD: Saving c:\NTDDK\build.dat...
BUILD: Compiling c:\helloworld directory
Compiling - helloworld.c for i386
BUILD: Linking c:\helloworld directory
Linking Executable - i386\helloworld.sys for i386
BUILD: Done
1 file compiled
1 executable built
C:\HelloWorld>
现在C:\HelloWorld\i386目录下,就有了HelloWorld.sys。
=======================================================================================
=============
驱动程序的安装如同安装服务一样,唯一不同的是,创建服务时,类型是内核驱动,其他跟操作服务没什
么区别。
安装驱动程序流程:
1,调用OpenSCManager()打开服务控制管理器
2,调用CreateService()创建一个服务,服务类型为内核驱动
3,调用OpenService()取得服务句柄
启动服务
4,调用StartService()启动服务
停止服务
4,调用ControlService()停止服务
删除服务
4,调用DeleteService()删除服务
5,调用CloseServiceHandle()关闭服务句柄
操作驱动程序流程:
1,调用CreateFile()取得设备句柄
2,调用DeviceIoControl()传递I/O控制代码
3,调用CloseHandle()关闭设备句柄
这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码
完整代码:
#include
#include
#include
#define DEVICE_HELLO_INDEX 0x860
#define START_HELLPWORLD
CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define STOP_HELLPWORLD
CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define erron GetLastError()
#define MY_DEVICE_NAME ""
#define MY_DEVICE_START "-start"
#define MY_DEVICE_STOP "-stop"
BOOL DriverControl (TCHAR *Maik);
void Usage (TCHAR *Paramerter);
int main (int argc,TCHAR *argv[])
{
if (argc!=2)
{
Usage(argv[0]);
return 0;
}
if (strcmpi(argv[1],MY_DEVICE_START)==0 || strcmpi(argv[1],MY_DEVICE_STOP)==0)
DriverControl(argv[1]);
else
{
Usage(argv[0]);
return 0;
}
return 0;
}
BOOL DriverControl (TCHAR *Maik)
{
HANDLE hDevice=NULL; //设备句柄
DWORD RetBytes=0;
//获得设备句柄
hDevice=CreateFile(MY_DEVICE_NAME,GENERIC_READ |
GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hDevice==INVALID_HANDLE_VALUE)
{
#ifdef DEBUGMSG
printf("CreateFile() GetLastError reports %d\n",erron);
#endif
return FALSE;
}
//启动
if (strcmpi(Maik,MY_DEVICE_START)==0)
{
//传递启动的I/O控制代码
if (!(DeviceIoControl(hDevice,START_HELLPWORLD,NULL,0,NULL,0,&RetBytes,NULL)))
{
#ifdef DEBUGMSG
printf("DeviceIoControl() GetLastError reports %d\n",erron);
#endif
CloseHandle(hDevice);
return FALSE;
}
}
//停止
if (strcmpi(Maik,MY_DEVICE_STOP)==0)
{
//传递停止的I/O控制代码
if (!(DeviceIoControl(hDevice,STOP_HELLPWORLD,NULL,0,NULL,0,&RetBytes,NULL)))
{
#ifdef DEBUGMSG
printf("DeviceIoControl() GetLastError reports %d\n",erron);
#endif
CloseHandle(hDevice);
return FALSE;
}
}
if (hDevice)
CloseHandle(hDevice); //关闭句柄
return TRUE;
}
void Usage (TCHAR *Paramerter)
{
fprintf(stderr,"=======================================================================
=====\n"
" 驱动版Hello World\n"
"作者:dahubaobao[EST]\n"
"主页: or "
"邮件:"
"OICQ:382690\n\n"
"%s -start\t启动\n"
"%s -stop \t停止\n\n"
"本程序只是用做代码交流,如有错误,还请多多包含!\n"
"============================================================================\n"
,Paramerter,Paramerter);
}
头文件HelloWorld.h代码:
#ifndef __HELLOWORLD_H__
#define __HELLOWORLD_H__
#include
#define DEVICE_HELLO_INDEX 0x860
//2个IOCTL宏
#define START_HELLPWORLD
CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define STOP_HELLPWORLD
CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define NT_DEVICE_NAME L"" //设备名称
#define DOS_DEVICE_NAME L"" //符号连接
NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp);
VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject);
#endif