PNP设备的加载过程

文章目录

  • PNP设备的加载过程
    • 1. Pnp Root的初始化
    • 2. 设备节点
    • 3. IopEnumerateDevice 设备枚举
    • 4. IopActionInitChildServices
    • 5. 设备的插入
      • 51 系统获取到硬件插入消息
      • 5.2 端口状态改变

PNP设备的加载过程

如果开发过Windows驱动,一定听说过一个概念,那就是驱动的设备树,Windows整个系统通过设备的堆叠,将物理设备对象和功能设备对象堆叠成一颗树,如下:

PNP设备的加载过程_第1张图片

那么整个设备树是怎么加载起来的呢?本文来探讨一下整个过程。

1. Pnp Root的初始化

驱动设备属于IO对象,初始化在IoInitSystem中完成的:

BOOLEAN
INIT_FUNCTION
NTAPI
IoInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
    //...
    /* Initialize PnP manager */
    IopInitializePlugPlayServices();

    //...
    /* Initialize PnP root relations */
    IopEnumerateDevice(IopRootDeviceNode->PhysicalDeviceObject);

    //...
}

这里两个比较重要的东西:

  1. IopInitializePlugPlayServices 初始化Pnp服务。
  2. IopEnumerateDevice枚举整个设备,这里形成设备树。

我们看下PNP服务的初始化

INIT_FUNCTION
NTSTATUS
NTAPI
IopInitializePlugPlayServices(VOID)
{
    //...
    /* Create the root driver */
    Status = IoCreateDriver(&PnpManagerDriverName, PnpRootDriverEntry);

    //...
    Status = IoCreateDevice(IopRootDriverObject,
                            sizeof(IOPNP_DEVICE_EXTENSION),
                            NULL,
                            FILE_DEVICE_CONTROLLER,
                            0,
                            FALSE,
                            &Pdo);

    //...
    /* Create the root device node */
    IopRootDeviceNode = PipAllocateDeviceNode(Pdo);

    //..
    /* Call the add device routine */
    IopRootDriverObject->DriverExtension->AddDevice(IopRootDriverObject,
                                                    IopRootDeviceNode->PhysicalDeviceObject);

    //..                         
}

这里操作总结起来就几个:

  1. 创建Pnp的驱动对象IopRootDriverObject(\Driver\PnpManager).
  2. 接着创建一个根设备对象,这个是整个系统的第一个Pnp设备对象,命名为IopRootDeviceNode.
  3. 接着调用Pnp管理器的AddDevice函数,在这个函数中,会创建一个设备对象,作为PnpManager的FDO,响应IRP请求,注意IopRootDeviceNode->PhysicalDeviceObject(也就是IopRootDeviceNode)设备对象并不响应任何请求,仅仅当做最顶层虚拟的设备对象。

这里创建完成最底层的对象指挥就可以开始枚举所有设备并生成设备树了。

2. 设备节点

整颗设备树中的节点命名为Device Node,这个结构信息如下:

