[Windows驅動開發](三)基礎知識——驅動例程


一、NT式驅動的基本例程

1. 驅動入口函數——DriverEntry

// 驅動程序的一般性定義

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath);

// 

DriverEntry的主要工作是對驅動程序進行初始化。它由系統進程System調用的。

驅動被加載的時候會創建一個驅動對象,查詢此驅動程序對應的註冊表項。

DriverEntry被調用的時候會傳進兩個參數。他們分別是剛纔創建的驅動對象的指針和指向設備服務鍵的鍵名字符串指針。這個字符串的內容一般是\RESGISTRY\MACHINE\SYSTEM\ControlSet\Services\[服務名]。在驅動程序中,字符串一般以UNICODE形式存在:

注:設備服務鍵的鍵名有時候需要保存下來,當函數返回的時候此值可能會被清空。

DriverEntry等內核函數的返回值一般是NTSTATUS類型的。使用宏NT_SUCCESS(NTSTATUS status)可檢測返回狀態是否成功。

在DriverEntry中,一般需要做一下幾件事情:

a. 設置驅動卸載例程

b. IRP的派發函數

c. 創建設備對象

其中,驅動卸載例程與IRP派發函數都是對驅動對象設置的。設備對象中有一個MajorFunction數字,用來存放哥哥IRP派發函數的函數指針。

在NT式驅動中,創建設備對象可以調用IoCreateDevice:

// IoCreateDevice在WDK中的定義

NTKERNELAPI
NTSTATUS
IoCreateDevice(
    __in  PDRIVER_OBJECT DriverObject,
    __in  ULONG DeviceExtensionSize,
    __in_opt PUNICODE_STRING DeviceName,
    __in  DEVICE_TYPE DeviceType,
    __in  ULONG DeviceCharacteristics,
    __in  BOOLEAN Exclusive,
    __out
    __drv_out_deref(
        __drv_allocatesMem(Mem)
        __drv_when((((inFunctionClass$("DRIVER_INITIALIZE"))
             ||(inFunctionClass$("DRIVER_DISPATCH")))),
             __drv_aliasesMem)
        __on_failure(__null))
    PDEVICE_OBJECT *DeviceObject
    );

//

DriverObject:當前驅動對象指針。

DeviceExtensionSize:設備擴展的大小。IO管理器會根據這個大小在內存中創建設備擴展,並與驅動對象關聯。

DeviceName:設備對象的名稱。

DeviceCharacteristics:設備對象的特徵。

Exclusive:設備對象是否爲內核模式下使用,一般固定爲TRUE。

DeviceObject:創建好的設備對象指針。

注:設備名稱字符串必須是\Device\[設備名]的形式。


讓用戶態的應用程序能識別設備一般有兩種方法:

a. 符號鏈接
b. 設備接口(在NT式驅動很少使用)

符號鏈接可以理解爲設備對象在用戶態的名稱。即:設備名稱在內核態使用,符號鏈接在用戶態使用。

創建符號鏈接可以使用IoCreateSymbolicLink:

// IoCreateSymbolicLink在WDK中的定義

NTKERNELAPI
NTSTATUS
IoCreateSymbolicLink(
    __in PUNICODE_STRING SymbolicLinkName,
    __in PUNICODE_STRING DeviceName
    );

//

SymbolicLinkName:符號鏈接名。

DeviceName:設備對象名。

注:在內核態,符號鏈接是以“\??\”或者“\DosDevices\”開頭的。而在用戶模式下則是以“\\.\”開頭的。


設備擴展在被使用時可使用如下的代碼:

// Code:使用設備擴展

PDEVICE_EXTENSION pDeviceExtension = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

// 

2. 驅動卸載例程——DriverUnload

此例程在驅動卸載的時候被調用。在NT式驅動裏面負責刪除在DriverEntry中創建的設備對象,並且刪除與其相關聯的符號鏈接。同時還負責某些資源的回收工作。

刪除設備對象的函數是IoDeleteDevice

// IoDeleteDevice在WDK中的定義

NTKERNELAPI
VOID
IoDeleteDevice(
    __in __drv_mustHold(Memory) __drv_freesMem(Mem) PDEVICE_OBJECT DeviceObject
    );

//

DeviceObject:要被刪除的設備對象指針。


刪除符號鏈接的函數是IoDeleteSymbolicLink

// IoDeleteSymbolicLink在WDK中的定義

NTKERNELAPI
NTSTATUS
IoDeleteSymbolicLink(
    __in PUNICODE_STRING SymbolicLinkName
    );

//

SymbolicLinkName:已經被註冊的、待刪除的符號鏈接。


根據驅動對象可以遍歷所有由該驅動創建的設備對象。通過驅動對象的DeviceObject域可以找到驅動對象的第一個設備,然後根據設備對象的NextDevice域,就可以找到設備鏈表中其他的設備對象。


