從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為底的設備堆棧中。可以通過以下步驟完成:
- 調用IoCreateDevice創建設備對象,並建立一個私有的設備擴展對象。
- 寄存一個或多個設備接口,以便應用程序能知道設備的存在。另外,還可以給出設備名並創建符號連接。
- 調用IoAttachDeviceToDeviceStack函數把新設備對象放到堆棧上。
- 初始化設備擴展和設備對象的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驅動
設備擴展主要用來維護設備狀態信息、存儲驅動程序使用的內核對象或系統資源(如自旋鎖)、保存驅動程序需要的數據等。由於大多數的總線驅動、功能驅動和過濾器驅動都要工作在任意線程上下文,即任意線程都可能成為當前線程,所以,設備擴展是保存設備狀態信息和數據的主要空間。