CSDN上转悠了一圈发现关于PNP管理的文章不多,那就由我献个丑,记录自己对PNP管理器的看法。
pnp管理器被描述为向内核和应用程序提供关于设备拔插的通知,凭感觉,pnp管理器应该是个线程函数等待设备通知。搜索ReactOS发现有这么个函数符合这个功能:
static DWORD WINAPI PnpEventThread(LPVOID lpParameter) { PPLUGPLAY_EVENT_BLOCK PnpEvent; ... PnpEvent = HeapAlloc(GetProcessHeap(), 0, PnpEventSize); ... for (;;) { DPRINT("Calling NtGetPlugPlayEvent()\n"); /* Wait for the next pnp event */ //这是个等待操作,等待PnpEvent有信号 Status = NtGetPlugPlayEvent(0, 0, PnpEvent, PnpEventSize); .... NtPlugPlayControl(PlugPlayControlUserResponse, NULL, 0); ... } HeapFree(GetProcessHeap(), 0, PnpEvent); return ERROR_SUCCESS; } /* V操作 等待IopPnpNotifyEvent有信号 然后从IopPnpEventQueueHead队列中取pnp事件 */ NTSTATUS STDCALL NtGetPlugPlayEvent(IN ULONG Reserved1, IN ULONG Reserved2, OUT PPLUGPLAY_EVENT_BLOCK Buffer, IN ULONG BufferSize) { PPNP_EVENT_ENTRY Entry; NTSTATUS Status; ... Status = KeWaitForSingleObject(&IopPnpNotifyEvent, UserRequest, KernelMode, FALSE, NULL); /* Get entry from the tail of the queue */ Entry = CONTAINING_RECORD(IopPnpEventQueueHead.Blink, PNP_EVENT_ENTRY, ListEntry); memcpy(Buffer, &Entry->Event, Entry->Event.TotalSize); return STATUS_SUCCESS; } NTSTATUS STDCALL NtPlugPlayControl(IN PLUGPLAY_CONTROL_CLASS PlugPlayControlClass, IN OUT PVOID Buffer, IN ULONG BufferLength) { ... switch (PlugPlayControlClass) { case PlugPlayControlUserResponse: if (Buffer || BufferLength != 0) return STATUS_INVALID_PARAMETER; return IopRemovePlugPlayEvent(); case PlugPlayControlProperty: if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_PROPERTY_DATA)) return STATUS_INVALID_PARAMETER; return IopGetDeviceProperty((PPLUGPLAY_CONTROL_PROPERTY_DATA)Buffer); case PlugPlayControlGetRelatedDevice: if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_RELATED_DEVICE_DATA)) return STATUS_INVALID_PARAMETER; return IopGetRelatedDevice((PPLUGPLAY_CONTROL_RELATED_DEVICE_DATA)Buffer); case PlugPlayControlDeviceStatus: if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_STATUS_DATA)) return STATUS_INVALID_PARAMETER; return IopDeviceStatus((PPLUGPLAY_CONTROL_STATUS_DATA)Buffer); case PlugPlayControlGetDeviceDepth: if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_DEPTH_DATA)) return STATUS_INVALID_PARAMETER; return IopGetDeviceDepth((PPLUGPLAY_CONTROL_DEPTH_DATA)Buffer); case PlugPlayControlResetDevice: if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_RESET_DEVICE_DATA)) return STATUS_INVALID_PARAMETER; return IopResetDevice((PPLUGPLAY_CONTROL_RESET_DEVICE_DATA)Buffer); default: return STATUS_NOT_IMPLEMENTED; } return STATUS_NOT_IMPLEMENTED; }段代码有点TCP服务器accept事件通知模型:等待事件发生,然后分类处理事件。NtGetPlugPlayEvent函数中用到了两个重要的变量:IopPnpNotifyEvent和IopPnpEventQueueHead。内核将新的pnp事件插入IopPnpEventQueueHead队列,然后唤醒应用层线程,IopPnpNotifyEvent就是内核和用户态PV操作的信号量。
这两个变量在系统启动时被初始化:
BOOLEAN INIT_FUNCTION NTAPI IoInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock) { KeInitializeGuardedMutex(&PnpNotifyListLock); ... InitializeListHead(&PnpNotifyListHead); ... PnpInit(); ... } VOID INIT_FUNCTION PnpInit(VOID) { /* Initialize PnP-Event notification support */ Status = IopInitPlugPlayEvents(); //这个函数够重要的 调用失败就直接BUGCHECK了 if (!NT_SUCCESS(Status)) { CPRINT("IopInitPlugPlayEvents() failed\n"); KEBUGCHECKEX(PHASE1_INITIALIZATION_FAILED, Status, 0, 0, 0); } } /* GLOBALS *******************************************************************/ static LIST_ENTRY IopPnpEventQueueHead; static KEVENT IopPnpNotifyEvent; /* FUNCTIONS *****************************************************************/ //上面提到的重要的函数的实现 NTSTATUS INIT_FUNCTION IopInitPlugPlayEvents(VOID) { InitializeListHead(&IopPnpEventQueueHead); KeInitializeEvent(&IopPnpNotifyEvent, SynchronizationEvent, FALSE); return STATUS_SUCCESS; }到这,PNP管理器这个服务器模型的骨架部分已经清晰了,后面循着骨架找齐完整的(相对完整的)结构。
总有些项目希望得到这样的功能:当插入设备的时候获得通知。界面开发者可能会这么做:添加一个WM_DEVICECHANGE事件的回调函数
BEGIN_MESSAGE_MAP(CDeviceMonitorDlg, CDialog) //}}AFX_MSG_MAP ON_WM_DEVICECHANGE() END_MESSAGE_MAP()这个 WM_DEVICECHANGE消息可能由WIN发出,也可能由某些后台程序监测到设备拔插后发出。为了监测某个设备拔插事件,就得注册回调函数,就像为了观察北极气候就建立一个北极观测站一样。总线驱动程序如果想捕获到相关设备插入拔出事件,可以在其IRP_MN_START_DEVICE结束处用IoRegisterPlugPlayNotification注册一回调函数。这个设备拔插事件回调函数注册/注销接口如下:
IoRegisterPlugPlayNotification/IoUnregisterPlugPlayNotificatio STDCALL 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) { /* windows内核中注册回调事件都有类似的模式:创建一个回调事件项, 然后把各种参数填入,最后把这个新建项挂入队列 */ PPNP_NOTIFY_ENTRY Entry; ... Entry = ExAllocatePoolWithTag( NonPagedPool, sizeof(PNP_NOTIFY_ENTRY), TAG_PNP_NOTIFY); ... Entry->PnpNotificationProc = CallbackRoutine; Entry->EventCategory = EventCategory; Entry->Context = Context; ... KeAcquireGuardedMutex(&PnpNotifyListLock); InsertHeadList(&PnpNotifyListHead, &Entry->PnpNotifyList); KeReleaseGuardedMutex(&PnpNotifyListLock); return STATUS_SUCCESS; }
其中的通知队列和通知互斥锁:PnpNotifyListHead/PnpNotifyListLock在前面IoInitSystem时已经被初始化。接着看下这个监测站怎么工作:
前面往回调事件队列中插入了回调函数,相应的,内核中有个遍历队列并调用回调函数的接口:
VOID IopNotifyPlugPlayNotification( IN PDEVICE_OBJECT DeviceObject, IN IO_NOTIFICATION_EVENT_CATEGORY EventCategory, IN LPCGUID Event, IN PVOID EventCategoryData1, IN PVOID EventCategoryData2) { PPNP_NOTIFY_ENTRY ChangeEntry; PLIST_ENTRY ListEntry; PVOID NotificationStructure; ... ListEntry = PnpNotifyListHead.Flink; while (ListEntry != &PnpNotifyListHead) { ChangeEntry = CONTAINING_RECORD(ListEntry, PNP_NOTIFY_ENTRY, PnpNotifyList); CallCurrentEntry = FALSE; switch (EventCategory) { case EventCategoryDeviceInterfaceChange: { if (ChangeEntry->EventCategory == EventCategory && RtlCompareUnicodeString(&ChangeEntry->Guid, (PUNICODE_STRING)EventCategoryData1, FALSE) == 0) { CallCurrentEntry = TRUE; } break; } case EventCategoryHardwareProfileChange: { CallCurrentEntry = TRUE; break; } case EventCategoryTargetDeviceChange: { if (ChangeEntry->FileObject == (PFILE_OBJECT)EventCategoryData1) CallCurrentEntry = TRUE; } default: { break; } } /* Move to the next element now, as callback may unregister itself */ ListEntry = ListEntry->Flink; //调用注入的回调函数 if (CallCurrentEntry) { /* Call entry into new allocated memory */ KeReleaseGuardedMutex(&PnpNotifyListLock); (ChangeEntry->PnpNotificationProc)( NotificationStructure, ChangeEntry->Context); KeAcquireGuardedMutex(&PnpNotifyListLock); } } }看下ReactOS中谁会调用这个函数:
---- IopNotifyPlugPlayNotification Matches (6 in 3 files) ---- Deviface.c (ntoskrnl\io\iomgr): IopNotifyPlugPlayNotification( Io.h (ntoskrnl\include\internal):IopNotifyPlugPlayNotification( Pnpnotify.c (ntoskrnl\io\pnpmgr):IopNotifyPlugPlayNotification( Pnpnotify.c (ntoskrnl\io\pnpmgr): DPRINT1("IopNotifyPlugPlayNotification(): unknown EventCategory 0x%x UNIMPLEMENTED\n", EventCategory); Pnpnotify.c (ntoskrnl\io\pnpmgr): DPRINT1("IopNotifyPlugPlayNotification(): unknown EventCategory 0x%x UNIMPLEMENTED\n", EventCategory);
---- IoSetDeviceInterfaceState Matches (12 in 6 files) ---- Deviface.c (ntoskrnl\io\iomgr): * @name IoSetDeviceInterfaceState Deviface.c (ntoskrnl\io\iomgr):IoSetDeviceInterfaceState(IN PUNICODE_STRING SymbolicLinkName, Deviface.c (ntoskrnl\io\iomgr): DPRINT("IoSetDeviceInterfaceState('%wZ', %d)\n", SymbolicLinkName, Enable); Deviface.c (ntoskrnl\io\iomgr): DPRINT("IoSetDeviceInterfaceState() returning STATUS_INVALID_PARAMETER_1\n"); Fdo.c (drivers\serial\serenum): Status = IoSetDeviceInterfaceState(&DeviceExtension->SerenumInterfaceName, TRUE); Fdo.c (drivers\serial\serenum): DPRINT("IoSetDeviceInterfaceState() failed with status 0x%08lx\n", Status); Ntoskrnl.def (ntoskrnl):IoSetDeviceInterfaceState@8 Pdo.c (drivers\usb\usbhub): Status = IoSetDeviceInterfaceState(&DeviceExtension->SymbolicLinkName, TRUE); Pdo.c (drivers\usb\usbhub): DPRINT("Usbhub: IoSetDeviceInterfaceState() failed with status 0x%08lx\n", Status); Pnp.c (drivers\serial\serial): IoSetDeviceInterfaceState(&DeviceExtension->SerialInterfaceName, FALSE); Pnp.c (drivers\serial\serial): IoSetDeviceInterfaceState(&DeviceExtension->SerialInterfaceName, TRUE); Winddk.h (include\ddk):IoSetDeviceInterfaceState(从这些输出来看:当用户拔插串口,usb时都会使IoRegisterDeviceInterface注册的回调函数得到执行。如果把通知机制比如血液循环系统,那么到这,PNP管理器的循环系统也建立起来了。接着往下看其他脏器的组织结构,pnp管理器中管理的主要对象:设备驱动。
整个windows的设备驱动都是从根设备对象出发,层层堆叠形成一颗树结构(虽然说legacy设备不形成堆叠,但是windows还是把他纳入到堆叠结构,只是其堆叠的PDO是个虚拟的设备对象)。附带说一下,看ReactOS源码时,总觉得PNP管理器跟linux的sysfs概念有点接近,大家都用虚拟的驱动/设备形成一个自上至下的树结构。既然根设备是整个系统中设备驱动的基石,他的出场一定比较靠前--至少要在系统创建其他设备之前已经有了吧?来看下传说中根设备出场函数:
//创建IopRootDriverObject,IopRootDeviceNode,PnpRootDeviceOBject,并建立三者关系 VOID INIT_FUNCTION PnpInit(VOID) { ... Status = IopCreateDriver(NULL, PnpDriverInitializeEmpty, NULL, 0, 0, &IopRootDriverObject); if (!NT_SUCCESS(Status)) { CPRINT("IoCreateDriverObject() failed\n"); KEBUGCHECKEX(PHASE1_INITIALIZATION_FAILED, Status, 0, 0, 0); } Status = IoCreateDevice(IopRootDriverObject, 0, NULL, FILE_DEVICE_CONTROLLER, 0, FALSE, &Pdo); if (!NT_SUCCESS(Status)) { CPRINT("IoCreateDevice() failed\n"); KEBUGCHECKEX(PHASE1_INITIALIZATION_FAILED, Status, 0, 0, 0); } Status = IopCreateDeviceNode(NULL, Pdo, NULL, &IopRootDeviceNode); if (!NT_SUCCESS(Status)) { CPRINT("Insufficient resources\n"); KEBUGCHECKEX(PHASE1_INITIALIZATION_FAILED, Status, 0, 0, 0); } if (!RtlCreateUnicodeString(&IopRootDeviceNode->InstancePath, L"HTREE\\ROOT\\0")) { CPRINT("Failed to create the instance path!\n"); KEBUGCHECKEX(PHASE1_INITIALIZATION_FAILED, STATUS_NO_MEMORY, 0, 0, 0); } /* Report the device to the user-mode pnp manager */ IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL, &IopRootDeviceNode->InstancePath); PnpRootDriverEntry(IopRootDriverObject, NULL); IopRootDeviceNode->PhysicalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; //居然自己主动调用AddDevice,而不是通过内核调用IopInitializeDevice IopRootDriverObject->DriverExtension->AddDevice( IopRootDriverObject, IopRootDeviceNode->PhysicalDeviceObject); ... }PnpInit调用了PnpRootDriver的驱动入口和AddDevice函数,这么看来PnpRootDriver也算得上是Pnp设备了。跟进PnpRootDriver和AddDevice函数一瞧究竟:
NTSTATUS NTAPI PnpRootDriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { DPRINT("PnpRootDriverEntry(%p %wZ)\n", DriverObject, RegistryPath); //内核加载驱动后在IopInitializeDevice中调用AddDevice DriverObject->DriverExtension->AddDevice = PnpRootAddDevice; DriverObject->MajorFunction[IRP_MJ_PNP] = PnpRootPnpControl; //DriverObject->MajorFunction[IRP_MJ_POWER] = PnpRootPowerControl; return STATUS_SUCCESS; }
PnpRootDriver应该算是一个最简单的DriverEntry入口函数了。如毛德操书中所述,Pnp设备至少要提供PNP派遣函数,PnpRootDriver真的只做了这么件事!
NTSTATUS STDCALL PnpRootAddDevice( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject) { Status = IoCreateDevice( DriverObject, sizeof(PNPROOT_FDO_DEVICE_EXTENSION), NULL, FILE_DEVICE_BUS_EXTENDER, FILE_DEVICE_SECURE_OPEN, TRUE, &PnpRootDeviceObject); ... DeviceExtension = (PPNPROOT_FDO_DEVICE_EXTENSION)PnpRootDeviceObject->DeviceExtension; RtlZeroMemory(DeviceExtension, sizeof(PNPROOT_FDO_DEVICE_EXTENSION)); InitializeListHead(&DeviceExtension->DeviceListHead); DeviceExtension->DeviceListCount = 0; KeInitializeGuardedMutex(&DeviceExtension->DeviceListLock); Status = IoAttachDeviceToDeviceStackSafe( PnpRootDeviceObject, PhysicalDeviceObject, &DeviceExtension->Ldo); }内核中维持一颗已装载的设备驱动模块树,树根是一个虚拟节点IopRootDeviceNode,节点在PnpInit函数中创建。这个树根下挂满了各类总线设备和Legacy设备。形成设备树中的节点是结构:
typedef struct _DEVICE_NODE { struct _DEVICE_NODE *Parent; struct _DEVICE_NODE *NextSibling; struct _DEVICE_NODE *Child; ... PDEVICE_OBJECT PhysicalDeviceObject; PCM_RESOURCE_LIST ResourceList; ... }对于根设备IopRootDeviceNode,从PnpInit中可以看到其PhysicalDeviceObject指向IopRootDriverObject创建的Pdo。这个重要的Pdo是个无名设备,默默无闻的提供堆叠功能,真是居功至伟!PnpInit函数完成了DEVICE_NODE IopRootDeviceNode和DriverObject IopRootDriverObject的创建之后马上调用IopRootDriverObject的AddDevice函数完成设备堆叠。
结合PnpRootAddDevice的代码,可以看到PnpRootDeviceObject的驱动对象是IopRootDriverObject,并且堆叠在PnpInit创建的无名设备对象Pdo上。至此三个重要的对象之间的关系已经确立:IopRootDeviceNode是系统设备堆叠的根,其物理设备是一个无名的设备,并被堆叠了一个以IopRootDriverObject为驱动对象的设备PnpRootDeviceObject。这三个变量算构成PNP管理器的神经中枢,特别是PnpRootDeviceObject,其自定义设备扩展域DeviceExtension,维护了一个PNPROOT_DEVICE结构。为什么这是个特别的域?下面这段代码做出了解释:
/* 这个函数主要干了2件事:创建PNPROOT_DEVICE节点和DEVICE_OBJECT设备对象 由于这个函数只可能因NtLoadDriver创建非pnp设备而间接调用。为了维持 pnp设备树,NtLoadDriver将创建DEVICE_NODE节点,并在PnpRootCreateDevice这个函数 中创建PNPROOT_DEVICE节点和DEVICE_OBJECT pdo对象。 看结构,PNPROOT_DEVICE更像是一个pnp描述符,描述了pdo对象的附加信息。 最主要的,因为创建的DEVICE_NODE挂在IopRootDeviceNode下,因此把这个新 创建的PNPROOT_DEVICE节点挂在IopRootDeviceNode的链表下,这是在表明pnp 根节点下第一层子节点的意思吗? */ NTSTATUS PnpRootCreateDevice( IN PUNICODE_STRING ServiceName, IN PDEVICE_OBJECT *PhysicalDeviceObject) { ... DeviceExtension = PnpRootDeviceObject->DeviceExtension; KeAcquireGuardedMutex(&DeviceExtension->DeviceListLock); ... Device = ExAllocatePoolWithTag(PagedPool, sizeof(PNPROOT_DEVICE), TAG_PNP_ROOT); ... RtlZeroMemory(Device, sizeof(PNPROOT_DEVICE)); ... /* 创建一个用于给legacy设备进行堆叠的pnp root根下第一层pdo对象, 虽然legacy不提供堆叠但是也被纳入pnp树的管理范畴,因此创建 这么个父设备对象。 从IoCreateDevice的参数PnpRootDeviceObject->DriverObject也可以看出创建的pdo 并不是给NtLoadDriver用的,是根节点驱动的设备对象 */ Status = IoCreateDevice( PnpRootDeviceObject->DriverObject, sizeof(PNPROOT_PDO_DEVICE_EXTENSION), NULL, FILE_DEVICE_CONTROLLER, FILE_AUTOGENERATED_DEVICE_NAME, FALSE, &Device->Pdo); /* 上面创建PnpRootDeviceObject设备对象,属于IopRootDriverObject驱动的设备对象 下面将所有属于IopRootDriverObject驱动的设备对象加入链表DeviceExtension->DeviceListHead中 */ PdoDeviceExtension = (PPNPROOT_PDO_DEVICE_EXTENSION)Device->Pdo->DeviceExtension; RtlZeroMemory(PdoDeviceExtension, sizeof(PNPROOT_PDO_DEVICE_EXTENSION)); PdoDeviceExtension->Common.IsFDO = FALSE; PdoDeviceExtension->DeviceInfo = Device; ... InsertTailList( &DeviceExtension->DeviceListHead, &Device->ListEntry); DeviceExtension->DeviceListCount++; *PhysicalDeviceObject = Device->Pdo; }如我的注释所诉,PnpRootDeviceObject!DeviceExtension!DeviceListHead用于存放所有根节点下的设备,设备枚举时通过这个列表遍历各个设备。本以为通过NtLoadDriver装载驱动时会枚举设备请求资源,不过很不幸,我没在ReactOS中找到相关的代码,只在IoInitSystem时看到相关代码。
IoInitSystem调用IoSynchronousInvalidateDeviceRelations,这个函数名字真长按字面意思理解是"同步无效的设备关系"。怎么同步?
VOID NTAPI IoSynchronousInvalidateDeviceRelations( IN PDEVICE_OBJECT DeviceObject, IN DEVICE_RELATION_TYPE Type) { ... Status = IopInitiatePnpIrp( DeviceObject, &IoStatusBlock, IRP_MN_QUERY_DEVICE_RELATIONS, &Stack); if (!NT_SUCCESS(Status)) { DPRINT("IopInitiatePnpIrp() failed with status 0x%08lx\n", Status); return; } DeviceRelations = (PDEVICE_RELATIONS)IoStatusBlock.Information; ... }向DeviceObject发送主功能号为PNP次功能号为IRP_MN_QUERY_DEVICE_RELATIONS的IRP请求,请求结果存放在IoStatusBlock.Information中。这里的DeviceObject即为IopRootDeviceNode->PhysicalDeviceObject,这个设备对象对应的PNP回调函数为:
PnpRootFdoPnpControl!FdoQueryDeviceRelations!PnpRootQueryDeviceRelations这个函数调用EnumerateDevices,枚举注册表Root下的子健,每个子健代表一个设备项(应该是安装驱动时由inf文件设置),对于每个被发现的子健创建PNPROOT_DEVICE结构的节点并挂入IopRootDevice扩展部的DeviceListHead队列中,当EnumerateDevices返回,为枚举到的每个设备创建驱动对象和设备对象,这样整个设备对象树逐渐变得枝繁叶茂起来。
到这里,整个PNP差不多完整了,但是还差一步,设备毕竟运行需要中断,端口等资源来配合完成。因此枚举设备后运行设备前必然要满足设备资源请求。这个在IopActionInterrogateDeviceStack函数中完成。
NTSTATUS IopActionInterrogateDeviceStack(PDEVICE_NODE DeviceNode, PVOID Context) { ... Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject, &IoStatusBlock, IRP_MN_QUERY_ID, &Stack); ... Status = IopQueryDeviceCapabilities(DeviceNode, &DeviceCapabilities); ... Stack.Parameters.QueryId.IdType = BusQueryInstanceID; Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject, &IoStatusBlock, IRP_MN_QUERY_ID, &Stack); ... Status = IopInitiatePnpIrp( DeviceNode->PhysicalDeviceObject, &IoStatusBlock, IRP_MN_QUERY_BUS_INFORMATION, NULL); ... //请求资源 Status = IopInitiatePnpIrp( DeviceNode->PhysicalDeviceObject, &IoStatusBlock, IRP_MN_QUERY_RESOURCE_REQUIREMENTS, NULL); }对于请求的资源存放在一个资源列表中:
DeviceNode->ResourceRequirements = (PIO_RESOURCE_REQUIREMENTS_LIST)IoStatusBlock.Information;然后由IopAssignDeviceResources和IopTranslateDeviceResources遍历资源队列,并调用相应的HAL函数分配物理资源