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

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


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

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

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

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




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