驱动程序中重要的数据结构
驱动对象: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 )
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 ); } }
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; }
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例程函数通过IOCreateDevice函数创建FDO
然后通过IoAttachDeviceToDeviceStack将FDO附加在PDO上
pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);接下来是创建设备对象的符号链接
DriverUnload例程
在NT驱动中,DriverUnload例程主要负责删除设备和符号链接。而在WDM驱动中,这部分操作被IRP_MN_REMOVE_DEVICE IRP的处理函数负责,而DriverUnlaod例程只是负责一些内存回收工作。