typedef struct _DEVICE_NODE
{
    struct _DEVICE_NODE *Sibling;  //兄弟节点,这里横向串联
    struct _DEVICE_NODE *Child;    //子设备节点,纵向连接
    struct _DEVICE_NODE *Parent;  
    struct _DEVICE_NODE *LastChild;  //最后一个子节点,插入的时候使用,插入的时候使用LastChild->Sibling = Node
    ULONG Level;
    struct _PO_DEVICE_NOTIFY *Notify;
    PO_IRP_MANAGER PoIrpManager;
    PNP_DEVNODE_STATE State;
    PNP_DEVNODE_STATE PreviousState;
    PNP_DEVNODE_STATE StateHistory[20];
    ULONG StateHistoryEntry;
    NTSTATUS CompletionStatus;
    PIRP PendingIrp;
    ULONG Flags;
    ULONG UserFlags;
    ULONG Problem;
    PDEVICE_OBJECT PhysicalDeviceObject;
    PCM_RESOURCE_LIST ResourceList;
    PCM_RESOURCE_LIST ResourceListTranslated;
    UNICODE_STRING InstancePath;
    UNICODE_STRING ServiceName;
    PDEVICE_OBJECT DuplicatePDO;
    PIO_RESOURCE_REQUIREMENTS_LIST ResourceRequirements;
    INTERFACE_TYPE InterfaceType;
    ULONG BusNumber;
    INTERFACE_TYPE ChildInterfaceType;
    ULONG ChildBusNumber;
    USHORT ChildBusTypeIndex;
    UCHAR RemovalPolicy;
    UCHAR HardwareRemovalPolicy;
    LIST_ENTRY TargetDeviceNotify;
    LIST_ENTRY DeviceArbiterList;
    LIST_ENTRY DeviceTranslatorList;
    USHORT NoTranslatorMask;
    USHORT QueryTranslatorMask;
    USHORT NoArbiterMask;
    USHORT QueryArbiterMask;
    union
    {
        struct _DEVICE_NODE *LegacyDeviceNode;
        PDEVICE_RELATIONS PendingDeviceRelations;
    } OverUsed1;
    union
    {
        struct _DEVICE_NODE *NextResourceDeviceNode;
    } OverUsed2;
    PCM_RESOURCE_LIST BootResources;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    PCM_RESOURCE_LIST BootResourcesTranslated;
#endif
    ULONG CapabilityFlags;
    struct
    {
        PROFILE_STATUS DockStatus;
        LIST_ENTRY ListEntry;
        WCHAR *SerialNumber;
    } DockInfo;
    ULONG DisableableDepends;
    LIST_ENTRY PendedSetInterfaceState;
    LIST_ENTRY LegacyBusListEntry;
    ULONG DriverUnloadRetryCount;
    struct _DEVICE_NODE *PreviousParent;
    ULONG DeletedChildren;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    ULONG NumaNodeIndex;
#endif
} DEVICE_NODE, *PDEVICE_NODE;

这个数据结构相当的庞大,保存着这个设备的所有信息(例如资源,ID等等)。

3. IopEnumerateDevice 设备枚举

完成了整个PNP管理器的初始化之后,就可以开始枚举设备了,代码如下:

