IRP

[返回] [下一頁]

數據結構


有兩個數據結構對I/O請求的處理至關重要:I/O請求包(IRP)本身和IO_STACK_LOCATION結構。下面我將詳細描述這兩個結構。

IRP結構

圖5-1顯示了IRP的數據結構,陰影部分代表不透明域。下面是該結構中重要域的簡要描述。

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本身用於描述用戶模式虛擬緩衝區,但它同時也含有該緩衝區鎖定內存頁的物理地址。爲了訪問用戶模式緩衝區,驅動程序必須做一點額外工作。

圖5-1. I/O請求包數據結構

Flags(ULONG)域包含一些對驅動程序只讀的標誌。但這些標誌與WDM驅動程序無關。

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請求把這個域作爲指向另外一個結構的指針,這個結構通常包含查詢請求的結果。

RequestorMode將等於一個枚舉常量UserModeKernelMode,指定原始I/O請求的來源。驅動程序有時需要查看這個值來決定是否要信任某些參數。

PendingReturned(BOOLEAN)如果爲TRUE,則表明處理該IRP的最低級派遣例程返回了STATUS_PENDING。完成例程通過參考該域來避免自己與派遣例程間的潛在競爭。

Cancel(BOOLEAN)如果爲TRUE,則表明IoCancelIrp已被調用,該函數用於取消這個請求。如果爲FALSE,則表明沒有調用IoCancelIrp函數。取消IRP是一個相對複雜的主題,我將在本章的最後詳細描述它。

CancelIrql(KIRQL)是一個IRQL值,表明那個專用的取消自旋鎖是在這個IRQL上獲取的。當你在取消例程中釋放自旋鎖時應參考這個域。

CancelRoutine(PDRIVER_CANCEL)是驅動程序取消例程的地址。你應該使用IoSetCancelRoutine函數設置這個域而不是直接修改該域。

UserBuffer(PVOID) 對於METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL請求,該域包含輸出緩衝區的用戶模式虛擬地址。該域還用於保存讀寫請求緩衝區的用戶模式虛擬地址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO標誌的驅動程序,其讀寫例程通常不需要訪問這個域。當處理一個METHOD_NEITHER控制操作時,驅動程序能用這個地址創建自己的MDL。

Tail.OverlayTail聯合中的一種結構,它含有幾個對WDM驅動程序有潛在用途的成員。圖5-2是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)僅能作爲你自己實現的私有隊列的連接域。

圖5-2. IRP中Tail聯合的組成圖

CurrentLocation (CHAR)和Tail.Overlay.CurrentStackLocation(PIO_STACK_LOCATION)沒有公開爲驅動程序使用,因爲你完全可以使用象IoGetCurrentIrpStackLocation這樣的函數獲取這些信息。但意識到CurrentLocation就是當前I/O堆棧單元的索引以及CurrentStackLocation就是指向它的指針,會對驅動程序調試有一些幫助。

I/O堆棧

任何內核模式程序在創建一個IRP時,同時還創建了一個與之關聯的IO_STACK_LOCATION結構數組:數組中的每個堆棧單元都對應一個將處理該IRP的驅動程序,另外還有一個堆棧單元供IRP的創建者使用(見圖5-3)。堆棧單元中包含該IRP的類型代碼和參數信息以及完成函數的地址。圖5-4顯示了堆棧單元的結構。

圖5-3. 驅動程序和I/O堆棧之間的平行關係

注意
我將在本章稍後處討論IRP的創建機制。將幫助你瞭解DEVICE_OBJECT的StackSize域,該域指出IRP應爲其目標設備驅動程序保留多少堆棧單元。

圖5-4. I/O堆棧單元數據結構

MajorFunction(UCHAR)是該IRP的主功能碼。這個代碼應該爲類似IRP_MJ_READ一樣的值,並與驅動程序對象中MajorFunction表的某個派遣函數指針相對應。如果該代碼存在於某個特殊驅動程序的I/O堆棧單元中,它有可能一開始是,例如IRP_MJ_READ,而後被驅動程序轉換成其它代碼,並沿着驅動程序堆棧發送到低層驅動程序。我將在第十一章(USB總線)中舉一個這樣的例子,USB驅動程序把標準的讀或寫請求轉換成內部控制操作,以便向USB總線驅動程序提交請求。

MinorFunction(UCHAR)是該IRP的副功能碼。它進一步指出該IRP屬於哪個主功能類。例如,IRP_MJ_PNP請求就有約一打的副功能碼,如IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE,等等。

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函數自動設置,其值來自該函數的某個參數。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章