<学习笔记>Windows驱动技术开发详解__驱动程序的基本结构

驱动程序中重要的数据结构


驱动对象:DRIVER_OBJECT

每个驱动程序都会有唯一的驱动对象与之相对应,这个驱动对象是在驱动加载的时候,被内核中对象管理程序创建的。

DRIVER_OBJECT数据结构:

typedef struct _DRIVER_OBJECT {
    CSHORT Type;
    CSHORT Size;	
    PDEVICE_OBJECT DeviceObject;
    ULONG Flags;
    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;
    UNICODE_STRING DriverName;
    PUNICODE_STRING HardwareDatabase;
    PFAST_IO_DISPATCH FastIoDispatch;
    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
	
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;

其中比较重要的字段:

DeviceObject(设备对象):每个驱动程序都会有一个或多个设备对象,所有设备对象以链表的形式串联起来,驱动对象中的设备对象字段指的是所有设备对象的第一个对象,通过它可以遍历所有设备对象。

DriverName(驱动名称):该驱动的名称,采用UNICODE编码该字符串的一般形式为\Driver\[驱动程序名称]。

DriverUnload(驱动卸载):指向该驱动程序的卸载函数地址。

MajorFunction(派遣函数指针数组):该数组的每个指针成员指向一个响应的处理IRP的派遣函数。


设备对象:DEVICE_OBJECT

每个驱动程序都会有一个或多个设备对象,所有设备对象以链表的形式串联起来,驱动对象中的设备对象字段指的是所有设备对象的第一个对象,通过它可以遍历所有设备对象。

DEVICE_OBJECT的数据结构:

typedef struct _DEVICE_OBJECT {
    CSHORT Type;
    USHORT Size;
    LONG ReferenceCount;
    struct _DRIVER_OBJECT *DriverObject;
    struct _DEVICE_OBJECT *NextDevice;
    struct _DEVICE_OBJECT *AttachedDevice;
    struct _IRP *CurrentIrp;
    PIO_TIMER Timer;
    ULONG Flags;                                // See above:  DO_...
    ULONG Characteristics;                      // See ntioapi:  FILE_...
    PVPB Vpb;
    PVOID DeviceExtension;
    DEVICE_TYPE DeviceType;
    CCHAR StackSize;
    union {
        LIST_ENTRY ListEntry;
        WAIT_CONTEXT_BLOCK Wcb;
    } Queue;
    ULONG AlignmentRequirement;
    KDEVICE_QUEUE DeviceQueue;
    KDPC Dpc;
	
    ULONG ActiveThreadCount;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    KEVENT DeviceLock;
	
    USHORT SectorSize;
    USHORT Spare1;
	
    struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;
    PVOID  Reserved;
} DEVICE_OBJECT;
typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT; 

其中比较重要的字段:

DriverObject(驱动对象):指明该设备属于哪个驱动对象。

NextDevice(下一个设备对象):指向下一个设备对象,通过该字段可以遍历整个设备对象链。

AttachedDevice:指向下一个设备对象。这里指,如果有更高一层的驱动对象附加到这个驱动的时候,AttachDevice指向的就是那个更高一层的驱动。

DeviceExtension(设备扩展对象):指向设备的扩展对象,每个设备都会指定一个设备扩展对象,设备扩展对象记录的自己定义的一些结构体,也是该设备自己的特殊结构体。


NT式驱动的基本结构


驱动入口函数(DriverEntry)

和普通变成一样,驱动程序也有自己的入口函数,相当于普通程序的main()函数一样,驱动程序的入口函数名为DriverEntry()。

函数原型:

extern "C" NTSTATUS DriverEntry (
			IN PDRIVER_OBJECT pDriverObject,
			IN PUNICODE_STRING pRegistryPath	) 

请注意extern "C",再拿C++写驱动程序的时候,由于C++支持函数的重载,所以编译器编译后的函数名与C语言编译编译出来的函数名会有不同,所以这里用extern "C"关键字,使得C++编译出来的函数名与C语言一致,否则则会出现连接错误。

DriverEntry()函数主要负责驱动程序的初始化工作,它是由系统进程成调用的。

函数返回一个状态量 NTSTATUS,NTSTATUS被定义成32位无符号长整形,其中不同数据代表不同的返回状态。

pDriverObject参数指向由系统对象管理器自动创建的驱动对象

PUNICODE_STRING 参数指向设备服务的键名字符串

在驱动程序编写中,函数的参数往往会有“IN”,“OUT”或者“INOUT”修饰符,他们其实被宏定义为空串,没有实际意义,只是起到类似于注释的作用,IN代表传入参数,级使用者不能改变这个指针本身,OUT代表输出参数。

我们来看一下DriverEntry的具体实现

#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
			IN PDRIVER_OBJECT pDriverObject,
			IN PUNICODE_STRING pRegistryPath	) 
{
	NTSTATUS status;
	KdPrint(("Enter DriverEntry\n"));

	//注册其他驱动调用函数入口
	pDriverObject->DriverUnload = HelloDDKUnload;
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
	
	//创建驱动设备对象
	status = CreateDevice(pDriverObject);

	KdPrint(("DriverEntry end\n"));
	return status;
}


