Wednesday, August 24, 2011

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

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 __cplusplus12 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)犹如控制台程序需要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、创建设备例程(函数)


******
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卸载驱动例程


卸载驱动例程是我们在DriverEntry 中自己定义的,当驱动被卸载时I/O管理器负责调用该例程,它主要做一些扫尾处理的工作。
KdPrint
由于驱动程序工作于内核态,不像控制台的程序一样可以使用printf 输出一些信息,也不像Win32 程序可以通过MessageBox 来弹出一个对话框,它要想输出一些信息,就需要调用DbgPrint 函数,不过这个函数输出的信息我们无法直接看到,需要使用一些专门的工具,比如DbgView (KmdManager)等。
有些内容我们只想在调试版输出,在发行版忽略,因此DDK 中定义了一个宏KdPrint,它在发行版不被编译,只在调试版才会运行。KdPrint是这样定义的:
#define KdPrint(_x_) DbgPrint _x_,在使用时最外层要有两个连续的括号。
5派遣例程


派遣例程是处理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 驱动开发技术详解

代码
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 }

代码
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 }

代码
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 }

代码


#1楼[楼主]2010-10-22 15:41 | edwardlewiswe
"采用C++编程,所以需要用extern "C",因为我们导入的是C的函数的符号表",系统内核是有C语言编写的,连接时要调用相应的函数,所以应当把驱动头文件编译成C语言形式。
#2楼[楼主]2010-10-22 16:06 | edwardlewiswe
设备名称用UNICODE字符串指定,且必须是“\Device\[设备名]”形式。用户模式下,通过两种方法找到设备:
法1:符号连接
法2:通过设备接口。
一般虚拟设备用FILE_DEVICE_UNKNOWN类型

No comments:

Post a Comment