Windows PnP设备驱动删除设备的处理流程
转自:Windows PnP设备驱动删除设备的处理流程 - 知乎 (zhihu.com)参考文档:Understanding When Remove IRPs Are Issued - Windows drivers / 让设备实现即插即用--福优学苑@音视频+流媒体 (hellotongtong.com)Windows对于PnP(即插即用)设备,有一个专门的IRP Major Code定义:IRP_MJ_PNP,任何PnP设备驱动以及工作在PnP设备协议栈之上的驱动(如文件系统驱动)和过滤驱动都需要处理IRP_MJ_PNP请求,尤其是删除的过程,如果驱动没有针对这类请求进行处理,可能会导致系统挂起或者删除设备失败。一个常见的用户层面表现就是无法安全弹出U盘。本文主要翻译自前面的链接,不保证内容一定正确(未进行完整验证)。PnP设备有两种删除流程,一种是正常删除,也就是用户层面发起的“安全弹出”请求;另一种是用户直接拔出设备,这种是非正常删除。对于正常安全删除的流程:Windows会先发起一个IRP_MN_QUERY_REMOVE_DEVICE请求,去查询设备是否可以正常删除,如果此时设备忙,则可以通过Irp->IoStatus.Status返回一个STATUS_UNSUCCESSFUL,同时调用IoCompleteRequest,并且不传播Irp给下层驱动。这样在用户层面上,就会提示设备忙,无法删除。如果驱动认为设备时可以删除的,那么收到此请求以后,就应该禁止新的其它请求进入,同时设置Irp->IoStatus.Status为STATUS_SUCCESS,并调用IoSkipCurrentIrpStackLocation和IoCallDriver传播这个Irp到更底层的设备。通常来说,如果这个请求返回成功,那么驱动此时就应该释放一些资源,当然驱动也可以选择不释放,而是仅仅阻止更多的请求进入,具体驱动选择哪种行为,由驱动自己决定,但通常情况下,对设备加锁是必需的,也要阻止任何“创建”类型的请求进入驱动。当IRP_MN_QUERY_REMOVE_DEVICE返回成功,并且整个设备栈上的所有驱动都返回成功时,Windows的PnP管理器会发送IRP_MN_REMOVE_DEVICE请求,完成对设备彻底删除,当收到IRP_MN_REMOVE_DEVICE请求时,驱动需要释放全部资源,关闭设备,停止还在执行中的其它Irp请求。同时,驱动也要传播这个请求到下层驱动。Windows文档中要求:IRP_MN_REMOVE_DEVICE请求是必须成功的,不能失败,否则可能会造成设备状态信息不一致。IRP_MN_REMOVE_DEVICE请求有可能是驱动被卸载前的最后一个请求,所以必须要保证所有资源被正确释放。如果IRP_MN_QUERY_REMOVE_DEVICE返回失败,那么Windows会重新发送一个IRP_MN_CANCEL_REMOVE_DEVICE请求通知驱动以及整个设备栈取消之前的请求。当驱动收到IRP_MN_CANCEL_REMOVE_DEVICE时,需要解开设备锁定的状态,并允许“创建”类型的请求进入驱动。类似地,IRP_MN_CANCEL_REMOVE_DEVICE也要被传播到底层驱动,通知底层设备驱动更新状态。还有一种特例是,虽然IRP_MN_QUERY_REMOVE_DEVICE返回成功,但上层代码改变主意,不希望删除设备,那么也会发送一个IRP_MN_CANCEL_REMOVE_DEVICE请求,对于这个请求的处理跟前面描述的是一致的:更新状态,传播Irp到底层。所以大致的流程图如下IRP_MN_QUERY_REMOVE_DEVICE(查询是否可以删除)
|
+-----(失败)---> IRP_MN_CANCEL_REMOVE_DEVICE
|
+-----(用户层面取消请求)---> IRP_MN_CANCEL_REMOVE_DEVICE
|
(成功)
v
IRP_MN_REMOVE_DEVICE (可能是最后一个IRP)对于非正常拔出设备的删除流程:Windows PnP管理器会发送一个IRP_MN_SURPRISE_REMOVAL请求给驱动,通知驱动这个设备已经被强制删除了。这个时候驱动需要做的事情如下:1. 检查设备是否还存在,如果还存在,需要停止并删除设备(笔者注:这段状态很奇怪,但文档里是这么写的)2. 释放全部资源,阻止新的IO请求。3. 除了IRP_MJ_CLEANUP,IRP_MJ_CLOSE,IRP_MJ_POWER,IRP_MJ_PNP之外,其它IRP都需要被阻止。4. 如果过滤驱动不能处理这个请求,向底层传播。5. 设置Irp->IoStatus.Status为STATUS_SUCCESS,必须成功,不能失败。6. 调用IoSkipCurrentIrpStackLocation和IoCallDriver传播IRP7. 通过IoCallDriver获得返回值并返回IRP dispatch函数8. 不要调用任何Complete函数当IRP_MN_SURPRISE_REMOVAL请求返回以后,Windows会继续发送一个IRP_MN_REMOVE_DEVICE完成设备删除,流程与正常安全移除设备的方式一致。=============================================================================附注:一、即插即用IRP表二、启动与停止代码#pragma PAGEDCODE
NTSTATUS HandleStartDevice(PDEVICE_EXTENSION pdx, PIRP Irp)
{
PAGED_CODE();
KdPrint(("Enter HandleStartDevice\n"));
//转发IRP并等待返回
NTSTATUS status = ForwardAndWait(pdx,Irp);
if (!NT_SUCCESS(status))
{
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
//得到当前堆栈
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
//从当前堆栈得到资源信息
PCM_PARTIAL_RESOURCE_LIST raw;
if (stack->Parameters.StartDevice.AllocatedResources)
raw = &stack->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList;
else
raw = NULL;
KdPrint(("Show raw resouces\n"));
ShowResources(raw);
//从当前堆栈得到翻译信息
PCM_PARTIAL_RESOURCE_LIST translated;
if (stack->Parameters.StartDevice.AllocatedResourcesTranslated)
translated = &stack->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList;
else
translated = NULL;
KdPrint(("Show translated resouces\n"));
ShowResources(translated);
//完成IRP
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
KdPrint(("Leave HandleStartDevice\n"));
return status;
}
#pragma PAGEDCODE
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp)
{
PAGED_CODE();
KdPrint(("Enter HelloWDMPnp\n"));
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
static NTSTATUS (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) =
{
HandleStartDevice,// IRP_MN_START_DEVICE
DefaultPnpHandler,// IRP_MN_QUERY_REMOVE_DEVICE
HandleRemoveDevice,// IRP_MN_REMOVE_DEVICE
DefaultPnpHandler,// IRP_MN_CANCEL_REMOVE_DEVICE
DefaultPnpHandler,// IRP_MN_STOP_DEVICE
DefaultPnpHandler,// IRP_MN_QUERY_STOP_DEVICE
DefaultPnpHandler,// IRP_MN_CANCEL_STOP_DEVICE
DefaultPnpHandler,// IRP_MN_QUERY_DEVICE_RELATIONS
DefaultPnpHandler,// IRP_MN_QUERY_INTERFACE
DefaultPnpHandler,// IRP_MN_QUERY_CAPABILITIES
DefaultPnpHandler,// IRP_MN_QUERY_RESOURCES
DefaultPnpHandler,// IRP_MN_QUERY_RESOURCE_REQUIREMENTS
DefaultPnpHandler,// IRP_MN_QUERY_DEVICE_TEXT
DefaultPnpHandler,// IRP_MN_FILTER_RESOURCE_REQUIREMENTS
DefaultPnpHandler,//
DefaultPnpHandler,// IRP_MN_READ_CONFIG
DefaultPnpHandler,// IRP_MN_WRITE_CONFIG
DefaultPnpHandler,// IRP_MN_EJECT
DefaultPnpHandler,// IRP_MN_SET_LOCK
DefaultPnpHandler,// IRP_MN_QUERY_ID
DefaultPnpHandler,// IRP_MN_QUERY_PNP_DEVICE_STATE
DefaultPnpHandler,// IRP_MN_QUERY_BUS_INFORMATION
DefaultPnpHandler,// IRP_MN_DEVICE_USAGE_NOTIFICATION
DefaultPnpHandler,// IRP_MN_SURPRISE_REMOVAL
};
ULONG fcn = stack->MinorFunction;
if (fcn >= arraysize(fcntab))
{// 未知的子功能代码
status = DefaultPnpHandler(pdx, Irp); // some function we don't know about
return status;
}
#if DBG
static char* fcnname[] =
{
"IRP_MN_START_DEVICE",
"IRP_MN_QUERY_REMOVE_DEVICE",
"IRP_MN_REMOVE_DEVICE",
"IRP_MN_CANCEL_REMOVE_DEVICE",
"IRP_MN_STOP_DEVICE",
"IRP_MN_QUERY_STOP_DEVICE",
"IRP_MN_CANCEL_STOP_DEVICE",
"IRP_MN_QUERY_DEVICE_RELATIONS",
"IRP_MN_QUERY_INTERFACE",
"IRP_MN_QUERY_CAPABILITIES",
"IRP_MN_QUERY_RESOURCES",
"IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
"IRP_MN_QUERY_DEVICE_TEXT",
"IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
"",
"IRP_MN_READ_CONFIG",
"IRP_MN_WRITE_CONFIG",
"IRP_MN_EJECT",
"IRP_MN_SET_LOCK",
"IRP_MN_QUERY_ID",
"IRP_MN_QUERY_PNP_DEVICE_STATE",
"IRP_MN_QUERY_BUS_INFORMATION",
"IRP_MN_DEVICE_USAGE_NOTIFICATION",
"IRP_MN_SURPRISE_REMOVAL",
};
KdPrint(("PNP Request (%s)\n", fcnname[fcn]));
#endif // DBG
status = (*fcntab[fcn])(pdx, Irp);
KdPrint(("Leave HelloWDMPnp\n"));
return status;
}三、设备资源#pragma PAGEDCODE
VOID ShowResources(IN PCM_PARTIAL_RESOURCE_LIST list)
{
//枚举资源
PCM_PARTIAL_RESOURCE_DESCRIPTOR resource = list->PartialDescriptors;
ULONG nres = list->Count;
ULONG i;
for (i = 0; i < nres; ++i, ++resource)
{// for each resource
ULONG type = resource->Type;
static char* name[] = {
"CmResourceTypeNull",
"CmResourceTypePort",
"CmResourceTypeInterrupt",
"CmResourceTypeMemory",
"CmResourceTypeDma",
"CmResourceTypeDeviceSpecific",
"CmResourceTypeBusNumber",
"CmResourceTypeDevicePrivate",
"CmResourceTypeAssignedResource",
"CmResourceTypeSubAllocateFrom",
};
KdPrint((" type %s", type < arraysize(name) ? name[type] : "unknown"));
switch (type)
{// select on resource type
case CmResourceTypePort:
case CmResourceTypeMemory:
KdPrint((" start %8X%8.8lX length %X\n",
resource->u.Port.Start.HighPart, resource->u.Port.Start.LowPart,
resource->u.Port.Length));
break;
case CmResourceTypeInterrupt:
KdPrint((" level %X, vector %X, affinity %X\n",
resource->u.Interrupt.Level, resource->u.Interrupt.Vector,
resource->u.Interrupt.Affinity));
break;
case CmResourceTypeDma:
KdPrint((" channel %d, port %X\n",
resource->u.Dma.Channel, resource->u.Dma.Port));
}// select on resource type
}// for each resource
}// ShowResources