Windows驱动开发技术详解笔记(1) 入门基础-驱动程序结构

Windows 驱动程序分为两类:一类是不支持即插即用功能的NT式驱动程序;一类是支持即插即用功能的WDM驱动式程序。


NT

1Driver.h头文件中包含了开发NT式驱动所需要的NTDDK.h,此外还定义了几个标志来指明函数和变量分配在分页内存还是非分页内存中。Windows驱动程序的入口函数是DriverEntry函数。WDM式的驱动程序要导入的头文件是WDM.h

代码
1 #ifdef __cplusplus
2
3  extern "C"
4
5 {
6
7  #endif
8
9 #include <NTDDK.h>
10
11 #ifdef __cplusplus
12
13 }
14
15  #endif
16
17  #define PAGEDCODE code_seg("PAGE")
18
19  #define LOCKEDCODE code_seg()
20
21  #define INITCODE code_seg("INIT")
22
23  #define PAGEDDATA data_seg("PAGE")
24
25  #define LOCKEDDATA data_seg()
26
27  #define INITDATA data_seg("INIT")
28
29 #define arraysize(p) (sizeof(p)/sizeof((p)[0]))
30
31 typedef struct _DEVICE_EXTENSION {
32
33 PDEVICE_OBJECT pDevice;
34
35 UNICODE_STRING ustrDeviceName; //设备名称
36
37 UNICODE_STRING ustrSymLinkName; //符号链接名
38
39 } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
40
41 // 函数声明
42
43 NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
44
45 VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
46
47 NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
48
49 IN PIRP pIrp);

说明:

1)采用C++编程,所以需要用extern "C",因为我们导入的是C的函数的符号表。

2)在驱动中用到的变量或函数都需要指定分配在分页或非分页内存中,分页内存在物理内存不够的情况下可能会被交换出去,对于一些需要高IRQL的例程绝对不能被交换出页面,因此它们必须被定义为非分页内存。

3DriverEntry需要放在INIT标志的内存中。

2、驱动程序的入口函数

代码
1 /************************************************************************
2
3 * 函数名称:DriverEntry
4
5 * 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
6
7 * 参数列表:
8
9 pDriverObject:从I/O管理器中传进来的驱动对象
10
11 pRegistryPath:驱动程序在注册表的中的路径
12
13 * 返回值:返回初始化驱动状态
14
15 *************************************************************************/
16
17 #pragma INITCODE
18
19 extern "C" NTSTATUS DriverEntry (
20
21 IN PDRIVER_OBJECT pDriverObject,
22
23 IN PUNICODE_STRING pRegistryPath )
24
25 {
26
27 NTSTATUS status;
28
29 KdPrint(("Enter DriverEntry\n"));
30
31 //注册其他驱动调用函数入口
32
33 pDriverObject->DriverUnload = HelloDDKUnload;
34
35 pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
36
37 pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
38
39 pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
40
41 pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
42
43 //创建驱动设备对象
44
45 status = CreateDevice(pDriverObject);
46
47 KdPrint(("DriverEntry end\n"));
48
49 return status;
50
51 }

1)犹如控制台程序需要mainWin32 程序需要WinMainDLL 程序需要DllMain 一样,驱动程序也有自己的入口点,即DriverEntryDriverEntry 需要被加载到INIT 内存区域中,这样当驱动被卸载后它可以退出内存。

2)DriverEntry 是由内核中的I/O 管理器负责调用的,它有两个参数DriverObject RegistryPath(当然形参的名字可以自己改变)。其中DriverObject 是由I/O管理器传递进来的驱动对象,RegistryPath 则指向此驱动负责的注册表。

3)我们可以看到DriverEntry 首先是定义了一些变量,然后调用IoCreateDevice 创建设备对象,紧接着调用IoCreateSymbolicLink 创建符号链接。HelloDDKDispatchRoutine等是驱动程序在向Windows I/O 管理器注册一些回调函数。上面代码的含义是:当驱动程序将被卸载时自动调用HelloDDKUnload例程;当驱动程序接收到 IRP_MJ_CREATE 时自动调用HelloDDKDispatchRoutine

现在可以把IRP_MJ_CREATE理解成类似ring3消息,当我们的驱动程序接收到不同的IRP就表明发生了不同的事件,然后我们及时给予处理。

4)KdPrint是宏,用来输出。类似于MFC中的TRACE

5#pragma INITCODE来指明此函数加载到INIT内存函数中。

