一、IRP<?xml:namespace prefix = o />
MdlAddress(PMDL)域指向一個記憶體描述符表(MDL),該表描述了一個與該請求關聯的使用者模式緩沖區。如果頂級裝置對象的Flags域為DO_DIRECT_IO,則I/O管理器為IRP_MJ_READ或IRP_MJ_WRITE請求建立這個MDL。如果一個IRP_MJ_DEVICE_CONTROL請求的控制代碼指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式,則I/O管理器為該請求使用的輸出緩沖區建立一個MDL。MDL本身用于描述使用者模式虛拟緩沖區,但它同時也含有該緩沖區鎖定記憶體頁的實體位址。

AssociatedIrp(union)域是一個三指針聯合。其中,與WDM驅動程式相關的指針是AssociatedIrp.SystemBuffer。 SystemBuffer指針指向一個資料緩沖區,該緩沖區位于核心模式的非分頁記憶體中。對于IRP_MJ_READ和IRP_MJ_WRITE操作,如果頂級裝置指定DO_BUFFERED_IO标志,則I/O管理器就建立這個資料緩沖區。對于IRP_MJ_DEVICE_CONTROL操作,如果I/O控制功能代碼指出需要緩沖區,則I/O管理器就建立這個資料緩沖區。I/O管理器把使用者模式程式發送給驅動程式的資料複制到這個緩沖區,這也是建立IRP過程的一部分。這些資料可以是與WriteFile調用有關的資料,或者是DeviceIoControl調用中所謂的輸入資料。對于讀請求,裝置驅動程式把讀出的資料填到這個緩沖區,然後I/O管理器再把緩沖區的内容複制到使用者模式緩沖區。對于指定了METHOD_BUFFERED的I/O控制操作,驅動程式把所謂的輸出資料放到這個緩沖區,然後I/O管理器再把資料複制到使用者模式的輸出緩沖區。
IoStatus(IO_STATUS_BLOCK)是一個僅包含兩個域的結構,驅動程式在最終完成請求時設定這個結構。IoStatus.Status域将收到一個NTSTATUS代碼,而IoStatus.Information的類型為ULONG_PTR,它将收到一個資訊值,該資訊值的确切含義要取決于具體的IRP類型和請求完成的狀态。Information域的一個公認用法是用于儲存資料傳輸操作,如IRP_MJ_READ,的流量總計。某些PnP請求把這個域作為指向另外一個結構的指針,這個結構通常包含查詢請求的結果。
UserBuffer(PVOID) 對于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL請求,該域包含輸出緩沖區的使用者模式虛拟位址。該域還用于儲存讀寫請求緩沖區的使用者模式虛拟位址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驅動程式,其讀寫例程通常不需要通路這個域。當處理一個METHOD_NEITHER控制操作時,驅動程式能用這個位址建立自己的MDL。
Tail.Overlay是Tail聯合中的一種結構,它含有幾個對WDM驅動程式有潛在用途的成員。圖示是Tail聯合的組成圖。在這個圖中,以水準方向從左到右是這個聯合的三個可選成員,在垂直方向是每個結構的成員描述。Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY)和Tail.Overlay.DriverContext(PVOID[4])是Tail.Overlayare内一個未命名聯合的兩個可選成員(隻能出現一個)。I/O管理器把DeviceQueueEntry作為裝置标準請求隊列中的連接配接域。當IRP還沒有進入某個隊列時,如果你擁有這個IRP你可以使用這個域,你可以任意使用DriverContext中的四個指針。Tail.Overlay.ListEntry(LIST_ENTRY)僅能作為你自己實作的私有隊列的連接配接域。
二、IO堆棧
任何核心模式程式在建立一個IRP時,同時還建立了一個與之關聯的IO_STACK_LOCATION結構數組:數組中的每個堆棧單元都對應一個将處理該IRP的驅動程式,另外還有一個堆棧單元供IRP的建立者使用。堆棧單元中包含該IRP的類型代碼和參數資訊以及完成函數的位址。
圖示. 驅動程式和I/O堆棧之間的平行關系
圖示. I/O堆棧單中繼資料結構
MajorFunction(UCHAR)是該IRP的主功能碼。這個代碼應該為類似IRP_MJ_READ一樣的值,并與驅動程式對象中MajorFunction表的某個派遣函數指針相對應。
MinorFunction(UCHAR)是該IRP的副功能碼。
Parameters(union)是幾個子結構的聯合,每個請求類型都有自己專用的參數,而每個子結構就是一種參數。這些子結構包括Create(IRP_MJ_CREATE請求)、Read(IRP_MJ_READ請求)、StartDevice(IRP_MJ_PNP的IRP_MN_START_DEVICE子類型),等等。
DeviceObject(PDEVICE_OBJECT)是與該堆棧單元對應的裝置對象的位址。該域由IoCallDriver函數負責填寫。
FileObject(PFILE_OBJECT)是核心檔案對象的位址,IRP的目标就是這個檔案對象。驅動程式通常在處理清除請求(IRP_MJ_CLEANUP)時使用FileObject指針,以區分隊列中與該檔案對象無關的IRP。
CompletionRoutine(PIO_COMPLETION_ROUTINE)是一個I/O完成例程的位址,該位址是由與這個堆棧單元對應的驅動程式的更上一層驅動程式設定的。你絕對不要直接設定這個域,應該調用IoSetCompletionRoutine函數,該函數知道如何參考下一層驅動程式的堆棧單元。裝置堆棧的最低一級驅動程式并不需要完成例程,因為它們必須直接完成請求。然而,請求的發起者有時确實需要一個完成例程,但通常沒有自己的堆棧單元。這就是為什麼每一級驅動程式都使用下一級驅動程式的堆棧單元儲存自己完成例程指針的原因。
Context(PVOID)是一個任意的與上下文相關的值,将作為參數傳遞給完成例程。你絕對不要直接設定該域;它由IoSetCompletionRoutine函數自動設定,其值來自該函數的某個參數。
圖示. IRP處理的“标準模型”
1、建立
可以使用下面任何一種函數建立IRP:
•IoBuildAsynchronousFsdRequest 建立異步IRP(不需要等待其完成)。該函數和下一個函數僅适用于建立某些類型的IRP。
•IoBuildSynchronousFsdRequest 建立同步IRP(需要等待其完成)。
•IoBuildDeviceIoControlRequest 建立一個同步IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL請求。
•IoAllocateIrp 建立上面三個函數不支援的其它種類的IRP。
2、發往派遣例程
建立完IRP後,你可以調用IoGetNextIrpStackLocation函數獲得該IRP第一個堆棧單元的指針。然後初始化這個堆棧單元。在初始化過程的最後,你需要填充MajorFunction代碼。堆棧單元初始化完成後,就可以調用IoCallDriver函數把IRP發送到裝置驅動程式:
PDEVICE_OBJECT DeviceObject; //something gives you this
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
stack->MajorFunction = IRP_MJ_Xxx;
<other initialization of "stack">
NTSTATUS status = IoCallDriver(DeviceObject, Irp);
IoCallDriver函數的第一個參數是你在某處獲得的裝置對象的位址
IRP中的第一個堆棧單元指針被初始化成指向該堆棧單元之前的堆棧單元,因為I/O堆棧實際上是IO_STACK_LOCATION結構數組,你可以認為這個指針被初始化為指向一個不存在的“-1”元素,是以當我們要初始化第一個堆棧單元時我們實際需要的是“下一個”堆棧單元。IoCallDriver将沿着這個堆棧指針找到第0個表項,并提取我們放在那裡的主功能代碼,在上例中為IRP_MJ_Xxx。然後IoCallDriver函數将利用DriverObject指針找到裝置對象中的MajorFunction表。IoCallDriver将使用主功能代碼索引這個表,最後調用找到的位址(派遣函數)。
NTSTATUS IoCallDriver(PDEVICE_OBJECT device, PIRP Irp)
{
IoSetNextIrpStackLocation(Irp);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
stack->DeviceObject = device;
ULONG fcn = stack->MajorFunction;
PDRIVER_OBJECT driver = device->DriverObject;
return (*driver->MajorFunction[fcn])(device, Irp);
}