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驱动开发1(最简单的NT驱动)

VS2013 WDK8.1驱动开发2(最简单的WDM驱动)

VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)

VS2013 WDK8.1驱动开发4(NT式驱动基本结构)

VS2013 WDK8.1驱动开发5(WDM驱动基本结构)

VS2013 WDK8.1驱动开发6(内存管理)

VS2013 WDK8.1驱动开发7(派遣函数)

VS2013 WDK8.1驱动开发8(设备读写操作)

VS2013 WDK8.1驱动开发9(IO设备控制操作)


分享给朋友阅读吧


您还未登录,登录微博账号发表精彩评论

 微博登录


最新评论

    还没有人评论...

 

 

刘杰

28岁, 现居苏州

微博:

微信:

BurnellLIU

邮箱:

burnell_liu@outlook.com

Github:

https://github.com/BurnellLiu

简介:

努力做一个快乐的程序员, good good study, day day up!

友情链接: Will Mao

苏ICP备16059872号. Copyright © 2016. http://www.burnelltek.com. All rights reserved.