6)在 驱动对象DriverObject 中,有个函数指针数组MajorFunction,它里面的每一个元素都记录着一个函数的地址对应着相应的IRP,我们可以通过简单地设置这个数组将 IRP 与相应的派遣函数关联起来。诸如IRP_MJ_CREATE 其实是使用#define 定义的一个宏,比如IRP_MJ_CREATE 实际上就是0x00,而IRP_MJ_CLOSE 则是0x02 等。由于在进入DriverEntry 之前,I/O 管理器会将_IopInvalidDeviceRequest 的地址填满整个MajorFunction 数组,因此除了我们自行设置过的IRP 之外,其他的IRP 都与系统默认的_IopInvalidDeviceRequest 函数关联。

3、创建设备例程(函数)

代码
1 /************************************************************************
2
3 * 函数名称:CreateDevice
4
5 * 功能描述:初始化设备对象
6
7 * 参数列表:
8
9 pDriverObject:从I/O管理器中传进来的驱动对象
10
11 * 返回 值:返回初始化状态
12
13 *************************************************************************/
14
15 #pragma INITCODE
16
17 NTSTATUS CreateDevice (
18
19 IN PDRIVER_OBJECT pDriverObject)
20
21 {
22
23 NTSTATUS status;
24
25 PDEVICE_OBJECT pDevObj;
26
27 PDEVICE_EXTENSION pDevExt;
28
29 //创建设备名称
30
31 UNICODE_STRING devName;
32
33 RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
34
35 //创建设备
36
37 status = IoCreateDevice( pDriverObject,
38
39 sizeof(DEVICE_EXTENSION),
40
41 &(UNICODE_STRING)devName,
42
43 FILE_DEVICE_UNKNOWN,
44
45 0, TRUE,
46
47 &pDevObj );
48
49 if (!NT_SUCCESS(status))
50
51 return status;
52
53 pDevObj->Flags |= DO_BUFFERED_IO;
54
55 pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
56
57 pDevExt->pDevice = pDevObj;
58
59 pDevExt->ustrDeviceName = devName;
60
61 //创建符号链接
62
63 UNICODE_STRING symLinkName;
64
65 RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
66
67 pDevExt->ustrSymLinkName = symLinkName;
68
69 status = IoCreateSymbolicLink( &symLinkName,&devName );
70
71 if (!NT_SUCCESS(status))
72
73 {
74
75 IoDeleteDevice( pDevObj );
76
77 return status;
78
79 }
80
81 return STATUS_SUCCESS;
82
83 }

******

RtlInitUnicodeString

http://msdn.microsoft.com/en-us/library/ms648420%28VS.85%29.aspx

IoCreateDevice

http://msdn.microsoft.com/en-us/library/ff548397%28VS.85%29.aspx

1),前面我们创建的设备对象虽然有个参数指定了设备名称,但是这个设备名称只能在内核态可见,也就说ring3 的应用层程序是看不见它的,因此驱动程序需要向ring3 公布一个符号链接,这个链接指向真正的设备名称,而ring3 的应用程序可以通过该符号链接找到驱动程序进行通信。实际上我们经常所说的C 盘、D 盘就是一个符号链接,它们在内核中的真正设备对象是“\Device\HarddiskVolume1”“\Device \HarddiskVolume2”。在内核模式下,符号链接是以“\??\”( 或“\DosDevices\”)开头的,如C 盘就是“\??\C:”

而在用户模式下,则是以“\\.\”开头的,如C 盘就是“\\.\C:”

4卸载驱动例程

代码
1 #pragma PAGEDCODE
2
3 VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
4
5 {
6
7 PDEVICE_OBJECT pNextObj;
8
9 KdPrint(("Enter DriverUnload\n"));
10
11 pNextObj = pDriverObject->DeviceObject;//由驱动对象得到设备对象
12
13 while (pNextObj != NULL)
14
15 {
16
17 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
18
19 pNextObj->DeviceExtension;
20
21 //删除符号链接
22
23 UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
24
25 IoDeleteSymbolicLink(&pLinkName);
26
27 //删除设备对象
28
29 pNextObj = pNextObj->NextDevice;
30
31 IoDeleteDevice( pDevExt->pDevice );
32
33 }
34
35 }

卸载驱动例程是我们在DriverEntry 中自己定义的,当驱动被卸载时I/O管理器负责调用该例程,它主要做一些扫尾处理的工作。

KdPrint

由于驱动程序工作于内核态,不像控制台的程序一样可以使用printf 输出一些信息,也不像Win32 程序可以通过MessageBox 来弹出一个对话框,它要想输出一些信息,就需要调用DbgPrint 函数,不过这个函数输出的信息我们无法直接看到,需要使用一些专门的工具,比如DbgView (KmdManager)等。

