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

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


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

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

2.上層驅(qū)動程序與下層驅(qū)動程序的交互.
(1).當上層驅(qū)動調(diào)用IoGetDeviceObjectPointer()時,該函數(shù)會向下層驅(qū)動發(fā)送IRP_MJ_CREATE和IRP_MJ_CLEANUP.
(2).當上層驅(qū)動調(diào)用ObDereferenceObject()時,該函數(shù)只會向下層驅(qū)動發(fā)送IRP_MJ_CLOSE.

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




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