创建设备对象

在NT式驱动中,创建设备对象是有IOCreateDevice内核函数完成的

函数原型:

NTSTATUS IoCreateDevice( IN PDRIVER_OBJECT DriverObject,
						IN ULONG DeviceExtensionSize, 
						IN PUNICODE_STRING DeviceName OPTIONAL, 
						IN DEVICE_TYPE DeviceType, 
						IN ULONG DeviceCharacteristics,
						IN BOOLEAN Exclusive, 
						OUT PDEVICE_OBJECT *DeviceObject )

DriverObject:输入参数,该驱动程序所对应的唯一驱动对象。

DeviceExtensionSize:输入参数,指定设备扩展的大小。

DeviceName:输入参数,设备对象的名字。

DeviceCharacteristics:输入参数,设备对象的特征。例如如果要创建虚拟设备的话,参数可以为FILE_DEVICE_UNKNOWN。

Exclusive:输入参数,设置内核对象是否在内核模式下下使用,一般设置为TRUE。

DeviceObject:输出参数,I/O管理器负责创建这个设备对象。

返回值:此函数的条用状态。


我们可以写一个CreateDevice函数来看一下如何创建一个设备对象


#pragma INITCODE
NTSTATUS CreateDevice (
		IN PDRIVER_OBJECT	pDriverObject) 
{
	NTSTATUS status;
	PDEVICE_OBJECT pDevObj;
	PDEVICE_EXTENSION pDevExt;
	
	//创建设备名称
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
	
	//创建设备
	status = IoCreateDevice( pDriverObject,
						sizeof(DEVICE_EXTENSION),
						&(UNICODE_STRING)devName,
						FILE_DEVICE_UNKNOWN,
						0, TRUE,
						&pDevObj );
	if (!NT_SUCCESS(status))
		return status;

	pDevObj->Flags |= DO_BUFFERED_IO;
	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
	pDevExt->pDevice = pDevObj;
	pDevExt->ustrDeviceName = devName;
	//创建符号链接
	UNICODE_STRING symLinkName;
	RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
	pDevExt->ustrSymLinkName = symLinkName;
	status = IoCreateSymbolicLink( &symLinkName,&devName );
	if (!NT_SUCCESS(status)) 
	{
		IoDeleteDevice( pDevObj );
		return status;
	}
	
	return STATUS_SUCCESS;
}

其中有段代码是创建符号链接的代码,所谓符号链接,就是该设备的“别名”,因为一个设备的设备名称只能被内核模式下其他的驱动程序识别,而想要让用户模式下的程序识别这个设备,有两种方法,其中一种就是通过符号链接,来给该设备起一个别名。


DriverUnload例程

驱动程序结束时需要有一个函数来删除在DriverEntry中创建的设备对象,我们来写一个函数完成此功能

