VS2013 WDK8.1驱动开发5(WDM驱动基本结构)
本系列博客为学习《Windows驱动开发技术详解》一书的学习笔记。
前言
在VS2013 WDK8.1驱动开发2(最简单的WDM驱动)一文中我们完成了一个最简单的WDM驱动程序,今天我们详细的看看它的结构。
物理设备对象与功能设备对象
在WDM模型中,完成一个设备的操作,需要两个设备对象来共同完成。其中一个是物理设备对象(Physical Device Object,简称PDO),另一个是功能设备对象(Function Device Object,简称FDO)。当PC插入某个设备时,总线驱动会创建该设备的PDO,该设备对应的WDM驱动程序会创建相应的FDO,FDO会附加在PDO上。形成如下图的结构,每个设备对象都会有一个AttachedDevice属性,用于记录上层设备对象。
设备对象堆栈
实际情况要比上面的结构更复杂,在FDO的上面可能会存在多个上层过滤设备对象(上层过滤驱动创建),在FDO的下面可能会存在多个下层过滤设备对象(下层过滤驱动创建),每个设备对象都会有一个StackSize属性,用于记录该设备对象所在的层次。如下图:
WDM驱动的AddDevice例程
VS2013 WDK8.1驱动开发2(最简单的WDM驱动)一文中的AddDevice例程就是用于创建FDO,但是如果我们重复安装两次该驱动程序,那么会显示第二次我们安装的驱动程序有问题,如下图:
这是因为安装两次驱动程序会导致AddDevice例程会被调用两次,这样我们使用相同的设备对象名称去创建两个设备就会失败。现在我们改写AddDevice例程如下:
/// @brief 添加新设备
/// @param[in] pDriverObject 从I/O管理器传进来的驱动对象
/// @param[in] pPhysicalDeviceObject 从I/O管理器传进来的物理设备对象
/// @return 添加新设备状态
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT pPhysicalDeviceObject)
{
KdPrint(("Enter HelloWDMAddDevice\n"));
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT pDeviceObject = NULL; // 创建的设备对象
PDEVICE_EXTENSION pDeviceExt = NULL; // 设备扩展对象
// 创建设备
status = IoCreateDevice(
pDriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDeviceObject);
if (!NT_SUCCESS(status))
{
KdPrint(("Create Decive Fail\n"));
return status;
}
pDeviceExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
pDeviceExt->PDeviceObject = pDeviceObject;
// 讲设备对象挂接在设备堆栈上
pDeviceExt->PNextStackDevice = IoAttachDeviceToDeviceStack(pDeviceObject, pPhysicalDeviceObject);
pDeviceObject->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
KdPrint(("Leave HelloWDMAddDevice\n"));
return status;
}
- 在上面的代码中,我们没有指定设备名和符号链接,这样的话I/0管理器多次调用AddDevice例程是没有问题的,其实我们也可以分别命名不同的设备对象名称。
- 因为没有实际的硬件插入PC中,所以PNP管理器传给我们的PDO实际上并不对应一个真实设备,可以说它是一个虚拟设备。
- IoAttachDeviceToDeviceStack函数负责将我们创建的FDO附加在PDO上,该函数返回一个下层设备对象,下层设备对象可能是下层过滤设备对象也可能就是PDO,同时我们将下层设备对象记录在设备扩展中。
- 最后我们需要设置FDO的Flags属性,~DO_DEVICE_INITIALIZING是将Flag上的DO_DEVICE_INITIALIZING位清零,表示我们以及完成了设备的初始化工作。
修改后的驱动加载两次后,可以使用DeviceTree工具来观察PDO或者我们创建的FDO,如下:
对IRP_MN_REMOVE_DEVICE IRP的处理
IRP_MN_REMOVE_DEVICE这个IRP是当设备需要被卸载的时候,由即插即用管理器创建,并发送到驱动程序中。处理该IRP的例程如下:
/// @brief PNP移除设备处理函数
/// @param[in] pDeviceExt 设备扩展对象
/// @param[in] pIrp 请求包
/// @return 状态
NTSTATUS PnpRemoveDevice(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp)
{
KdPrint(("Enter HandleRemoveDevice\n"));
pIrp->IoStatus.Status = STATUS_SUCCESS;
NTSTATUS status = PnpDefaultHandler(pDeviceExt, pIrp);
//调用IoDetachDevice()把设备对象从设备栈中脱开:
if (pDeviceExt->PNextStackDevice != NULL)
IoDetachDevice(pDeviceExt->PNextStackDevice);
//删除设备对象:
IoDeleteDevice(pDeviceExt->PDeviceObject);
KdPrint(("Leave HandleRemoveDevice\n"));
return status;
}
/// @brief 对PNP IRP进行默认处理
/// @param[in] pDeviceExt 设备对象扩展
/// @param[in] pIrp I/O请求包
/// @return 状态
NTSTATUS PnpDefaultHandler(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp)
{
KdPrint(("Enter DefaultPnpHandler\n"));
// 略过当前堆栈
IoSkipCurrentIrpStackLocation(pIrp);
KdPrint(("Leave DefaultPnpHandler\n"));
// 用下层堆栈的驱动设备处理此IRP
return IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);
}
- 先让下层设备对象处理卸载动作。
- 将我们的设备对象从设备堆栈上脱开,这个过程和附加刚好相反。
- 之后删除我们创建的设备对象。
驱动程序的复杂层次结构
我们知道USB设备链接在USB HUB上,USB HUB链接在USB 控制器上,USB 控制器链接在PCI总线上,那么USB设备驱动是一种什么样的层次呢?如下图:
- PCI总线FDO枚举USB控制器设备创建PDO。
- USB控制器驱动创建FDO。
- USB控制器FDO枚举USB HUB设备创建USB HUB PDO。
- USB HUB驱动创建USB HUB FDO。
- USB HUB FDO枚举USB设备创建USB设备PDO。
- USB设备驱动创建USB设备FDO。
上面的每一个FDO的上下都可能存在过滤设备对象。
后话
本文完整工程和代码托管在GitHub上猛戳我。
其他章节链接
VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)
VS2013 WDK8.1驱动开发5(WDM驱动基本结构)
还没有人评论...