一、驅動程序層次結構
在《Windows驅動開發詳解》的第四章簡單介紹了一下驅動程序的層次結構,但介紹得不清不楚,反覆看了幾遍,仍然是一分清楚,九分糊塗。爲此,花了幾個小時來查閱相關資料,最後分別參考《Windows驅動開發詳解》和《Windows操作系統原理第2版》,纔算有了個初步的認識。
要想詳細解釋驅動程序的層次結構,以我現在的水平可能還沒那個能力,但或許能通過文字的形式讓自己多一分認識。
1.再看DriverEntry和HelloDDKDispatchRoutine函數
要想理解驅動程序的層次結構,還得再來看看第一個驅動程序的大致流程。我們知道,驅動程序的入口函數是DriverEntry,而在DriverEntry函數中,對DRIVER_OBJECT結構體進行了初始化。其中MajorFunction成員是用來設置IRP對應的派遣函數,這樣使用該驅動程序進行不同的I/O請求時,就會相應相應的派遣函數。下面是DriverEntry函數中對MajorFunction成員的初始化設置:
pDriverObject->MajorFunction[IRP_MJ_CREATE] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
通過以上的初始化,在不同的處理不同的IRP時就會響應相應的派遣函數,接着我們就可以來看看這個傳說中的派遣函數是什麼樣的了:
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;
}
函數體很簡單,而在這個函數中,有一個很重要的結構體,那就是函數參數中的PIRP結構體。PIRP是一個很複雜的結構體,在以上這個派遣函數中,只用到了其中的一個結構體成員。那這個結構體中到底包含了些什麼數據呢?這個很重要!
2.PIRP結構體
參考《Windows操作系統原理 9.2.4》中的解釋,該結構體主要包含IRP請求類型、用戶緩衝區首地址、用戶請求數據的長度等信息,除此之外還包括這個IRP請求的處理結構,比如以上代碼中的“ pIrp->IoStatus.Status = status;”。
PIRP結構體有十多個成員,可以把該結構體分爲兩個部分:固定部分和一個I/O堆棧單元。其中固定部分主要包含一些IRP請求的基本信息,對於這一部分暫且略過。重中之重是I/O堆棧單元,因爲我需要通過了解這一部分來弄清驅動程序的層次結構。
3.IO_STACK_LOCATION結構體(I/O堆棧單元)
I/O堆棧單元由IO_STACK_LOCATION定義,每一個堆棧單元都對應一個設備對象。我們知道,在一個驅動程序中,可以創建一個或多個設備對象,而這些設備對象都對應着一個IO_STACK_LOCATION結構體,而在驅動程序中的多個設備對象,而這些設備對象之間的關係爲水平層次關係。好,到此可以理解驅動程序中的水平層次結構了,但還有一個垂直層次的結構需要理解。
還得再說說IO_STACK_LOCATION這個結構體,從wdm.h中看了下這個結構體的定義,不看不知道,居然有幾十個成員。還是到《Windows操作系統原理》中看看幾個重要成員的作用吧:
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;
UCHAR MinorFunction;
UCHAR Flags;
UCHAR Control;
union Parameters;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PIO_COMPLETION_ROUTINE CompletionRoutine;
PVOID Context;
通過以上的整理,發現之前所說的幾十個成員都是Parameters聯合中的,這就好說了,現在一個一個的來了解這些成員的作用,還是回到《Windows操作系統原理》中。
MajorFuncion是該IRP的主要功能,它指出了I/O請求的操作類型。
MinorFunction是該IRP的副功能,這個參數在《Windows驅動開發詳解》中沒作詳解的解釋,我也就先不作了解。
parameters是多個結構體的聯合,再到wdm.h中看了看,忽然明白了什麼,原來這個聯合中的每一個結構體,都包含了每一個IRP類型的數據,可以看看IRP_MJ_CREATE這個IRP相應的結構體定義:
struct {
PIO_SECURITY_CONTEXT SecurityContext;
ULONG Options;
USHORT POINTER_ALIGNMENT FileAttributes;
USHORT ShareAccess;
ULONG POINTER_ALIGNMENT EaLength;
} Create;
這個結構體中具體的數據就先不管了,至少已經知道parameters中存放的是些什麼數據。
DeviceObject是當前IRP對應的設備對象的地址。
FileObject是指向與一個I/O請求有關的文件對象地址。對這個參數沒什麼認識,先略過。
CompletionRoutine是一個I/O完成例程的地址。該地址是由與這個堆棧單元對應的驅動程序的更上一層驅動程序設置的,驅動程序不要直接設置這個域。這段文字是直接抄《Windows操作系統原理》上的,不太理解。
Context。是一個任意的與上下文相關的值,將作爲參數傳遞給完成例程。
在《Windows操作系統原理》中沒有對Flags和Control這兩個參數作介紹。
通過以上的整理,對於PIRP的數據結構雖然不能清楚認識,但至少也有了一定的理解。之所以要熟PIRP的數據結構,是因爲要了解IO_STACK_LOCATION這個結構體,而瞭解IO_STACK_LOCATION結構體的目的是爲了明白驅動程序的層次結構。
4.入正題,瞭解驅動程序的層次結構
前面已經說到,每個驅動程序中的多個設備對象的關係是水平層次關係,而在瞭解了IO_STACK_LOCATION結構體後,就要說說驅動程序中的垂直層次結構了。
在認識垂直層次結構之前,還得先了解幾個概念:
FiDO(Filter Device Object) 過濾器設備對象
FDO(Functional Device Object) 功能設備對象
PDO(Physical Device Object) 物理設備對象
要了解垂直層次結構,情況就變得複雜多了,這涉及到Windows操作系統原理,我對這方面的認識很少。
“設備的創建順序是,先創建底層PDO,再創建高層FDO,這也是設備堆棧的生長方向,即從底層設備到高層設備。在PDO和FDO中夾雜着各種過濾驅動。每層的設備對象由不同的驅動程序所創建,或者說每層的設備對應着不同的驅動程序。有的驅動程序是系統自帶的,有的是需要程序員編寫。底層設備對象尋找上一層的設備對象,是依靠底層設備對象的AttachedDevice來尋找的,如果某一設備的AttachedDevice爲空,說明已經到了設備堆棧的頂部。”
以上是引用《Windows驅動開發詳解》中第四章中的一段話,對於這段話,我對其中的過濾驅動不太瞭解,但已經知道了PDO、FDO和FiDO之間的關係。其中PDO處於最底層,FDO在PDO的上面,而在FDO的上面和下面分別有不同的PiDO,來看看《Windows驅動開發詳解》中對於這種垂直層次關係的圖示:
5.對層次結構的總結
在對PDO、FDO和FiDO有了一些瞭解後,又再翻看了一下《Windows操作系統原理》中的相關內容,最後對驅動程序又有了一些瞭解。
一個硬件設備至少有兩個驅動程序,一個是PDO驅動,即總線驅動程序;一個是FDO驅動,即功能設備驅動。總線驅動程序負責硬件與計算機的連接;功能設備驅動負責初始化I/O操作,處理I/O操作完成時所產生的中斷事件,而HelloDDK就屬於功能設備驅動。而FiDO驅動又分爲底層過濾器驅動和高層過濾器驅動,也許在以後對文件過濾驅動的學習中會明白這一概念。