NTSTATUS
IopEnumerateDevice(
    IN PDEVICE_OBJECT DeviceObject)
{
    //...
    Status = IopInitiatePnpIrp(
        DeviceObject,
        &IoStatusBlock,
        IRP_MN_QUERY_DEVICE_RELATIONS,  //查询设备的关系Pnp根对象的响应为 PnpRootPnpControl
        &Stack);

    //...
    for (i = 0; i < DeviceRelations->Count; i++)
    {
        ChildDeviceObject = DeviceRelations->Objects[i];
        ChildDeviceNode = IopGetDeviceNode(ChildDeviceObject);
        if (!ChildDeviceNode)
        {
            /* One doesn't exist, create it */
            Status = IopCreateDeviceNode(
                DeviceNode,
                ChildDeviceObject,
                NULL,
                &ChildDeviceNode);
    }

    //..
    IopInitDeviceTreeTraverseContext(
        &Context,
        DeviceNode,
        IopActionInterrogateDeviceStack,  //遍历配置信息,主要是DeviceId, HardwardId等信息
        DeviceNode);

    Status = IopTraverseDeviceTree(&Context);
    //...
    IopInitDeviceTreeTraverseContext(
        &Context,
        DeviceNode,
        IopActionConfigureChildServices,  //获取设备的Services 和 ClassGuid放到 _DEVICE_NODE 结构中
        DeviceNode);

    Status = IopTraverseDeviceTree(&Context);
    //...
     Status = IopInitializePnpServices(DeviceNode);
}

NTSTATUS
IopInitializePnpServices(IN PDEVICE_NODE DeviceNode)
{
    DEVICETREE_TRAVERSE_CONTEXT Context;

    DPRINT("IopInitializePnpServices(%p)\n", DeviceNode);

    IopInitDeviceTreeTraverseContext(
        &Context,
        DeviceNode,
        IopActionInitChildServices,
        DeviceNode);

    return IopTraverseDeviceTree(&Context);
}


NTSTATUS
IopTraverseDeviceTree(PDEVICETREE_TRAVERSE_CONTEXT Context)
{
    NTSTATUS Status;
    /* Start from the specified device node */
    Context->DeviceNode = Context->FirstDeviceNode;

    /* Recursively traverse the device tree */
    Status = IopTraverseDeviceTreeNode(Context);
    return Status;
}


NTSTATUS
IopTraverseDeviceTreeNode(PDEVICETREE_TRAVERSE_CONTEXT Context)
{
    //...
    for (ChildDeviceNode = ParentDeviceNode->Child;
         ChildDeviceNode != NULL;
         ChildDeviceNode = NextDeviceNode)
    {
    	//...
        Context->DeviceNode = ChildDeviceNode;

        Status = IopTraverseDeviceTreeNode(Context);
       //..

        NextDeviceNode = ChildDeviceNode->Sibling;
        //..
    }

	//..
}

这个代码的主要实现功能为这几个:

  1. 向根总线发送IRP_MN_QUERY_DEVICE_RELATIONS请求查询子设备个数的请求;这里如果是根总线,那么就会查询到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\ROOT这个下面所有的设备信息,并为每个设备信息创建一个设备对象作为PDO与之对应。
  2. 然后通过for (i = 0; i < DeviceRelations->Count; i++)将刚刚查询到的设备创建设备节点并加入到设备树中。
  3. 接下来两个IopTraverseDeviceTree用来遍历这棵树,从注册表中查询一些信息,例如描述,ClassGuid等等。
  4. 最后使用IopTraverseDeviceTree并指定IopActionInitChildServices来加载所有设备对应的驱动并初始化。

4. IopActionInitChildServices

我们从上面分析得知IopActionInitChildServices这个函数是用来加载设备节点对应的所有信息,包含驱动,过滤驱动等等,接下来我们看下这个函数的实现主要流程。

NTSTATUS
IopActionInitChildServices(PDEVICE_NODE DeviceNode,
                           PVOID Context)
{
    //...
     Status = IopLoadServiceModule(&DeviceNode->ServiceName, &ModuleObject);  //加载service对应的驱动
    //...
    Status = IopInitializeDriverModule(DeviceNode, ModuleObject,
                    &DeviceNode->ServiceName, FALSE, &DriverObject);
    //...
     Status = PipCallDriverAddDevice(DeviceNode, FALSE, DriverObject);
    //...
}

这个函数主要三个流程:

  1. IopLoadServiceModule加载设备对应的服务的驱动(设备对应的服务在硬件键中可以查到)。
  2. PipCallDriverAddDevice调用设备对象的AddDevice来创建工作的功能对象(代表对象可以正常工作)。
NTSTATUS
NTAPI
PipCallDriverAddDevice(IN PDEVICE_NODE DeviceNode,
                       IN BOOLEAN LoadDriver,
                       IN PDRIVER_OBJECT DriverObject)
{
    //...
    //先加载硬件的Lower过滤驱动(SubKey), 然后加载Class的Lower过滤驱动(ClassKey), 
    /* Do ReactOS-style setup */
    Status = IopAttachFilterDrivers(DeviceNode, SubKey, ClassKey, TRUE);  //回调IopAttachFilterDriversCallback
   

    //调用AddDevice
    Status = IopInitializeDevice(DeviceNode, DriverObject); 
    

    Status = IopAttachFilterDrivers(DeviceNode, SubKey, ClassKey, FALSE);

    //启动设备
    Status = IopStartDevice(DeviceNode);
    //...
}

这里我们可以得到当PnP管理器遇到一个新设备时,它打开设备的硬件键和类键,然后按如下顺序装入驱动程序:

  1. 硬件键中指定的任何低层过滤器驱动程序。由于LowerFilter值的类型是REG_MULTI_SZ,所以它可以指定多个驱动程序,其装入顺序由它们在串中的位置决定。
  2. 类键中指定的任何低层过滤器驱动程序。同样,它也可以指定多个驱动程序,装入顺序同1。
  3. 硬件键中Service值指定的驱动程序。
  4. 硬件键中指定的任何高层过滤器驱动程序,同样,它也可以指定多个驱动程序,装入顺序同1。
  5. 类键中指定的任何高层过滤器驱动程序,同样,它也可以指定多个驱动程序,装入顺序同1。

最后,调用IopStartDevice启动驱动,这个函数给设备分配资源并且调用IRP_MN_START来启动设备。

5. 设备的插入

上面分析的是当系统启动的时候设备已经存在的情况,那么如果向系统新插入一个设备,那么整个流程是什么样的呢?

51 系统获取到硬件插入消息

当硬件插入到系统的时候,系统会检测到硬件的插入,并产生中断,并向总线和端口驱动下发相关请求,如下:

ChildEBP RetAddr  Args to Child              
f6b86c04 baff1b3e 87ae11a8 87338da3 87338ce8 usbhub!USBH_SubmitInterruptTransfer
f6b86c1c 8081e103 87ae10f0 00000000 87ae11a8 usbhub!USBH_PowerIrpCompletion+0xe6
f6b86c4c f6f76d2a 00000001 f6b86c94 f6f85914 nt!IopfCompleteRequest+0xcd
f6b86c58 f6f85914 878f5030 87338ce8 00000000 USBPORT!USBPORT_CompleteIrp+0x2a
f6b86c94 f6f76e66 878f5030 87338ce8 808acb68 USBPORT!USBPORT_PdoPowerIrp+0x5fa
f6b86cb8 8081dfaf 87338da0 878f5188 87338da0 USBPORT!USBPORT_Dispatch+0x132
f6b86ccc 8086cd6b baff2f27 87338da0 87338ce8 nt!IofCallDriverSpecifyReturn+0x41
f6b86ce0 8086d295 87338da0 87338ce8 baff2f27 nt!PopPresentIrp+0x59
f6b86d04 baff2f27 878f5030 878f59e8 87338ce8 nt!PoCallDriver+0x19b
f6b86d28 bafedd6f 87ae11a8 87338ce8 00000002 usbhub!USBH_FdoPower+0x3fd
f6b86d50 bafedf08 87ae11a8 87338ce8 f6b86d74 usbhub!USBH_FdoDispatch+0x7f
f6b86d60 8081dfaf 87ae10f0 87338ce8 808ae5fc usbhub!USBH_HubDispatch+0x5e
f6b86d74 8086cbf3 00000000 f6b86dac 80880441 nt!IofCallDriverSpecifyReturn+0x41
f6b86d80 80880441 87338ce8 00000000 88024240 nt!PopPassivePowerCall+0x15
f6b86dac 80949b7c 87338ce8 00000000 00000000 nt!ExpWorkerThread+0xeb
f6b86ddc 8088e062 80880356 80000001 00000000 nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

5.2 端口状态改变

接着总线开始接受到端口状态改变的消息,并调用IoInvalidateDeviceRelations,提交设备关系已经改变。

0: kd> kb
ChildEBP RetAddr  Args to Child              
f78ead0c bafed0af 878f5030 00000000 00000006 nt!IoInvalidateDeviceRelations
f78ead2c baff67fc f78ead58 00000001 87ae11a8 usbhub!USBH_ProcessPortStateChange+0x111
f78ead50 bafefc3c 00010501 870ecd90 881ba3f0 usbhub!USBH_ChangeIndicationWorker+0x148
f78ead6c 808ec1eb 87ae10f0 87f47548 808ae5fc usbhub!UsbhIoWorker+0x24
f78ead80 80880441 87f47548 00000000 881ba3f0 nt!IopProcessWorkItem+0x13
f78eadac 80949b7c 87f47548 00000000 00000000 nt!ExpWorkerThread+0xeb
f78eaddc 8088e062 80880356 00000001 00000000 nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

也就是说,当我们的硬件插入的时候,总线驱动最先感知到硬件的插入,然后调用IoInvalidateDeviceRelations更新总线关系,代码如下:

VOID
NTAPI
IoInvalidateDeviceRelations(
    IN PDEVICE_OBJECT DeviceObject,
    IN DEVICE_RELATION_TYPE Type)
{
    DEVICE_ACTION_DATA ActionData;

    ActionData.DeviceObject = DeviceObject;
    ActionData.Action = DeviceActionInvalidateDeviceRelations;
    ActionData.InvalidateDeviceRelations.Type = Type;

    IopQueueDeviceAction(&ActionData);
}

DeviceActionInvalidateDeviceRelations这个动作将会导致设备的枚举操作,代码如下:

NTSTATUS
NTAPI
IoSynchronousInvalidateDeviceRelations(
    IN PDEVICE_OBJECT DeviceObject,
    IN DEVICE_RELATION_TYPE Type)
{
    PAGED_CODE();

    switch (Type)
    {
        case BusRelations:
            /* Enumerate the device */
            return IopEnumerateDevice(DeviceObject);
        case PowerRelations:
             /* Not handled yet */
             return STATUS_NOT_IMPLEMENTED;
        case TargetDeviceRelation:
            /* Nothing to do */
            return STATUS_SUCCESS;
        default:
            /* Ejection relations are not supported */
            return STATUS_NOT_SUPPORTED;
    }
}

IopEnumerateDevice(DeviceObject);是我们上面分析的设备枚举的过程。设置请求之后,我们将重新枚举总线设备树,如下

1: kd> kb
ChildEBP RetAddr  Args to Child              
f6b868e8 baff9d02 0000154b 00006001 00001100 usbhub!USBH_BuildHardwareIDs
f6b86914 baffa0ad 87977d10 87d2af28 87ae11a8 usbhub!USBH_PdoQueryId+0x186
f6b86930 baff0880 87977d10 87d2ae70 00000013 usbhub!USBH_PdoPnP+0x177
f6b86958 bafedef2 01977d10 87d2ae70 f6b8697c usbhub!USBH_PdoDispatch+0x5e
f6b86968 8081df65 87977c58 87d2ae70 f6b869e8 usbhub!USBH_HubDispatch+0x48
f6b8697c 8090d728 f6b869e8 f6b86ab4 00000000 nt!IofCallDriver+0x45
f6b869a8 8090e07e 87977c58 f6b869c4 f6b86ab4 nt!IopSynchronousCall+0xb8
f6b869ec 80907736 87977c58 00000001 f6b86ab4 nt!PpIrpQueryID+0x3e
f6b86a14 8090c7a6 8728ec70 00000001 f6b86ab4 nt!PpQueryID+0x24
f6b86ad0 8090cd2d 0128ec70 87f9c920 87439a88 nt!PiProcessNewDeviceNode+0x6f8
f6b86d24 8090d21c 87439a88 00000001 00000000 nt!PipProcessDevNodeTree+0x16b
f6b86d58 80823325 00000003 88024240 808ae5fc nt!PiProcessReenumeration+0x60
f6b86d80 80880441 00000000 00000000 88024240 nt!PipDeviceActionWorker+0x16b
f6b86dac 80949b7c 00000000 00000000 00000000 nt!ExpWorkerThread+0xeb
f6b86ddc 8088e062 80880356 80000001 00000000 nt!PspSystemThreadStartup+0x2e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

此时由总线驱动生成一个硬件ID。

你可能感兴趣的:(Windows系统原理)