#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject) 
{
	PDEVICE_OBJECT	pNextObj;
	KdPrint(("Enter DriverUnload\n"));
	pNextObj = pDriverObject->DeviceObject;
	while (pNextObj != NULL) 
	{
		PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pNextObj->DeviceExtension;

		//删除符号链接
		UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
		IoDeleteSymbolicLink(&pLinkName);
		pNextObj = pNextObj->NextDevice;
		IoDeleteDevice( pDevExt->pDevice );
	}
}

这段代码中我们通过设备对象的NextDevice域完成整个设备链的遍历,将其全部删除。



WDM驱动的基本结构


对于WDM驱动来说,一般是基于分层的,也就是说,完成一个设备的操作,至少要有两个设备共同完成。其中一个是物理设备对象PDO,另一个是功能设备对象FDO。其关系是“附加”与“被附加”的关系。

当PC插入某个设备时,PDO会自动创建,准确的说,是由总线驱动创建的。但是PDO不能单独操纵设备,需要配合FDO一起使用。


WDM驱动的入口函数

和NT驱动一样,WDM驱动的入口函数也是DriverEntry,但是创建设备的责任不在DriverEntry中,而是在AddDevide例程中。同时,在WDM的DriverEntry中需要设置对IRP_MJ_PNP处理的派遣函数

我们来看一个WDM的DriverEntry函数

#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
								IN PUNICODE_STRING pRegistryPath)
{
	KdPrint(("Enter DriverEntry\n"));

	pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
	pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
	pDriverObject->MajorFunction[IRP_MJ_READ] = 
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
	pDriverObject->DriverUnload = HelloWDMUnload;

	KdPrint(("Leave DriverEntry\n"));
	return STATUS_SUCCESS;
}

这段代码与NT驱动不同的地方在于:

1.增加了AddDevice例程,WDM驱动加载后,PDO有操作系统自动创建,而FDO则由AddDice例程创建,并且附加在PDO之上。

2.加入了对IRP_MJ_PNP的派遣回到函数,RIP_MJ_PNP主要负责计算机中即插即用的处理。


WDM中的AddDevice例程

AddDevice例程是WDM驱动中所独有的。AddDevice例程的函数地址在驱动对象中的扩展对象里的AddDevice字段:

pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;

我们来看一个具体的AddDevice例程

#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject)
{ 
	PAGED_CODE();
	KdPrint(("Enter HelloWDMAddDevice\n"));

	NTSTATUS status;
	PDEVICE_OBJECT fdo;
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");
	status = IoCreateDevice(
		DriverObject,
		sizeof(DEVICE_EXTENSION),
		&(UNICODE_STRING)devName,
		FILE_DEVICE_UNKNOWN,
		0,
		FALSE,
		&fdo);
	if( !NT_SUCCESS(status))
		return status;
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
	pdx->fdo = fdo;
	pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
	UNICODE_STRING symLinkName;
	RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");

	pdx->ustrDeviceName = devName;
	pdx->ustrSymLinkName = symLinkName;
	status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);

	if( !NT_SUCCESS(status))
	{
		IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
		status = IoCreateSymbolicLink(&symLinkName,&devName);
		if( !NT_SUCCESS(status))
		{
			return status;
		}
	}

	fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
	fdo->Flags &= ~DO_DEVICE_INITIALIZING;

	KdPrint(("Leave HelloWDMAddDevice\n"));
	return STATUS_SUCCESS;
}

AddDevice例程函数有两个输入参数,一个是PDO,一个是FDO

AddDevice例程函数通过IOCreateDevice函数创建FDO

然后通过IoAttachDeviceToDeviceStack将FDO附加在PDO上

pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
接下来是创建设备对象的符号链接


DriverUnload例程

在NT驱动中,DriverUnload例程主要负责删除设备和符号链接。而在WDM驱动中,这部分操作被IRP_MN_REMOVE_DEVICE IRP的处理函数负责,而DriverUnlaod例程只是负责一些内存回收工作。

你可能感兴趣的:(数据结构,windows,object,struct,String,extension)