驅動入門


從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驅動

 

設備擴展主要用來維護設備狀態信息、存儲驅動程序使用的內核對象或系統資源(如自旋鎖)、保存驅動程序需要的數據等。由於大多數的總線驅動、功能驅動和過濾器驅動都要工作在任意線程上下文,即任意線程都可能成為當前線程,所以,設備擴展是保存設備狀態信息和數據的主要空間。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM