驱动入门

从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驱动

 

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

 

 

posted @ 2012-03-14 20:53  KingsLanding  阅读(7930)  评论(1编辑  收藏  举报