Windows NT如何處理I/O完成

以前我們描述了在內核中如何“創建你的IRPs”(roll your own IRPs)來執行I/O操作。這是一個很強大的技術,對於那些編寫設備驅動程序,文件系統驅動程序和過濾器驅動程序的人是非常有用的。這次我們討論I/O管理器如何完成IRPs。
雖然看起來是個簡單的話題,但是它包含了許多細微的主題。如果你沒有完全理解I/O完成,你的驅動程序會面臨危險。例如,回顧在編寫驅動程序代碼時我們經常遇到的一個問題是設置和清除I/O完成例程。理解I/O管理器如何執行I/O完成處理會使你明白爲什麼I/O完成例程必須設置成這個樣子。
在這篇文章中我們描述與I/O完成處理相關的I/O棧,當調用低級驅動程序時如何設置I/O棧,I/O管理器如何爲完成處理解開I/O棧,以及應該如何設置你的I/O完成例程使其能夠適當地工作。
 
I/O棧
圖一是一個有三個棧單元的I/O棧,同時顯示了每個棧單元(I/O Stack Location,有的驅動程序資料上翻譯爲棧位置)內的I/O完成例程的內容。

棧單元是從上到下使用的(例如,I/O棧在內存中是向下增長的)。你可以在I/O管理器的實現函數IoSetNextIrpStackLocation(在ntddk.h中的一個宏)中看到這一點:
#define IoSetNextIrpStackLocation( Irp ) { /
   (Irp)->CurrentLocation--; /
   (Irp)->Tail.Overlay.CurrentStackLocation--; }
當IRP首次被創建時,當前IRP棧單元是無效的,調用IoGetCurrentIrpStackLocation()函數返回一個指向IRP結構內部的指針。但是,通過使用IoGetNextIrpStackLocation()函數獲取的下一個IRP棧單元則是有效的,事實上第二個IRP棧單元在爲第一個被調用的驅動程序設置參數時用到(設置第一個I/O棧單元的機制在"Building IRPs to Perform File I/O" , The NT Insider V4N1中已經討論過了)

爲I/O完成設置I/O棧
當處理一個IRP時要做的一件事是建立完成例程。完成例程是一段簡單的程序,只要由IRP描述的I/O完成了,那麼下一個低級驅動程序就會調用這個完成例程。一個完成例程可以在以下I/O請求時指派:成功,錯誤,取消,或者它們的組合。特定IRP的完成例程由IoSetCompletionRoutine()函數建立。如果你的驅動程序不希望在I/O完成上收到通知,那麼在將IRP傳遞給下一個低級驅動程序之前可以指明。可以使用下面的代碼:
IoSetCompletionRoutine(Irp, NULL, NULL, FALSE, FALSE, FALSE);
IoSetCompletionRoutine() 是一個宏,它在ntddk.h中定義:
#define IoSetCompletionRoutine( Irp, Routine, CompletionContext, Success, Error, Cancel ) {
         PIO_STACK_LOCATION irpSp;
       ASSERT( (Success) | (Error) | (Cancel) ? (Routine) != NULL : TRUE );
       irpSp = IoGetNextIrpStackLocation( (Irp) );
       irpSp->CompletionRoutine = (Routine);
       irpSp->Context = (CompletionContext);
       irpSp->Control = 0;
       if ((Success)) { irpSp->Control = SL_INVOKE_ON_SUCCESS; }
       if ((Error)) { irpSp->Control |= SL_INVOKE_ON_ERROR; }
       if ((Cancel)) { irpSp->Control |= SL_INVOKE_ON_CANCEL; } }