有些内容我们只想在调试版输出,在发行版忽略,因此DDK 中定义了一个宏KdPrint,它在发行版不被编译,只在调试版才会运行。KdPrint是这样定义的:

#define KdPrint(_x_) DbgPrint _x_,在使用时最外层要有两个连续的括号。

5派遣例程

代码
1 /************************************************************************
2
3 * 函数名称:HelloDDKDispatchRoutine
4
5 * 功能描述:对读IRP进行处理
6
7 * 参数列表:
8
9 pDevObj:功能设备对象
10
11 pIrp:从IO请求包
12
13 * 返回 值:返回状态
14
15 *************************************************************************/
16
17 #pragma PAGEDCODE
18
19 NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
20
21 IN PIRP pIrp)
22
23 {
24
25 KdPrint(("Enter HelloDDKDispatchRoutine\n"));
26
27 NTSTATUS status = STATUS_SUCCESS;
28
29 // 完成IRP
30
31 pIrp->IoStatus.Status = status;
32
33 pIrp->IoStatus.Information = 0; // bytes xfered
34
35 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
36
37 KdPrint(("Leave HelloDDKDispatchRoutine\n"));
38
39 return status;
40
41 }

派遣例程是处理IRP的。

编译

DDK方式

进入相应目录,Build

Source如下:

TARGETNAME=HelloDDK

TARGETTYPE=DRIVER

TARGETPATH=OBJ //编译输出目录

INCLUDES=$(BASEDIR)\inc;\

$(BASEDIR)\inc\ddk;\

SOURCES=Driver.cpp\

VC方式:参见[1]第一章。

驱动安装

DriverStudio中的工具:DriverMonitor

WDM式驱动

/************************************************************************

* 函数名称:DriverEntry

* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象

* 参数列表:

pDriverObject:I/O管理器中传进来的驱动对象

pRegistryPath:驱动程序在注册表的中的路径

* 返回 值:返回初始化驱动状态

*************************************************************************/

#pragma INITCODE

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,

IN PUNICODE_STRING pRegistryPath)

{

KdPrint(("Enter DriverEntry\n"));

pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice; //相比NT,此回调函数的作用是创建设备对象并由PNP管理器调用。

...

/************************************************************************

* 函数名称:HelloWDMAddDevice

* 功能描述:添加新设备

* 参数列表:

DriverObject:I/O管理器中传进来的驱动对象

PhysicalDeviceObject:I/O管理器中传进来的物理设备对象

* 返回 值:返回添加新设备状态

*************************************************************************/

#pragma PAGEDCODE

NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,

IN PDEVICE_OBJECT PhysicalDeviceObject)

{

PAGED_CODE();

KdPrint(("Enter HelloWDMAddDevice\n"));

...

PAGED_CODE();//宏,只有check版本中有效,当此例程所在的中断请求超过APC_LEVEL时,会产生一个断言。

***

IRP_MN_REMOVE_DEVICE的处理,类似于NT式驱动中的卸载例程,而在WDM式驱动中,卸载例程几乎不做处理。

******

Source文件:

TARGETNAME=HelloWDM

TARGETTYPE=DRIVER

DRIVERTYPE=WDM

TARGETPATH=OBJ

INCLUDES=$(BASEDIR)\inc;\

$(BASEDIR)\inc\ddk;\

SOURCES=HelloWDM.cpp\

编译:

NT

安装:

EzDriverInstall安装或控制面版中添加硬件。

实际上,常见的Windows 驱动程序是可以分成两类的:一类是不支持即插即用功能的NT 式驱动程序,另一类是支持即插即用的WDM 式驱动程序。NT 式驱动的安装是基于服务的,可以通过修改注册表进行,也可以直接通过服务函数如CreateService 进行安装;但WDM 式驱动不同,它安装的时候需要通过编写一个inf 文件进行控制。除此之外,它们所使用的头文件也不大相同,例如NT 式驱动往往需要导入一个名为“ntddk.h”的头文件,而WDM 式驱动需要的却是“wdm.h”头文件。我们在学习的过程中所编写的大多属于NT 式驱动,除非我们需要自己的设备支持即插即用,才需要考虑编写
WDM 式驱动程序。

参考

1Windows 驱动开发技术详解

2http://msdn.microsoft.com/en-us/library/ff565757%28VS.85%29.aspx

posted @ 2011-08-11 11:05  飞翔荷兰人  阅读(634)  评论(0编辑  收藏  举报

I Love Lina~