正確的退出要求線程從wait函數返回后執行CancelIo操作取消掉你的IRP,這需要驅動配合在IRP里設置CancelRoutine。假如沒有CancelRoutine,那么CancelIo操作是失敗的,前面講的那些恐怖故事還是會發生。關于這一點,我打算下次再說。

當一個IRP的CancelRoutine沒有被設置時,CancelIo操作會失敗,系統中有可能會留下永遠都不會被complete的IRP。在Threaded IRP和non-threaded IRP一節中我們有談到irp分為線程相關和非線程相關兩種。倘若一個永遠不complete的irp是非線程相關的,情況會稍微好一點,頂多系統中泄露了一個資源。倘若該irp是線程相關的,那事情就大了。thread IRP由IoManager生成并保留在線程的IRP隊列里,負責處理該IRP的驅動在收到下層驅動的Complete事件后不會主動收回IRP的資源而是繼續complete給IoManager,由IoManager負責回收,并從線程IRP列表中刪除該IRP。一個線程在退出前會遍歷等待IRP隊列里所有的IRP,直到它們全部被complete為止。倘若其中有一個irp永遠不complete,那么線程就永遠不退出,無論是ExitThread也好還是_endthreadex也好還是什么邪惡的暴力擦除數據強退也好,全都不頂用。線程不退出,進程也不能銷毀(題外話:進程資源的回收動作由最后一個線程退出后發起,所謂的殺進程,其實是用apc給所有線程發起退出操作)。更糟糕的是,操作系統的關機過程都會被堵住,除了關電源,沒有其他辦法恢復,這一點簡直比BSOD還糟糕。我們知道由user mode發起的IO操作最后都會翻譯成threaded irp,這就是為什么我在7.1大談特談user mode線程的原因:這個陷阱連user mode程序也會掉進去。Bad dog!
要解決這一點方法很簡單目標很明確,那就是防止“永遠不complete的irp”這種東西出現。一般的做法是加個線程或者timer并設置超時時間,時間一到就cancel這個irp。如果irp由user mode程序發起,那么就調用CancelIo;如果irp由驅動發起,則是調用IoCancelIrp。所有這些動作要生效的大前提是你的irp有CancelRoutine的存在,否則一切都是白搭。所以這里我有個經驗要跟大家分享:任何時候都給你的irp設置CancelRoutine,并在CancelRoutine里Complete你的IRP!為方便起見我們選non-threaded irp做個例子,所有的代碼都在內核態,免得各位看官看示例代碼還要做上下文切換。


通過實驗,我現在已基本搞清了原因. 與大家交流

1.普通WIN32用戶態程序與驅動程序交互:
(1).當用戶程序打開設備時, IO管理器向驅動程序發IRP_MJ_CREATE.
(2) 用戶程序退出時,不論此時是否有未完成的IRP, IO管理器都會首先向驅動發IRP_MJ_CLEANUP.
(3) 如果在發送了IRP_MJ_CLEANUP之后,仍有未完成的IRP(例如驅動程序未提供CleanUp例程或在CleanUp例程中未清除所有未完成的IRP,就會造成這種情況), 則IO管理器會依次調用這些IRP的OnCancel例程(如果驅動程序既未提供CleanUp例程又未掛接Cancel例程就糟了,這些IRP將無法得到清除).最后,IO管理器向驅動發IRP_MJ_CLOSE.

2.上層驅動程序與下層驅動程序的交互.
(1).當上層驅動調用IoGetDeviceObjectPointer()時,該函數會向下層驅動發送IRP_MJ_CREATE和IRP_MJ_CLEANUP.
(2).當上層驅動調用ObDereferenceObject()時,該函數只會向下層驅動發送IRP_MJ_CLOSE.

3.故障原因:
我的上層驅動在Unload例程中調用了ObDereferenceObject(), 但是由于仍有未完成的IRP,因此即使我的上層驅動卸載了,但這些IRP仍然留在下層驅動程序的隊列中未得到清除,以后當下層驅動一旦去完成這些IRP時就會造成BUG_CHECK, 因為IRP中的FileObject域指的文件對象已不存在.




dazzy 2001-07-27 08:45
分析透徹!一般的驅動都要注冊并實現CLEANUP的dispatch.