Figure 2
一個常見的使用完成例程的地方是驅動程序創建了自己的IRP池。這時完成例程陷入這個IRP並把它返回到驅動程序的私有IRP池,然後給I/O管理器返回一個STATUS_MORE_PROCESSING_REQUIRED。這使得I/O管理器立即中止處理該IRP的完成並把它留給驅動程序處理。
圖一中關於I/O完成例程需要注意的是驅動程序的完成例程並不在它的I/O棧單元內,而是在下一個驅動程序的I/O棧單元內。爲什麼要這樣設置?其中一個原因是,最後一個驅動程序並不需要I/O完成例程。畢竟,它是最後執行I/O完成的驅動程序(通過調用IoCompleteRequest()函數)。因此不需要I/O管理器通知最底層驅動程序I/O已經完成了。
另一個原因是IRP的初始化。爲IRP分配初始內存的內核組件,例如驅動程序,不需要I/O棧單元。但是,它可能需要I/O完成例程(例如,以便IRP返回到它的私有池中)。回想一下,在調用鏈中最底層的驅動程序不需要I/O完成例程但是需要I/O棧單元。這樣,爲了空間效率,第N-1驅動程序的I/O完成例程存儲在第N個驅動程序的I/O棧單元中。
因爲I/O管理器自身不使用I/O完成例程,所以,如果你正在開發最高層驅動程序的話,決不會在你的I/O棧單元中看到完成例程。但是有些系統組件創建自己的IRPs並把它們傳遞給最高層驅動程序來來指示出它們有自己的完成例程。例如SRV(局域網文件管理服務器)和NTVDM(MS-DOS模擬層)它們都建立自己的IRPs並把它們傳遞給最高層驅動程序。這些組件也把自己的完成例程設置到它們所創建的IRPs中。
對I/O棧中完成例程的一個常見誤解出現在當你的驅動程序把IRP傳遞給接下來的驅動程序時。圖三解釋了這種錯誤,它簡單地把當前I/O棧單元複製到下一個I/O棧單元中。問題是複製當前I/O棧單元到下一個I/O棧單元的同時也複製了I/O完成例程。只要當前I/O棧單元內沒有完成例程的話,就可以不出問題。如果提供了完成例程,就會出現兩個不同的I/O棧單元,很明顯完成例程會被調用兩次。這通常導致藍屏或其他使系統不穩定的災難性結果。當然可以編寫代碼來處理這種情況,使其能夠正常工作。但是很少有驅動程序編寫者能夠預見到完成例程會被正確的調用。
PIO_STACK_LOCATION IrpSp;
PIO_STACK_LOCATION NextIrpSp;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
NextIrpSp = IoGetNextIrpStackLocation(Irp);
*NextIrpSp = *IrpSp;
return IoCallDriver(NextDeviceObject, Irp);
Figure 3
對於文件系統過濾器驅動程序,開始過濾由SRV服務的驅動程序時經常首先出現這種問題。SRV提供自己的I/O完成例程而且它不能防止它的完成例程被其它驅動程序的I/O棧單元調用。解決這個問題的辦法是,在你的驅動程序拷貝I/O棧單元之後一定要確保調用IoSetCompletionRoutine()函數。
當然,儘管你正確地編寫了過濾器驅動程序,但仍然可能存在問題。你可以看到該問題的一個曾經的例子是 NT4.0的NTFS文件系統。與近期的SP2一樣,NTFS文件系統在拷貝I/O棧時處理IRP_MJ_DEVICE_CONTROL過程中沒有清除完成例程。這樣就會出現我們的完成例程被調用兩次的情況(一次是由NTFS文件系統的設備對象調用的)。
解決該問題的辦法是直截了當的。在你的設備對象擴展區中你應該保留一些識別標記:一個惟一值,你可以利用它來分辨出傳遞給你的完成例程的是你的設備對象而不是其它驅動程序的。如果你的完成例程由其它驅動程序的設備對象調用了,你可以簡單地模擬I/O管理器使其認爲不存在完成例程。如圖四示出的代碼證明了這一點。現在,如果你的完成例程被其它驅動程序的設備對象調用了,你的代碼也能正常工作。
if(!DeviceObject>Extension||(POUR_DEVICE_EXTENSION)(DeviceObject>ExtensionMagicNumber)!=OUR_EXTENSION_MAGIC_NUMBER) {
   if (Irp->PendingReturned) {
       IoMarkIrpPending(Irp);
   }
   return STATUS_SUCCESS;
}
Figure 4
有一個雖然不常見但令人有點畏懼的問題是我們最近在NT 4.0 SP2的以前版本中,使用IRP_MJ_CLOSE的CDFS沒有適當地完成中發現的。由於I/O管理器的處理機制的原因,似乎可以正常工作,即使是Checked Build版本(這就是爲什麼在短短的時間內微軟對其做了更多的修復的原因)。
但是,對過濾器驅動程序,此問題所表現出的症狀是過濾驅動程序的完成例程沒有被調用。雖然在SP2中修復了該問題,但對於那些必須使他們的產品支持NT4.0的早期版本或者希望在將來再出現此類問題時能夠正確地解決它的人來說,需要依靠I/O完成處理來解決。圖五示出的代碼是偵測和對付這種問題的辦法,它的主幹代碼會與隨後圖六所示的設置事件同步對象的完成例程相結合。
RtlCopyMemory(NextIrpStack, CurrentIrpStack, sizeof(IO_STACK_LOCATION));
KeInitializeEvent(&Event, NotificationEvent, FALSE);
IoSetCompletionRoutine(Irp,&OurCloseCompletion,&Event,TRUE,TRUE,TRUE);
IoMarkIrpPending(Irp);
status = IoCallDriver(Extension->FilteredDeviceObject, Irp);
if (status != STATUS_PENDING) {
           if (KeReadStateEvent(&Event) == 0) {
                       IoCompleteRequest(Irp, IO_NO_INCREMENT);
           }
} else {
       KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, 0);
}
return STATUS_PENDING;
Figure 5
OurCloseCompletion(PDEVICE_OBJECT Device Object, PIRP Irp, PVOID Context)
{
Event = (PKEVENT)Context;
KeSetEvent(Event, 0, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
Figure 6
問題是CDFS返回了STATUS_SUCCESS而沒有調用IoCompleteRequest()函數。其實,這證明了對於大部分的I/O操作如果在它的分發例程中返回了除STATUS_PENDING之外的任何值,被分發的IRP就會由I/O管理器完成。當然,你決不會自己寫代碼做這樣的事情。沒有調用IoCompleteRequest()函數就完成了的IRP在驅動程序中是一種邏輯錯誤,它會給更高層的驅動程序帶來一些令人討厭的問題。
在除了返回STATUS_PENDING之外的所有情況中已經完成的IRPs會被傳送回來,因爲底層驅動程序已經調用了IoCompleteRequest()函數,它依次調用了你的驅動程序中的完成例程。這引領我們進入I/O管理器最複雜的部分——理解I/O管理器如何處理I/O完成。
 
I/O管理器的角色
I/O管理器處理I/O完成分爲兩個不同的階段。第一個階段是獨立於線程上下文的,這意味着在該階段的處理過程中I/O管理器可以在任何線程上下文中執行。第二個階段是依賴線程上下文的,這意味着在該階段的處理過程中I/O管理器必須在特定線程上下文中執行,在這裏這個特定線程是最初提出I/O請求的線程。
在處理I/O完成的第一個階段中,IRP被解開並通知每個驅動程序的完成例程I/O操作已經完成。在該階段處理期間,任何提供了完成例程的驅動程序都會被I/O管理器回調,並使這些驅動程序獲得察看IRP的第二次機會。
在第二階段處理中,把從IRP中獲得的信息拷貝到線程的用戶空間內存緩衝區中。這樣,必須保證該操作在正確的進程上下文中完成。當然,儘管大多數數據拷貝調用操作不是必須的,但是象直接I/O這樣的情況仍然需要拷貝數據。一旦完成拷貝操作,該IRP就會被拆除和丟棄,這包括與該IRP相關的MDL以及最終存儲IRP自身數據的內存也會被回收。
當驅動程序調用IoCompleteRequest()函數時,I/O完成處理的第一個階段便完成了。注意,IoCompleteRequest()函數是個void型函數,所以調用者不需要爲I/O完成提供任何額外的信息。事實上,調用者甚至不知道第一階段的I/O完成操作是否已經完成了,因爲高層驅動程序可能返回
STATUS_MORE_PROCESSING_REQUIRED,它暫停了I/O完成第一階段的進一步處理。
I/O完成的第二階段可以在調用IoCompleteRequest()之後的任意時間點上發生。這可能意味着I/O完成的第二階段在IoCompleteRequest()返回之時完成,或者甚至在它返回之後過一段時間才完成,這取決於當前系統的負載。
實際的順序取決於最後一個完成例程被調用之後IRP的狀態。這時,I/O管理器通過查看IRP來檢查最高層驅動程序是否已經返回了STATUS_PENDING(或者將要返回STATUS_PENDING,因爲此時我們不知道確切的操作順序),然後I/O管理器通過排隊一個異步過程調用(APC)來執行I/O完成的第二階段。I/O管理器是通過查看IRP的PendingReturned字段來執行該檢查的。
這裏有一個有意思的地方。如果最高層驅動程序沒有返回 STATUS_PENDING,那麼I/O管理器只是簡單地把控制權轉交給調用者(例如,返回到調用IoCompleteRequest()函數的驅動程序)。回想一下該階段的處理過程,就會發現調用IoCompleteRequest()函數的驅動程序對IRP的狀態信息沒有任何意識。
那麼在這種情況下I/O完成的第二階段是在何處發生的呢?在圖七中,我們給出了在這種情況下(同步I/O情況)控制轉移流程圖。

在對I/O管理器作進一步討論之前,最後複習一下以下這些步驟:
1.應用程序向I/O管理器發出I/O操作請求(讀、寫等操作)。
2.I/O管理器通過建立一個IRP來分發I/O請求並把它傳遞給調用鏈中第一個驅動程序。
3.第一個驅動程序通過向第二個驅動程序發出I/O請求以便繼續處理。
4.第二個驅動程序通過向第三個驅動程序發出I/O請求以便繼續處理。
5.第三個驅動程序完成I/O操作。它調用IoCompleteRequest()函數並開始執行I/O完成的第一階段。
6.在第一階段執行期間,第二個驅動程序的完成例程(如果有的話)會被回調。
7.在第一階段執行期間,第一個驅動程序的完成例程(如果有的話)會被回調。
8.接下來,IoCompleteRequest()函數會把控制權返回給第三個驅動程序。I/O完成的第一階段處理結束。
9.第三個驅動程序把一個狀態值(例如,SUCCESS)返回給第二個驅動程序。
10.第二個驅動程序把一個狀態值返回給第一個驅動程序。
11.第一個驅動程序把一個狀態值返回給I/O管理器。
12.I/O管理器注意到必須執行I/O完成的第二階段,並開始執行。
13.第二階段I/O完成例程返回I/O操作的狀態信息並把數據拷貝到應用程序的地址空間中。一旦完成了這些操作,I/O管理器拆除IRP並將其丟棄。
14.第二階段I/O完成例程把控制權返回給I/O管理器。
15.現在,I/O操作已經完成,I/O管理器把控制權返回給調用者。
該圖中一個有意思的地方是第12步,I/O管理器開始執行I/O完成的第二階段。這可以正確的執行,因爲調用是被同步地完成的,而且I/O管理器“知道”是在正確的線程上下文中執行以及I/O完成的第一階段處理已經結束了。
回想一下我們剛纔討論過的NT4.0中的CDFS的問題,這種特殊情況是CDFS調用IoCompleteRequest()失敗但它的分發函數卻返回了STATUS_SUCCESS。它可以正確執行的原因是I/O管理器執行了上面所討論的兩階段I/O完成處理,這可以確保信息傳回給應用程序同時I/O管理器解除了IRP。當然,任何中間層驅動程序決不會知道I/O完成了,也不會執行第一階段的處理操作(圖中步驟5到8)。
對於異步I/O情況,這些步驟的發生順序很不一樣。如圖八所示。

下面是每個步驟地描述:
1.應用程序向I/O管理器發出I/O操作請求(讀、寫等操作)。
2.I/O管理器通過建立一個IRP來分發I/O請求並把它傳遞給調用鏈中第一個驅動程序。
3.第一個驅動程序通過向第二個驅動程序發出I/O請求以便繼續處理。
4.第二個驅動程序通過向第三個驅動程序發出I/O請求以便繼續處理。
5.第三個驅動程序完成I/O操作。它調用IoCompleteRequest()函數並開始執行I/O完成的第一階段。
6.在第一階段執行期間,第二個驅動程序的完成例程(如果有的話)會被回調。
7.在第一階段執行期間,第一個驅動程序的完成例程(如果有的話)會被回調。
8.接下來,IoCompleteRequest()函數排隊一個APC來執行I/O完成的第二階段。第一階段結束。
9.I/O完成APC被交付,I/O完成第二階段開始。注意,這是在引起I/O操作的線程上下文中完成的。
10.拷貝I/O狀態和數據到用戶地址空間。
11.接着,IoCompleteRequest()函數返回到第三個驅動程序。
12.第三個驅動程序把一個狀態值(例如,SUCCESS 或 PENDING)返回各第二個驅動程序。
13.第二個驅動程序把一個狀態值返回給第一個驅動程序。
14.第一個驅動程序把一個狀態值返回給I/O管理器。
15.現在,I/O操作已經完成,I/O管理器把控制權返回給調用者。
對於這種情況(異步I/O),I/O完成處理的第二階段(步驟9到10)相對於控制權從IoCompleteRequest()返回到應用程序(步驟11到14)所執行的操作,可以以任意順序執行。
實際返回到應用程序(步驟15)的操作可能被I/O管理器排序了,例如這種情況,即應用程序請求同步I/O。當然,I/O管理器是以跟Windows NT內核模式代碼同樣的方式執行該操作的——通過等待一個事件對象(這裏,該事件對象是針對IRP的)。當第二階段完成時,該事件對象被設置成有信號狀態,I/O操作的狀態就會被返回給應用程序(步驟15)。
也要注意驅動程序到驅動程序的調用,以及接下來的返回步驟,可能是異步執行的。這時,如果調用鏈中第一個驅動程序希望將I/O請求排隊並異步地處理它,那麼它就會給I/O管理器返回一個STATUS_PENDING(步驟14)。
如果調用者不要求同步,那麼在步驟15中I/O管理器就會把控制權返還給調用者,表示I/O正在處理。在I/O完成處理的第二階段期間,完成I/O後操作的狀態值被拷貝回適當的地址空間中。接着應用程序通過使用一個現有機制,例如,APC例程,在特定事件上等待,或者使用輪詢來檢查I/O是否完成了。在這期間,最初引起I/O請求的線程可以繼續執行,甚至開始執行另外的I/O操作。

設置你的完成例程
因爲在特定類型的驅動程序中使用I/O完成例程是非常常見的,理解Window NT如何處理I/O完成的方式可以幫助開發者編寫更健壯的驅動程序,以及定位驅動程序的錯誤。一個常見的例子是我們的代碼無數次地在一個文件系統中使用 IRP_MJ_CREATE調用。一個過濾器驅動程序的新手寫出如圖九所示的代碼,想要在他們的完成例程中做像圖十所示的事情:
DbgPrint("Createfor%Z/n",&IrpSp>FileObject>FileName;
return IoCallDriver(NextDeviceObject, Irp);
Figure 9
// Build a work item for additional processing
ExQueueWorkItem(&WorkItem);
return STATUS_MORE_PROCESSING_REQUIRED;
Figure 10
通過閱讀DDK文檔可以使你滿懷信心地認爲它會正常地工作。事實證明並不是這樣的。I/O管理器把 IRP_MJ_CREATE看作的同步I/O操作(回想一下圖七中控制流程圖)。事實上你的完成例程返回的是STATUS_MORE_PROCESSING_REQUIRED(步驟7)而沒有得到返回到最底層驅動程序的通知
(IoCompleteRequest()是一個void型函數,所以它不給調用者返回任何東西)。但是,它把創建操作的結果依次返回給I/O管理器的調用驅動程序。I/O管理器推測出由於這是同步I/O的情況而且返回值不是STATUS_PENDING,必須執行I/O完成的第二階段。
這有個例外——如果你的驅動程序的工作例程已經完成了,一切都會順利進行,因爲在你的工作例程中調用了IoCompleteRequest()函數。如果你的驅動程序的工作例程正在處理中你卻調用了IoCompleteRequest(),那麼工作例程的處理就不會完成,這將導致系統崩潰並使用MULTIPLE_IRP_COMPLETIONS標誌藍屏死機。
所以從完成例程中返回STATUS_MORE_ PROCESSING_REQUIRED是不夠的。此外在分發例程的入口點中還必須做以下兩件事中的一個:
A.或者返回STATUS_PENDING或者
B.使用同步I/O請求
這樣做可以足夠保證你的完成例程安全地返回STATUS_MORE_PROCESSING_REQUIRED。如果驅動程序返回了STATUS_PENDING(情況A)那麼代碼應該類似於圖十一:
DbgPrint("Createfor%Z/n",&IrpSp->FileObject->FileName);
IoMarkIrpPending(Irp);
(void) IoCallDriver(NextDeviceObject, Irp);
return STATUS_PENDING;
Figure 11

回想一下上面我們提到的,如果最高層驅動程序返回了STATUS_PENDING,那麼I/O管理器排隊一個APC來執行I/O完成的第二階段。但是,當I/O管理器檢查STATUS_PENDING是否已經返回了以及STATUS_PENDING實際上是否真的從驅動程序返回了,在這兩者之間並沒有固定的順序。事實上
,我們所知道的是STATUS_PENDING在某個時間點上(過去,或者將來)從最高層驅動程序返回。
這個信息(最高層驅動程序會返回STATUS_PENDING)存儲在該驅動程序的I/O棧單元內。它由 SL_PENDING_RETURNED位表示,存儲在控制字段中。在你的驅動程序中通過調用IoMarkIrpPending()函數設置該位。I/O管理器輪流處理每個棧單元時,它通過設置Irp->PendingReturned字段來指示出SL_PENDING_RETURNED位是否已在下一個低層驅動程序的控制字段中設置了。調用最後一個驅動程序的完成例程後,I/O管理器使用在
Irp->PendingReturned字段中的信息來決定是否需要爲I/O完成的第二階段排隊一個APC,這就是爲什麼你的驅動程序必須同時調用IoMarkIrpPending()並返回STATUS_PENDING的原因。如果你的驅動程序只做了其中的一件事,那麼系統就會出現行爲異常。
如果驅動程序使用同步I/O方式(情況B),那麼就像圖十二所示的代碼一樣。驅動程序的完成例程或者被完成例程排隊的工作例程設置一個事件(通常它作爲一個上下文信息傳遞給完成例程)。
DbgPrint("Create for %Z/n", &IrpSp->FileObject->FileName);
Status = IoCallDriver(NextDeviceObject, Irp);
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, 0);
Figure 12
當做同步I/O(情況B)的時,這是最簡單的選擇,它不能用於所有的情況,因爲它干擾了異步I/O請求操作,會導致一系列的異步I/O操作發生。這些異步I/O操作的其中之一,將引起試圖做同步I/O操作的暫停。
同樣,像情況A那樣做異步I/O會引起某個異步I/O不正確,因爲它們把返回值STATUS_PENDING當作一個成功標誌了。一個例子是文件系統中的“oplocks”。這個操作鎖把STATUS_PENDING當作對該鎖的一個授權。不幸地是,如果驅動程序(例如文件系統過濾器驅動程序)返回了STATUS_PENDING,這將引起高速緩存的一致性問題以及網絡客戶端的數據過期。
我們以前提到一個注意事項,而這裏的第二個注意事項是,如果你的驅動程序返回了STATUS_PENDING就必須也調用IoMarkIrpPending()。因此,考慮一個驅動程序如果這樣做:
return IoCallDriver(NextDeviceObject, Irp);
那麼,如果下面的驅動程序返回了STATUS_PENDING,這個驅動程序就必須調用IoMarkIrpPending()函數。接着許多程序員這樣寫:
Status = IoCallDriver(NextDeviceObject, Irp);
if (Status == STATUS_PENDING) IoMarkIrpPending(Irp);
return Sta
這可以正常執行——大多數情況下。但有時這會引起系統崩潰、線程掛起或者其他令人討厭的行爲。記住,一旦你把IRP傳遞給下一個驅動程序(通過IoCallDriver()函數)該IRP就像消失了一樣。因爲IoCallDriver()返回到驅動程序後並不知道I/O請求的狀態。但是,你能做的是獲得第二次察看IRP的機會——在你的完成例程中。
因此,你所能做的,而不像上面代碼那樣,就是修改你的完成例程,就像這樣:
if (Irp->PendingReturned) IoMarkIrpPending(Irp);
這表示如果你的驅動程序沒有完成例程,那麼I/O管理器會爲你完成。但是,一旦提供了自己的完成例程I/O管理器不會接手干預,這樣你可以做任何想要做的——包括做錯!
總結
現在,你可以問問自己,提供自己的I/O完成例程卻帶來這麼多的麻煩是否值得。答案是肯定的。通過使用Windows NT爲同步和異步I/O提供豐富的操作方式,你的驅動程序可以監視和處理與I/O相關的各種事件。例如,能夠處理錯誤情況對於建立軟件RAID解決方案(例如,FTDISK)是非常重要的。這樣,驅動程序可以顯示地從I/O錯誤中恢復。對於過濾器驅動程序,使用完成例程可以使正在處理的完成狀態與最初提出I/O請時的相匹配。
爲了總結本文所討論的主題,我寫了一個“規則”清單,在你把IRP傳遞給另一個驅動程序時都可以遵照它來執行:
1.總是設置你自己的I/O完成例程。如果你不想得到I/O完成的通知,至少你應該使用 IoSetCompletionRoutine(Irp, NULL, NULL, FALSE, FALSE, FALSE);把它清除掉。
2.如果你提供了I/O完成例程,你的責任就是給調用鏈中的驅動程序傳遞迴一個IRP的未決狀態。這意味着如果你的驅動程序返回了STATUS_PENDING,那麼你必須也得把IRP標示爲未決的。
3.你的完成例程可以在IRQL <= DISPATCH_LEVEL時被調用。這時你必須或者執行可在DISPATCH_LEVEL下執行的操作,或者使用一個工作線程(運行在PASSIVE_LEVEL)。
4.如果你編寫的是最底層驅動程序,通過調用IoCompleteRequest()函數來完成適當的請求。否則,你會引起高層驅動程序的問題。

轉自http://gameboytxt.spaces.live.com/Blog/cns!C792824C7D49C4E4!1505.entry

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