二、WDM式驅動的基本例程

//

在WDM模型中,一個設備的操作至少需要兩個設備對象共同來完成。一個是物理設備對象(Physical Device Object,簡稱PDO),一個是功能設備對象(Function Device Object,簡稱FDO)。當計算機插入某個設備的時候,總線驅動會自動創建PDO。PDO不能單獨操作設備,必須與FDO一起使用。當Windows提示要安裝驅動的時候,實際上安裝的是WDM驅動程序,負責創建FDO,並附加到PDO上。

當一個FDO附加在PDO的時候,PDO的AttachedDevice會記錄FDO的位置。PDO是底層驅動(下層驅動),FDO是高層驅動(上層驅動)。

在FDO與PDO之間還會存在過濾驅動。在FDO上面的叫做上層過濾驅動,在FDO下層的叫做下層過濾驅動。一個WDM驅動可以有很多個上層過濾驅動和下層過濾驅動。設備對象的StackSize子域表明該設備對象到最下層的物理設備中間還存在的設備對象數。

1. WDM驅動的入口函數——DriverEntry

和NT式驅動一樣,WDM驅動的入口程序也是DriverEntry。但是創建設備對象的功能並不在DriverEntry中執行,而是交給了新的例程——AddDevice;同時增加了對IRP_MJ_PNP處理的派發函數。

AddDevice例程是WDM特有的,在DriverEntry中需要設置AddDevice例程的函數地址。設置的方式是在驅動對象的DriverExtension子域的AddDevice子域保存AddDevice實際例程的函數地址。AddDevice例程的名字可以隨意命名。

// AddDevice函數聲明
NTSTAUS MyAddDevice(IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject)

// 設置AddDevice實際例程的地址
pDriverObject->DriverExtension-> AddDevice = MyAddDevice;

//

2. 驅動卸載例程——DriverUnload()

在WDM驅動中,實際的卸載工作被IRP_MN_REMOVE_DEVICE對應的派發函數處理了,這裏的DriverUnload主要處理在DriverEntry中申請的內存。

    a. IRP_MN_REMOVE_DEVICE處理
    驅動橫須內部是由IRP驅動的,IRP_MN_REMOVE_DEVICE這個IRP是當設備需要被卸載的時候,由即插即用管理器創建,併發送到驅動程序中的。IRP一般由兩個號碼指定該IRP的具體意義,一個是主IRP號(Major IRP),一個是輔IRP號(Minor IRP)。
    當設備需要被卸載的時候,會先後發出多個IRP_MJ_PNP。這些IRP的輔IRP號會有所不同。其中之一的IRP_MN_REMOVE_DEVICE。
    在WDM驅動程序中,對設備的卸載一般是在對IRP_MN_REMOVE_DEVICE的出口函數中進行卸載。其除了需要刪除學部,取消符號鏈接外,還需要將FDO從PDO的堆棧中移除。需要調用IoDetachDevice:
// IoDetachDevice在WDK中的定義:

NTKERNELAPI
VOID
IoDetachDevice(
    __inout PDEVICE_OBJECT TargetDevice    // 下層堆棧上的設備對象
    );

// 
此時,FDO從設備鏈上刪除,但是PDO還在。PDO由操作系統負責刪除。



AddDevice基本步驟:
1. AddDevice通過IoCreateDevice函數創建FDO,創建FDO的符號鏈接
2. 在驅動設備擴展保存剛纔創建的FDO的地址。
3. 調用IoAttachDeviceToDeviceStack()將FDO附加到PDO上。
// IoAttachDeviceToDeviceStack在WDK中的定義

NTKERNELAPI
PDEVICE_OBJECT
IoAttachDeviceToDeviceStack(
    __in __drv_mustHold(Memory) __drv_when(return!=0, __drv_aliasesMem)
    PDEVICE_OBJECT SourceDevice,
    __in PDEVICE_OBJECT TargetDevice
    );

//
SourceDevice:FDO附加在PDO上時,這個參數代表FDO。
TargetDevice:被附加的設備。如果在FDO與PDO之間存在過濾驅動,則FDO實際上是附加在過濾驅動上的,過濾驅動則附加在PDO上。
返回值:返回SourceDevice的下層設備。



// 根據上面的一個設備擴展定義

typedef struct _DEVICE_EXTENSION
{
    PDEVICE_OBJECT pFunctionDeviceObject;  // 設備對象(FDO)
    UNICODE_STRING ustrDeviceName;         // 設備名稱
    UNICODE_STRING ustrSymbolicLinkName;   // 符號鏈接名

    PDEVICE_OBJECT pNextStackDevice;       // 下一個設備對象(FDO)的地址
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;


// 

4. 設置設備擴展pFunctionDeviceObject->Flags。
    DO_BUFFERED_IO:緩衝內存設備
    ~DO_DEVICE_INITIALIZING:這個必須設置,表示完成Flags的初始化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章