zoukankan      html  css  js  c++  java
  • Windows NT驱动程序的基本结构和实例

    Windows驱动程序分为两类:一类是不支持即插即用功能的NT式驱动程序;另一类是支持即插即用功能的WDM驱动程序。

    NT式驱动的基本结构:

    1)驱动加载过程与驱动入口函数DriverEntry:

    驱动程序入口点函数通常命名为DriverEntry,也可以指定另外的名字,但最好遵循这个名字:

    DRIVER_INITIALIZE DriverEntry;

    NTSTATUS DriverEntry(

      __in  struct _DRIVER_OBJECT *DriverObject, //指向驱动对象的指针

      __in  PUNICODE_STRING RegistryPath //指向设备服务键的键名字符串的指针

    )

    { ... }

    DriverEntry主要是对驱动程序进行初始化工作,它是由系统进程所调用的。在Windows中有个特殊的进程叫做系统进程,它是名为System的进程。系统进程在系统启动时就已经被创建了。

    驱动加载时,系统进程启动新的线程,调用执行体组件中的对象管理器,创建一个驱动对象,这个驱动对象就是一个DRIVER_OBJECT的结构体。此外,系统进程调用执行体组件中的配置管理程序,查询此驱动程序对应的注册表中的项。

    在DriverEntry中,主要功能是对系统进程创建的驱动对象进行初始化。另外,设备服务键的键名有时候需要保存下来,因为这个字符串不是长期存在的(函数返回后可能消失)。这个字符串内容一般是/REGISTRY/MACHINE/SYSTEM/ControlSet/Services/[服务名]

    DriverEntry返回值是NTSTATUS的数据,NTSTATUS是被定义为32位的无符号长整型。在驱动开发中,习惯用NTSTATUS返回状态,其中0~0x7FFFFFFF是正确的状态;0x80000000~0xFFFFFFFF是错误的状态。可以使用宏NT_SUCCESS来检测状态的正确性。

    注意:DriverEntry参数的修饰“IN(__in)”、“OUT(__out)”、“INOUT(__inout)”在DDK中被定义为空串,它们的功能类似与程序注释:“IN(__in)”表示该参数纯粹用于输入目的;“OUT(__out)”表示该参数仅用于函数的输出参数;“INOUT(__inout)”用于既可以输入又可以输出的参数。

    2)创建设备对象:

    在NT式驱动中,创建设备对象由IoCreateDevice内核函数完成:

    NTSTATUS IoCreateDevice(

      __in      PDRIVER_OBJECT DriverObject, //指向驱动对象的指针

      __in      ULONG DeviceExtensionSize, //指定设备扩展的大小(I/O管理器根据这个大小在内存中

    //创建设备扩展,并与驱动对象关联)

      __in_opt  PUNICODE_STRING DeviceName, //设置设备对象的名字

      __in      DEVICE_TYPE DeviceType, //设置设备对象的类型

      __in      ULONG DeviceCharacteristics, //设置设备对象的特征

      __in      BOOLEAN Exclusive, //设置设备对象是否在内核模式下使用,一般设置为TRUE

      __out     PDEVICE_OBJECT *DeviceObject //I/O管理器负责创建这个设备对象,

    //并返回设备对象的地址

    );

    设备名称用UNICODE字符串指定,并且字符串必须是“/Device/[设备名]”的形式,在Windows下所有设备都是以类似名字命名的,例如磁盘分区的C盘、D盘分别是“/Device/HarddiskVolume1”、“/Device/HarddiskVolume2”。当然也可以不指定设备名字,这时I/O管理器会自动分配一个数字作为设备的设备名:如“/Device/00000001”。

    指定的设备名只能被内核模式下的其他驱动所识别,在用户模式下的应用程序是无法识别这个设备的,对此,有两种解决方法:一是通过符号链接找到设备;一是通过设备接口找到设备,这种在NT驱动中很少使用。

    符号链接可以理解成为设备对象起一个“别名”。设备对象的名字只能被内核模式驱动识别,而别名可以被用户模式下的应用程序识别。所谓的C盘,指的是名为“C:”的符号链接,其真正的设备对象是“/Device/HarddiskVolume1”。创建符号链接的函数是IoCreateSymbolicLink:

    NTSTATUS IoCreateSymbolicLink(

      __in  PUNICODE_STRING SymbolicLinkName, //符号链接的字符串

      __in  PUNICODE_STRING DeviceName //设备对象名的字符串

    );

    在内核模式下,符号链接是以“/??/”开头的(或者是“/DosDevices/”开头),如C盘就是“/??/C:”。而用户模式下则是以//./开头的,如C盘就是//./C:

    3)DriverUnload例程

    在驱动对象中会设置DriverUnload例程,此例程在驱动被卸载时被调用。在NT驱动中,DriverUnload一般负责删除在DriverEntry中创建的设备对象,并且将设备对象所关联的符号链接删除,另外,DriverUnload还负责对一些资源进行回收。

    下面代码是一个简单的NT式驱动程序,先看头文件HelloDDK.h:

    #pragma once

    /**

    * 这里采用C++语言编写,如果直接包含ntddk.h,函数的符号表

    * 会导入错误,所以需要加入extern "C",以保证符号表的正确导入

    */

    #ifdef __cplusplus

    extern "C"

    {

    #endif

    #include <NTDDK.h> //所有NT式驱动程序都要包含该头文件

    #ifdef __cplusplus

    }

    #endif

    /**

    * 定义分页标记、非分页标记和初始化内存块

    */

    #define PAGEDCODE code_seg("PAGE")

    #define LOCKEDCODE code_seg()

    #define INITCODE code_seg("INIT")

    #define PAGEDDATA data_seg("PAGE")

    #define LOCKEDDATA data_seg()

    #define INITDATA data_seg("INIT")

    #define ARRAYSIZE(p) (sizeof(p)/sizeof((p)[0]))

    /**

    * 指定设备扩展结构体,这种结构体广泛应用与驱动程序中

    * 根据不同驱动程序的需要,它负责补充定义设备的相关信息

    */

    typedef struct _DEVICE_EXTENSION

    {

        PDEVICE_OBJECT pDevice;

        UNICODE_STRING ustrDeviceName; //设备名称

        UNICODE_STRING ustrSymLinkName; //符号链接名

    }DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    /**

    * 函数声明

    */

    NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject);

    VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject);

    NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,

                                        IN PIRP pIrp);

    和普通的应用程序不同,Windows驱动程序的入口函数不是main函数,而是一个叫做DriverEntry的函数,DriverEntry函数由内核中的I/O管理器负责调用:

    DRIVER_INITIALIZE DriverEntry; (最新版,下面用到的有点不同)

    NTSTATUS DriverEntry(

      __in  struct _DRIVER_OBJECT *DriverObject, //I/O管理器传递进来的驱动对象

      __in  PUNICODE_STRING RegistryPath //Unicode字符串,指向此驱动负责的注册表

    )

    { ... }

    代码如下:

    #include "HelloDDK.h"

    /**

    * 函数名称:DriverEntry

    * 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象

    * 参数列表:

    *    pDriverObject:从I/O管理器中传过来的驱动对象

    *    pRegistryPath:驱动程序在注册表中的路径

    * 返回值:返回初始化驱动状态

    */

    #pragma INITCODE      //指明此函数是加载到INIT内存区域中,即成功加载后,可以退出内存

    extern "C" NTSTATUS DriverEntry(//extern "C"指明按C方式编译成_DriverEntry@8的符号,而非C++方式

               IN PDRIVER_OBJECT pDriverObject,

               IN PUNICODE_STRING pRegistryPath)

    {

        NTSTATUS status;

        KdPrint(("Enter DriverEntry/n")); //宏,调试版本会用DbgPrint代替,发行版本不执行任何操作

        //注册其他驱动调用函数入口

        pDriverObject->DriverUnload = HelloDDKUnload;

        pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;

        pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;

        pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;

        pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;

       

        //创建驱动设备对象

        status = CreateDevice(pDriverObject);

        KdPrint(("DriverEntry end/n"));

        return status;

    }

    CreateDevice函数是一个帮助函数,辅助DriverEntry创建一个设备对象。其完全可以展开在DriverEntry中,此处是为了代码的简洁好看:

    /**

    * 函数名称:CreateDevice

    * 功能描述:初始化设备对象

    * 参数列表:

    *    pDriverObject:从I/O管理器中传进来的驱动对象

    * 返回值:返回初始化状态

    */

    #pragma INITCODE

    NTSTATUS CreateDevice(

             IN PDRIVER_OBJECT pDriverObject)

    {

        NTSTATUS status;

        PDEVICE_OBJECT pDevObj;

        PDEVICE_EXTENSION pDevExt;

       

        //创建设备名称

        UNICODE_STRING devName;

        RtlInitUnicodeString(&devName, L"//Device//ASCEDDKDevice");

       

        //创建设备对象

        status = IoCreateDevice(pDriverObject,

                             sizeof(DEVICE_EXTENSION),

                             &(UNICODE_STRING)devName,

                             FILE_DEVICE_UNKNOWN,

                             0, TRUE, &pDevObj);

        if(!NT_SUCCESS(status))

            return status;

       

        //设备对内存的操作分为两种:DO_BUFFERED_IO和DO_DIRECT_IO

        pDevObj->Flags |= DO_BUFFERED_IO;

        pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

        pDevExt->pDevice = pDevObj;

        pDevExt->ustrDeviceName = devName;

    //创建符号链接。驱动程序虽然有了设备名称,但是这种设备名称只能在内核态可见

    //对于应用程序不可见,因此,驱动需要向外界提供一个符号链接,该链接指向真正的设备名称

        UNICODE_STRING symLinkName;

        RtlInitUnicodeString(&symLinkName, L"//??//HelloDDK");

        pDevExt->ustrSymLinkName = symLinkName;

        status = IoCreateSymbolicLink(&symLinkName, &devName);

        if(!NT_SUCCESS(status))

        {

            IoDeleteDevice(pDevObj);

            return status;                      

        }

        return STATUS_SUCCESS;

    }

    卸载驱动例程用于设备被卸载的情况,由I/O管理器负责调用此回调函数。此例程遍历系统中所有此类设备对象。第一个设备对象的地址存在于驱动对象的DeviceObject域中,每个设备对象的NextDevice域记录着下一个设备对象的地址,这样就形成一个链表。卸载驱动例程的主要目的就是遍历系统中所有此类设备对象,然后删除设备对象以及符号链接:

    /**

    * 函数名称:HelloDDKUnload

    * 功能描述:负责驱动程序的卸载操作

    * 参数列表:

    *    pDriverObject:驱动对象

    * 返回值:返回状态

    */

    #pragma PAGEDCODE

    VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject)

    {

        PDEVICE_OBJECT pNextObj;

        KdPrint(("Enter DriverUnload/n"));

        pNextObj = pDriverObject->DeviceObject;

        while(pNextObj != NULL)

        {

            PDEVICE_EXTENSION pDevExt =

                      (PDEVICE_EXTENSION)pNextObj->DeviceExtension;

            //删除符号链接

            UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;

            IoDeleteSymbolicLink(&pLinkName);

            pNextObj = pNextObj->NextDevice;

            IoDeleteDevice(pDevExt->pDevice);

        }    

    }

    对设备对象的创建、关闭和读写操作都被指定到以下这个默认的派遣例程中:

    /**

    * 函数名称:HelloDDKDispatchRoutine

    * 功能描述:对读IRP进行处理

    * 参数列表:

    *    pDevObj:功能设备对象

    *    pIrp:从I/O请求包

    * 返回值:返回状态

    */

    #pragma PAGEDCODE

    NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,

                                        IN PIRP pIrp)

    {

        KdPrint(("Enter HelloDDKDispatchRoutine/n"));

        NTSTATUS status = STATUS_SUCCESS;

        //完成IRP

        pIrp->IoStatus.Status = status;

        pIrp->IoStatus.Information = 0;

        IoCompleteRequest(pIrp, IO_NO_INCREMENT);

        KdPrint(("Leave HelloDDKDispatchRoutine/n"));

        return status;

    }

  • 相关阅读:
    scala java 混合编译配置
    hadoop自带RPC的使用 代码demo
    《Java多线程设计模式》学习
    b+tree(mongoDB索引,mysql 索引) LSM树(hbase ) Elasticsearch索引
    java jvm虚拟机类加载器
    java jvm虚拟机类加载过程
    凉拌麻辣鸡丝
    C#与C++区别-------转载自博客园-Wei_java
    2019.1.17-我不选ABCD,我选E
    2019.1.1-考研总结and如果二战怎么办
  • 原文地址:https://www.cnblogs.com/alsofly/p/3623878.html
Copyright © 2011-2022 走看看