Windows提供了方法来通知用户层和内核层部件PNP事件发生,其中在用户层,有两种方式,分别是窗口和服务程序的,如下:
RegisterDeviceNotification
: 注册窗口的PNP事件,当事件到达的时候,就会接收到WM_DEVICECHANGE
.RegisterServiceCtrlHandlerEx
: 注册驱动的服务接口,接收PNP事件。此外,内核中也提供了IoRegisterPlugPlayNotification
函数来注册PNP事件的回调函数,本文重点来讨论一下内核中的PNP事件。
NTSTATUS IoRegisterPlugPlayNotification(
_In_ IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
_In_ ULONG EventCategoryFlags,
_In_opt_ PVOID EventCategoryData,
_In_ PDRIVER_OBJECT DriverObject,
_In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,
_In_opt_ PVOID Context,
_Out_ PVOID *NotificationEntry
);
typedef NTSTATUS
DRIVER_NOTIFICATION_CALLBACK_ROUTINE(
_In_ PVOID NotificationStructure,
_Inout_opt_ PVOID Context
);
EventCategory
这个特指PNP事件的类别:
EventCategoryDeviceInterfaceChange
: 设备实例到来和移除的消息。EventCategoryHardwareProfileChange
: 硬件Profile消息。EventCategoryTargetDeviceChange
: 设备改变消息,例如设备插入和设备的移除。EventCategoryFlags
: 这个标记针对EventCategoryDeviceInterfaceChange
这种事件有效,当标记PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES
, 这个函数就会枚举当前已经存在的设备接口。EventCategoryData
: 各种不同PNP事件对应的而外信息:
EventCategoryDeviceInterfaceChange
这个类别的话,这个程序将提供一个设备的GUID。EventCategoryHardwareProfileChange
: 必须为NULL。EventCategoryTargetDeviceChange
: 必须指定文件对象。NotificationEntry
: 返回值,用来反注册(IoUnregisterPlugPlayNotificationEx
)通知事件。NTSTATUS OnPnpNotify(PPLUGPLAY_NOTIFICATION_HEADER p, PREG_RECORD reg)
{
PAGED_CODE();
char *msg;
if (p->Event == GUID_DEVICE_INTERFACE_ARRIVAL)
msg = "arrival";
else if (p->Event == GUID_DEVICE_INTERFACE_REMOVAL)
msg = "removal";
else if (p->Event == GUID_TARGET_DEVICE_QUERY_REMOVE)
msg = "target query remove";
else if (p->Event == GUID_TARGET_DEVICE_REMOVE_CANCELLED)
msg = "target remove cancelled";
else if (p->Event == GUID_TARGET_DEVICE_REMOVE_COMPLETE)
msg = "target remove complete";
else
msg = "custom notification";
return STATUS_SUCCESS;
}
NTSTATUS Register(PDEVICE_EXTENSION pdx, PREGISTER_PARAMS p)
{
//...
return IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange,
PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
&p->guid,
pdx->DriverObject,
(PDRIVER_NOTIFICATION_CALLBACK_ROUTINE) OnPnpNotify,
reg,
®->InterfaceNotificationEntry);
}
在内核中,存在一个链表,记录着其他模块向系统注册过的PNP事件回调函数,这个链表如下:
LIST_ENTRY PnpNotifyListHead;
BOOLEAN
INIT_FUNCTION
NTAPI
IoInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
//...
InitializeListHead(&PnpNotifyListHead);
//...
}
其实这个队列中,保存的是PNP_NOTIFY_ENTRY
结构体信息,这个结构体记录着IoRegisterPlugPlayNotification
注册的具体结构信息,这个结构体如下:
typedef enum _IO_NOTIFICATION_EVENT_CATEGORY {
EventCategoryReserved,
EventCategoryHardwareProfileChange,
EventCategoryDeviceInterfaceChange,
EventCategoryTargetDeviceChange
} IO_NOTIFICATION_EVENT_CATEGORY;
typedef NTSTATUS
(NTAPI DRIVER_NOTIFICATION_CALLBACK_ROUTINE)(
_In_ PVOID NotificationStructure,
_Inout_opt_ PVOID Context);
typedef DRIVER_NOTIFICATION_CALLBACK_ROUTINE *PDRIVER_NOTIFICATION_CALLBACK_ROUTINE;
typedef struct _PNP_NOTIFY_ENTRY
{
LIST_ENTRY PnpNotifyList;
IO_NOTIFICATION_EVENT_CATEGORY EventCategory;
PVOID Context;
UNICODE_STRING Guid;
PFILE_OBJECT FileObject;
PDRIVER_OBJECT DriverObject;
PDRIVER_NOTIFICATION_CALLBACK_ROUTINE PnpNotificationProc;
} PNP_NOTIFY_ENTRY, *PPNP_NOTIFY_ENTRY;
所有的PNP_NOTIFY_ENTRY
都是通过PnpNotifyList
串联起来。
这个函数就是向PnpNotifyListHead
这个列表中注册一个PNP_NOTIFY_ENTRY
,当PNP事件到来的时候,就会遍历这个链表调用回调函数,这个函数的实现如下:
NTSTATUS
NTAPI
IoRegisterPlugPlayNotification(IN IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
IN ULONG EventCategoryFlags,
IN PVOID EventCategoryData OPTIONAL,
IN PDRIVER_OBJECT DriverObject,
IN PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,
IN PVOID Context,
OUT PVOID *NotificationEntry)
{
PPNP_NOTIFY_ENTRY Entry;
//...
if (EventCategory == EventCategoryDeviceInterfaceChange &&
EventCategoryFlags & PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES)
{
DEVICE_INTERFACE_CHANGE_NOTIFICATION NotificationInfos;
UNICODE_STRING SymbolicLinkU;
PWSTR SymbolicLink;
Status = IoGetDeviceInterfaces((LPGUID)EventCategoryData,
NULL, /* PhysicalDeviceObject OPTIONAL */
0, /* Flags */
&SymbolicLinkList);
if (NT_SUCCESS(Status))
{
/* Enumerate SymbolicLinkList */
NotificationInfos.Version = 1;
NotificationInfos.Size = sizeof(DEVICE_INTERFACE_CHANGE_NOTIFICATION);
RtlCopyMemory(&NotificationInfos.Event,
&GUID_DEVICE_INTERFACE_ARRIVAL,
sizeof(GUID));
RtlCopyMemory(&NotificationInfos.InterfaceClassGuid,
EventCategoryData,
sizeof(GUID));
NotificationInfos.SymbolicLinkName = &SymbolicLinkU;
for (SymbolicLink = SymbolicLinkList;
*SymbolicLink;
SymbolicLink += wcslen(SymbolicLink) + 1)
{
RtlInitUnicodeString(&SymbolicLinkU, SymbolicLink);
DPRINT("Calling callback routine for %S\n", SymbolicLink);
(*CallbackRoutine)(&NotificationInfos, Context); //已经存在的符号链接调用一下回调函数
}
ExFreePool(SymbolicLinkList);
}
}
Entry->PnpNotificationProc = CallbackRoutine;
Entry->EventCategory = EventCategory;
Entry->Context = Context;
Entry->DriverObject = DriverObject;
switch (EventCategory)
{
case EventCategoryDeviceInterfaceChange:
{
Status = RtlStringFromGUID(EventCategoryData, &Entry->Guid);
if (!NT_SUCCESS(Status))
{
ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY);
ObDereferenceObject(DriverObject);
return Status;
}
break;
}
case EventCategoryHardwareProfileChange:
{
break;
}
case EventCategoryTargetDeviceChange:
{
Entry->FileObject = (PFILE_OBJECT)EventCategoryData;
break;
}
default:
{
break;
}
}
KeAcquireGuardedMutex(&PnpNotifyListLock);
InsertHeadList(&PnpNotifyListHead,
&Entry->PnpNotifyList);
KeReleaseGuardedMutex(&PnpNotifyListLock);
*NotificationEntry = Entry;
return STATUS_SUCCESS;
}
其实这个函数流程很简单,主要三个:
EventCategoryFlags & PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES
判断是否枚举已经存在的接口信息,如果是,那么从注册表中枚举当前已经存在的接口并调用回调函数(*CallbackRoutine)(&NotificationInfos, Context)
.PPNP_NOTIFY_ENTRY
节点。InsertHeadList(&PnpNotifyListHead, &Entry->PnpNotifyList);
.当PPNP_NOTIFY_ENTRY
插入队列之后,就等待PNP事件到来并遍历链表调用了。
在内核中,PNP事件的通知是调用IopNotifyPlugPlayNotification
这个函数来通知的,这个函数调用的地方非常多,各种PNP事件都会导致这个函数调用,如下:
这里有一个函数IoSetDeviceInterfaceState
, 使能设备接口的时候,就会调用这个函数,如下:
NTSTATUS
NTAPI
IoSetDeviceInterfaceState(IN PUNICODE_STRING SymbolicLinkName,
IN BOOLEAN Enable)
{
//...
EventGuid = Enable ? &GUID_DEVICE_INTERFACE_ARRIVAL : &GUID_DEVICE_INTERFACE_REMOVAL;
IopNotifyPlugPlayNotification(
PhysicalDeviceObject,
EventCategoryDeviceInterfaceChange,
EventGuid,
&DeviceGuid,
(PVOID)SymbolicLinkName);
ObDereferenceObject(PhysicalDeviceObject);
return STATUS_SUCCESS;
}
这里就会有个GUID_DEVICE_INTERFACE_ARRIVAL
或者GUID_DEVICE_INTERFACE_REMOVAL
PNP消息通知。
其中IopNotifyPlugPlayNotification
通知函数的实现很简单,如下:
VOID
IopNotifyPlugPlayNotification(
IN PDEVICE_OBJECT DeviceObject,
IN IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
IN LPCGUID Event,
IN PVOID EventCategoryData1,
IN PVOID EventCategoryData2)
{
//...
ListEntry = PnpNotifyListHead.Flink;
while (ListEntry != &PnpNotifyListHead)
{
ChangeEntry = CONTAINING_RECORD(ListEntry, PNP_NOTIFY_ENTRY, PnpNotifyList);
CallCurrentEntry = FALSE;
//...
if (CallCurrentEntry)
{
KeReleaseGuardedMutex(&PnpNotifyListLock);
(ChangeEntry->PnpNotificationProc)(NotificationStructure,
ChangeEntry->Context);
KeAcquireGuardedMutex(&PnpNotifyListLock);
}
//...
}
}
总结过来就两点:
NotificationStructure
.PnpNotifyListHead
并调用合适的回调函数。当然注册了之外就可以移除,移除的实现如下(就是从PnpNotifyList
链表中摘除PPNP_NOTIFY_ENTRY
):
NTSTATUS
NTAPI
IoUnregisterPlugPlayNotification(IN PVOID NotificationEntry)
{
PPNP_NOTIFY_ENTRY Entry;
PAGED_CODE();
Entry = (PPNP_NOTIFY_ENTRY)NotificationEntry;
KeAcquireGuardedMutex(&PnpNotifyListLock);
RemoveEntryList(&Entry->PnpNotifyList);
KeReleaseGuardedMutex(&PnpNotifyListLock);
RtlFreeUnicodeString(&Entry->Guid);
ObDereferenceObject(Entry->DriverObject);
ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY);
return STATUS_SUCCESS;
}