• 驱动入门


    从DriverEntry()说起

           做过C语言开发的都知道程序是从main()函数开始执行。在进行Windows驱动程序开发的时候没有main()函数作为函数入口,取而代之的是DriverEntry().

    DriverEntry()的原型如下:

    extern "C" NTSTATUS  DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

    前面的extern “C”大概的意思就是调用C编译器对函数进行编译,实现C++和C的混合编程。

    DriverEntry()函数中的第一个参数为:PDRIVER_OBJECT DriverObject,代表一个驱动对象,每个驱动程序都有一个惟一的驱动对象。通过这个函数来初始化。

    typedef struct _DRIVER_OBJECT

    {

            CSHORT      Type;

            CSHORT      Size;

            PDEVICE_OBJECT  DeviceObject;

            ULONG  Flags;

            PVOID DriverStart;

            ULONG DriverSize;

            PVOID DriverSection;

            PDRIVER_EXTENSION DriverExtension;

            UNICODE_STRING DriverName;

            PUNICODE_STRING HardwareDataBase;

            PFAST_IO_DISPATCH FastIoDispatch;

            PDRIVER_INITIALIZE DriverInit;

            PDRIVER_STARTIO DriverStartIo;

            PDRIVER_UNLOAD DriverUnload;

          PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION];

    } DRIVER_OBJECT;

    根据MSDN的描述DRIVER_OBJECT是一个半透明的结构,上面并没有完全列出所有元素。其中有几个域需要进行进一步的说明。

    首先是:      PDEVICE_OBJECT  DeviceObject;

    typedef struct _DEVICE_OBJECT {

      struct _DRIVER_OBJECT *  DriverObject;//指向驱动程序中的驱动对象

      struct _DEVICE_OBJECT *  NextDevice;//指向下一个设备对象

      struct _DEVICE_OBJECT *  AttachedDevice;//如果有高层驱动附加到该

    //驱动的话,AttachedDevice指向那个更高层的驱动

      struct _IRP *  CurrentIrp;//指向当前的IRP结构

      PVOID                       DeviceExtension;//指向设备扩展对象,这是开发人员针对具体设备定义的结构体

      DEVICE_TYPE                 DeviceType;

      CCHAR                       StackSize;

      union {

        LIST_ENTRY         ListEntry;

        WAIT_CONTEXT_BLOCK Wcb;

      } Queue;

      ULONG                       AlignmentRequirement;

      KDEVICE_QUEUE               DeviceQueue;

      KDPC                        Dpc;

    } DEVICE_OBJECT, *PDEVICE_OBJECT;

    DEVICE_OBJECT结构被操作系统用来表示一个设备对象,这个设备对象可以代表逻辑的、虚拟的或者是物理设备。每一个设备对象都会有一个指针指向下一个设备对象(如果有的话),形成一个设备链。设备链的第一个设备时在DRIVER_OBJECT结构中指明的。

    接着是:PDRIVER_UNLOAD DriverUnload;

    这个函数用来指明驱动的卸载函数,在该函数中可以将各种资源释放。

    最后是: PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION];

    指向驱动程序的DispatchXXX函数指针的数组。每个驱动程序至少要设置一个DispatchXXX函数指针在这个数组里来处理这个驱动程序IRP请求包。需要设定一些默认的分发函数(DIspatchXXX)来处理一些默认的IRP包。

    Driver_Entry()函数的第二个参数一般不用管。

    在Driver_Entry()函数中主要是将各种函数和驱动对象关联起来。

    一般可以这样定义:

    extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)

    {

           KdPrint(("Enter DriverEntry\n"));

           pDriverObject->DriverExtension->AddDevice = xxxAddDevice;

           pDriverObject->MajorFunction[IRP_MJ_PNP] = xxxPnp;

           pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =xxxControl;

           pDriverObject->MajorFunction[IRP_MJ_CREATE] =xxxCreate;

           pDriverObject->MajorFunction[IRP_MJ_CLOSE] =xxxClose;

           pDriverObject->MajorFunction[IRP_MJ_READ] =xxxDispatchRoutine;

           pDriverObject->MajorFunction[IRP_MJ_WRITE] = xxxDispatchRoutine;

           pDriverObject->DriverUnload = xxxUnload;

           KdPrint(("Leave DriverEntry\n"));

           return STATUS_SUCCESS;

    }

    现在来挨个介绍一下。首先是DRIVER_OBJECT对象的扩展部分DriverExtension的AddDevice域。当即插即用管理器检测到一个由此驱动程序负责的设备时,即调用此例程来增加一个设备。在xxxAddDevvice “函数”中一般通过调用IoCreateDecive来创建一个DEVICE_OBJECT,并将该对象加入到设备栈中。然后是驱动程序DRIVER_OBJECT对象的MajorFunction,I/O管理器中发送的I/O请求最终都会有这一组函数来处理。这里就列出这些函数的一般定义方式。

    NTSTATUS xxxPnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp)

    对即插即用IRP进行处理,注意参数列表。

    NTSTATUS xxxControl(PDEVICE_OBJECT fdo, PIRP pIrp)

    IO设备控制操作,实现数据的传输,主要上层程序需要的操作都由该函数来完成。需要根据IRP传来的不同的“操作码”采取不同的操作。

    NTSTATUS xxxCreate(IN PDEVICE_OBJECT fdo,IN PIRP Irp)

    对create IRP进行处理

    NTSTATUS xxxClose(IN PDEVICE_OBJECT fdo,IN PIRP Irp)

    对 close IRP 进行处理

    NTSTATUS xxxDispatchRoutine(IN PDEVICE_OBJECT fdo,IN PIRP Irp)

    对缺省的IRP进行处理

    Driver_Entry()函数中最后一个函数:pDriverObject->DriverUnload = xxxUnload;

    用于与初始化例程(Routine)相对应,释放初始化设备时申请的资源。

    现在来探讨一下比较重要的xxxAddDevice 例程。

    NTSTATUS xxxAddDevice(IN PDRIVER_OBJECT DriverObject,IN PDEVICE_OBJECT PhysicalDeviceObject)

    该函数用来创建设和添加新设备对象。其中DriverObject是由I/O管理器传来的驱动对象,也就是是Driver_Entry()函数中的那个驱动程序对象。PhysicalDeviceObject 代表设备堆栈底部的物理设备对象(由总线驱动创建,其实就是操作系统创建,一般被称为PDO)。

    xxxAddDevice函数的基本职责是创建一个设备对象并把它连接到以PDO为底的设备堆栈中。可以通过以下步骤完成:

    1. 调用IoCreateDevice创建设备对象,并建立一个私有的设备扩展对象。
    2. 寄存一个或多个设备接口,以便应用程序能知道设备的存在。另外,还可以给出设备名并创建符号连接。
    3. 调用IoAttachDeviceToDeviceStack函数把新设备对象放到堆栈上。
    4. 初始化设备扩展和设备对象的Flag成员。

    通过IoCreateDevice()函数我们可以这样创建设备对象:

    NTSTATUS status;

          PDEVICE_OBJECT fdo;

          //创建设备名称,注意使用Unicode宽字符

          UNICODE_STRING devName;

          RtlInitUnicodeString(&devName,L"\\Device\\xxxDevice");

    status = IoCreateDevice(

                 DriverObject,

                 sizeof(DEVICE_EXTENSION),

                 &(UNICODE_STRING)devName,//指定设备名

                 FILE_DEVICE_UNKNOWN,

                 0,

                 FALSE,

                 &fdo);

    各个参数说明:

    第一个参数(DriverObject) 就是AddDevice的第一个参数。该参数用于在驱动程序和新设备对象之间建立连接,这样I/O管理器就可以向设备发送指定的IRP。

    第二个参数是设备扩展结构的大小。I/O管理器自动分配这个内存,并把设备对象中的DeviceExtension指针指向这块内存。

    第三个参数表示设备名称

    第四个参数(FILE_DEVICE_UNKNOWN)这个值可以被设备硬件键或类键中的超越值所替代,如果这两个键都含有该参数的超越值,那么硬件键中的超越值具有更高的优先权。对于属于某个已存在类的设备,必须在这些地方指定正确的值,因为驱动程序与外围系统的交互需要依靠这个值。另外,设备对象的默认安全设置也依靠这个设备类型值。

    第五个参数这里设置为0.

    第六个参数(FALSE) 指出设备是否是排斥的。通常,对于排斥设备,I/O管理器仅允许打开该设备的一个句柄。这个值同样也能被注册表中硬件键和类键中的值超越,如果两个超越值都存在,硬件键中的超越值具有更高的优先权。

       

    第七个参数(&fdo) 是存放设备对象指针的地址,IoCreateDevice函数使用该变量保存刚创建设备对象的地址。

          

    当IoCreateDevice返回后,我们可以建立一个私有设备扩展对象,私有设备对象DEVICE_EXTENSION是驱动程序开发者自己定义的。

    可以这样来完成:

    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

    pdx->fdo = fdo;

    可以定义为这样:

    typedef struct _DEVICE_EXTENSION

    {

           PDEVICE_OBJECT fdo;                     //功能层设备对象

           PDEVICE_OBJECT NextStackDevice;          //底层设备对象

           UNICODE_STRING interfaceName;           //设备接口

           UNICODE_STRING devName;               //设备名

           PKINTERRUPT InterruptObject;                    // address of interrupt object

           BOOLEAN mappedport;                                       //如果为真需要做IO端口映射

           PVOID MemBar0;                                                //内存基地址0

           ULONG nMem0;                                           //基地址BAR0占用字节数

           ULONG DmaChannel;                       //DMA通道

    PDMA_ADAPTER  DmaAdapter;               //DMA Adapter 对象

    PALLOCATE_COMMON_BUFFER allocateCommonBuffer;  //分配连续的物理内存

    //DMA函数

    PFREE_COMMON_BUFFER freeCommonBuffer;  //释放连续的物理内存DMA函数

    PPUT_DMA_ADAPTER putDmaAdapter;              //释放DMA Adapter对象

    PHYSICAL_ADDRESS RegsPhyBase;           //寄存器物理地址首地址

    PVOID RegsBase;                         //寄存器虚拟地址首地址

    PHBA_REGS pHBARegs;

    ULONG DmaPort;                          //设备DMA物理端口

          

           …

    }  DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    注:该设备扩展的定义针对了PCI驱动

    设备扩展主要用来维护设备状态信息、存储驱动程序使用的内核对象或系统资源(如自旋锁)、保存驱动程序需要的数据等。由于大多数的总线驱动、功能驱动和过滤器驱动都要工作在任意线程上下文,即任意线程都可能成为当前线程,所以,设备扩展是保存设备状态信息和数据的主要空间。

    make it simple, make it happen
  • 相关阅读:
    webuploader 上传文件参数设置
    数据库报插入异常
    System.Threading.Timer 定时器的用法
    JSON 获取属性值的方法
    JAVA Socket 编程学习笔记(二)
    JAVA Socket 编程学习笔记(一)
    JAVA 判断Socket 远程端是否断开连接
    JAVA 多线程和并发学习笔记(四)
    JAVA 多线程和并发学习笔记(三)
    巧用transform实现HTML5 video标签视频比例拉伸
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/2396560.html
Copyright © 2020-2023  润新知