转载自;http://mzf2008.blog.163.com/blog/static/35599786201011973648864/
WDM式驱动程序的基本结构
对于WDM驱动程序来说,一般都是基于分层的。也就是说,完成一个设备的操作,至少要由两个驱动设备共同完成。
1. 物理设备对象与功能设备对象
在WDM模型中,完成一个设备的操作,至少要有两个设备对象共同完成。其中,一个是物理设备对象(Physical Device Object,即PDO),另一个是功能设备对象(Function Device Object,即FDO)。其关系是“附加”与“被附加”的关系。
当PC插入某个设备时,PDO是由总线驱动创建的。PDO不能单独操作设备,需要配合FDO一起使用。系统会提示检测到新设备,要求安装设备驱动程序。需要安装的驱动程序指的就是WDM程序,此程序负责创建FDO,并且附加到PDO之上。
当一个FDO附加到PDO上的时候,PDO设备对象的子域AttachedDevice会记录FDO的位置。PDO被称作是底层驱动或者下层驱动,而FDO被称为是高层驱动或者上层驱动。这里的上层指的是接近发出I/O请求的地方,而下层指的是靠近物理设备的地方。
这是最简单的一种情况,事实要比这个复杂一些。在FDO和PDO之间还会存在过滤驱动。在FDO上面的过滤驱动被称作上层过滤驱动。在FDO下层的驱动,被称作下层过滤驱动。另外,每个设备对象中,有个StackSize子域,表明操作这个设备对象需要几层才能到达最下层的物理设备。
2.WDM驱动的入口函数
和NT驱动一样,WDM驱动的入口函数也是DriverEntry,但是初始化作用被分散到其他例程中。例如,创建设备对象的责任就被放在了AddDevice例程中。同时,在DriverEntry中,需要设置对IRP_MJ_PNP处理的派遣函数。
如下面的代码:
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
KdPrint(("Enter DriverEntry\n"));
//设置AddDevoce函数
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
//设置各个IRP的派遣函数
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;
}
从上述的代码中可以看出,WDM驱动的DriverEntry和NT式驱动的DriverEntry有以下几点不同:
1. 增加了对AddDevice函数的设置。因为NT驱动是主动加载设备的,也就是驱动一旦加载就创建设备。而WDM驱动是被动加载设备的。操作系统必须加载PDO以后,调用驱动程序的AddDevice例程,AddDevice例程负责创建FDO,并且附加到PDO之上。
2. 创建设备对象已经不在这个函数中了,而在AddDevice例程中创建。
3. 必须加入IRP_MJ_PNP的派遣回调函数。IPR_MJ_PNP主要负责计算机中即插即用的处理,在WDM驱动中加入了很多即插即用的处理。
3.WMD驱动的AddDevice例程
AddDevice例程用来创建驱动的设备对象。在DriverEntry中,需要设置AddDevice例程的函数地址。设置的方式如下:
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
实例代码如下:
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;
//将FDO附加到PDO上,并且返回PDO对象
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函数有两个参数,一个是驱动对象DriverObject,另一个是设备对象PhysicalDeviceObject。驱动对象是I/O管理器创建的驱动对象。设备对象是由底层总线驱动创建的PDO设备对象。传进该参数的目的就是让我们创建的FDO附加到PDO之上。
在AddDevice中,可以分为以下几个步骤:
1. 在AddDevice中通过IoCreateDevice等函数,创建设备对象,该设备对象就是FDO。
2. 创建完FDO后,需要将FDO的地址保存下来,保存的位置就是在设备扩展中。
3. 驱动程序将创建的FDO附加到PDO上,依靠IoAttachDeviceToDeviceStack函数实现。附加以后,该函数返回附加设备的下层驱动。如果中间没有过滤驱动的话,返回的就是PDO,否则返回过滤驱动。
我们可以把该返回地址记录在设备扩展结构中,以此可以访问FDO的下层设备。
4. 设置FDO设备的Flags子域。DO_BUFFERED_IO是定义设备为“缓冲内存设备”,另外~DO_DEVICE_INITIALIZING,是将Flags上的DO_DEVICE_INITIALIZING位清零。保证设备初始化完毕,这一步是必须的。
4.DriverUnload例程
在NT驱动中,DriverUnload例程主要负责做删除设备和取消符号链接。而在WDM驱动中,这部分操作被IRP_MN_REMOVE_DEVICE 的IRP处理函数所负责。而DriverUnload例程显得变得相对简单。如果在DriverEntry中有申请内存的操作,可以在DriverUnload例程中回收这些内存。
5. 对IRP_MN_REMOVE_DEVICE的处理
IRP_MN_REMOVE_DEVICE这个IRP是当设备需要被卸载的时候,由即插即用管理器创建,并发送到驱动程序中。IRP一般有两个号码指定该IRP的具体意义。一个是主版本号(Major IRP),另一个是辅IRP号(Minor IRP).
当设备被卸载的时候,会先后发送多个IRP_MJ_PNP。这些IRP的辅IRP号会有所不同。其中之一就是IRP_MN_REMOVE_DEVICE.
在WDM驱动程序中,对设备的卸载一般都是在IRP_MN_REMOVE_DEVICE的处理函数中进行卸载。
例如:
NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)
{
PAGED_CODE();
KdPrint(("Enter HandleRemoveDevice\n"));
//设置IRP的完成状态
Irp->IoStatus.Status = STATUS_SUCCESS;
//将IRP请求向底层驱动转发
NTSTATUS status = DefaultPnpHandler(pdx, Irp);
//删除符号链接
IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);
//调用IoDetachDevice()把fdo从设备栈中脱开:
if (pdx->NextStackDevice)
IoDetachDevice(pdx->NextStackDevice);
//删除fdo:
IoDeleteDevice(pdx->fdo);
KdPrint(("Leave HandleRemoveDevice\n"));
return status;
}
在处理IRP_MN_REMOVE_DEVICE的函数中,它的功能类似于NT驱动中的DriverUnload函数。除了删除设备,取消符号链接外,此函数中还需要将FDO从PDO上的堆栈中摘除下来。使用IoDetachDevice:
VOID
IoDetachDevice(
IN OUT PDEVICE_OBJECT TargetDevice
);
TargetDevice: 此时,FDO从设备链上被删除,但是PDO还是存在的。PDO的删除不是由程序员负责,而是由操作系统负责。
6. <span courier="" new";="" mso-hansi-font-family:"courier="" new";mso-bidi-font-family:"courier="" mso-bidi-font-style:italic;"="" style="font-size: 12pt; font-family: 宋体;">驱动程序的层次结构