為什麼 Windows 環境下不能刪除被載入內存的磁碟文件?

如正在運行的程序、打開的文檔等。為什麼要設置這麼一種機制呢?其他操作系統上也是這樣的嗎?謝謝。


以上很多回答說了一堆問題,有關於鎖的,有關於一致性的,但我覺得沒有講到點子上。

各位都是程序員,自己到微軟官網上下一份SDK,裡面包含有文件系統的FAT驅動的源代碼,雖然是學慣用的,但我實驗過,跟官方發布的(XP版本)二進位文件基本相同。

現在我從源碼的角度來說說為什麼不行。

刪除一個文件的流程:

掛的驅動鉤子是IRP_MJ_SET_INFORMATION

刪除請求的調用棧一般情況下是:

FatFsdSetInformation-&>FatCommonSetInformation-&>FatSetDispositionInfo

刪除一個FAT分區上的文件,做法是給FCB(一個FAT的驅動內的數據結構)打上一個FCB_STATE_DELETE_ON_CLOSE,之後,當該文件的句柄都被關閉的時候,再從磁碟上刪除,具體負責刪除的函數是FatCommonCleanup。

現在看刪除檢測的過程:

在函數FatSetDispositionInfo的開頭部分有一個檢測函數MmFlushImageSection,凡是不刪除的文件大部分都卡在這裡,下面是代碼截圖。

這個MmFlushImageSection,百度一下,就能找到很多關於它的介紹,用法就是用來強行刪除的,有興趣的就自己百度,所有強行刪除文件的軟體都是通過在MmFlushImageSection上掛鉤子實現的。

那麼,這個為什麼要調用這個函數?在《Windows File System Internal》這本書里,大概描述了一下。可惜這書很難找到電子版,正版的也極少,我手頭的是一份掃描件,也不太好。大意是:Windows會對一部分打開的文件進行內存映射,具體就是FileObject結構里有一個PrivateCacheMap結構,這個結構就是用來做映射的,不管你打開的是一個EXE映射,還是一個普通的數據文件,只要PrivateCacheMap指針不是空的,那麼就是有映射,映射的好處就是讀寫一個文件的時候,底層驅動不必一次性的讀取全部文件內容,而是讀取一段數據做緩存,這樣效率最高(比沒有緩存要快,比全部做緩存性能要好)。

在Windows系統中,同時打開的文件可能很多,內存中會有很多的內存頁用於緩存文件,Windows把這些緩存頁全部串聯起來,由一個緩存管理器完成(CC MGR),這個緩存管理器,維護了一張表格,標識每個內存頁對應的文件緩存,假設我們允許繞過CC MGR直接刪文件,那麼這個文件對應的FileObject也會被刪掉,後果就是CC MGR里有一個內存頁指向的一個無效的FileObject,而且,由於這個文件處於打開狀態,假如打開它的那個進程仍然對這個文件讀寫,那麼CC MGR在試圖更新緩存的時候訪問一個無效的FileObject,然後就藍屏(BSOD)了。

之前回答的人里爭論的各種鎖,那是設計的問題,從本質上說是因為Windows緩存管理器必須讓所有緩存頁指向有效的文件對象(FileObject)。網上有很多強行刪除文件的工具,本質也無非是掛到MmFlushImageSection里,清理CC MGR里的引用,讓這個函數返回成功,然後刪除就沒有障礙了。

一旦這一步通過了,文件對象失效,即使再有任何讀寫請求下來,在文件系統一層檢查IRP的時候就會發現對象不存在,就直接返回錯誤了,保證後續的對這個已經刪除文件的讀寫不會被繼續執行。

從設計的角度上說,因為Windows緩存管理器設計成這樣了。文件系統驅動只能照做,為的只是保證內存映射的文件不出錯,而且內存映射這種東西,一旦一個操作系統內核設計成這樣,那麼它基本上不會改變,因為會影響整個OS的行為。


這個機制叫做 mandatory lock。本質上說,lock 都是 cooperative 的(就像多線程必須所有線程都檢查和加鎖才能保護)。但是如果在系統調用級彆強制加鎖,就沒有用戶態進程可以繞過,成為 mandatory lock。

UNIX 系統上沒有這種機制。在某些系統上(比如 OS X)的上層 API 有針對本層的 mandatory lock(比如 FileManager),但是可以被使用底層 API(比如 BSD C runtime)的程序繞過,所以不是真正的 mandatory lock。

Mandatory lock 有很多問題。但是也有些人認為它有用。比較負面的評價是 mandatory lock 解決的很多問題其實可以通過 cooperative lock 或者 file permission 來解決,而後兩者更靈活(比如同時寫入的問題基本上出現在同一種 app 上,完全可以用協作鎖)。而 Windows 上的 mandatory lock 實現的粒度也不夠細,是簡單互斥鎖而不是讀寫鎖,給很多 workflow 造成困難(比如查毒、用 dropbox 同步文件)。


各樓針對Windows討論了很多了,我來說說「其他操作系統」:

Linux可以刪掉使用中的程序/文檔而不造成問題。

當然你得用正常人使用的刪除方式,如shell中的rm或者圖形界面中用滑鼠刪。

Linux中的目錄項(你看到的一個個文件)都是指向inode的指針,系統會統計對inode的引用。只要有進程還用著這個文件,所對應的inode和區塊中的數據都依然保留。所以即使刪掉目錄項,進程還是能正確訪問到需要的數據。引用清零後,所對應的inode和存儲空間才會被釋放。

所以你可以在內核跑著的時候把內核刪了,或者更新驅動。


問題部分不成立,Modern 應用是可以的。

比如在8.1的自帶播放器里播放音樂時,可以移動或刪除音樂,然後播放器會停止播放並報錯。


回答樓主問題中「其他操作系統是不是這樣」

Linux不是這樣


LS兩位綜合起來就是:防止文件損壞和不可預料的讀取錯誤


對另外的4個答案感到失望,所以額外寫一個答案,希望至少有點拋磚引玉的作用。

載入內存的磁碟上的內容

提問中這一句是一個誤解。請容我詳細解釋一下。首先,從編程人員的角度來說,這是一個內核對象。沒錯,打開的文件在Windows中意味著一個內核對象。內核對象在用戶態程序中由一個句柄表示,而每個句柄都歸屬於一個Windows進程。文件可以被多次打開,只要之前的文件句柄擁有者不排斥這一點,且每次打開文件都產生一個新的內核對象(不要將此內核對象與文件本身對應的內核對象混淆了)。擁有句柄的進程可以通過關閉句柄減少相應內核對象的引用計數,系統會在引用計數減至0時釋放內核對象。因此這裡是個關於內核對象的問題,而不是關於載入內存什麼的。

之所以Windows程序常常會有「文件在編輯狀態不可刪除」的行為,有許多原因,例如歷史的原因、場景導致的原因。例如微軟倡導以文檔為中心,其背後的技術就是複合文檔,而複合文檔在打開時是不可刪除的。這個行為主要是當時開發複合文檔的編程人員決定的,而沒有什麼複雜的原因。

UNIX系統以管道為核心,各種信息流常常以瞬時的為主,並且以小文件為主(而不是像複合文檔那樣在一個文件內包含一個目錄系統),因此更合理的行為是打開文件,讀入內容到內存,然後迅速關閉文件。因此給人感覺UNIX不存在這個問題。實際上Windows的情況在這方面和UNIX 差不多。

誤以為文件在打開狀態不可刪除的可以看看我的示例代碼:

http://codepad.org/WrcF2YbC

2012.11.13更新:

一些老的UNIX似乎可以在文件不該能被刪除的時候刪除:

自然那些設計(咳嗽)UNIX的混蛋們對複雜的文件版本化功能不感興趣,而這一功能就能救我的命。當然,那些混蛋也從未想到過對準備進行頁面扇出的文件加鎖,是不是?

來源:《UNIX痛恨手冊》 http://freebsd.chinaunix.net/doc/2005/06/17/1121242.shtml


大體看了一下樓上的回答 bombless 的回答是正確的。在Windows中文件對象也是一種內核對象,簡單來說每個對象都有個引用計數,一個文件每當被其他程序打開一次,這個文件的引用計數可能增加1個,要刪除一個文件,那麼需要這個文件對象的引用計數為0.舉個例子,word打開一個文件,它不會關閉這個文件的句柄,那麼這個文件對象的引用計數不為0,因此不能刪除文件;但notepad++打開一個文件,載入內存後會隨之關閉這個句柄,那麼引用計數為0,就可以刪除這個文件。這也是應用層強刪文件這類工具的原理,如軟媒魔方裡面的一個工具。你用sysinternals的procexp可以搜索一個文件被哪些進程打開,可以觀察一個文件的引用計數以及更改。


禁止刪除打開的文件是順便為之,文件的打開狀態給了禁止刪除一個理由。

好比程序a打開了一個文件,產生了一個句柄,此時如果允許刪除文件的話,那麼如果程序a或b通過老句柄複製了一個句柄,那麼將讀取不到任何內容。所以,既然文件打開了,老子(操作系統)就禁止刪除這個文件。


推薦閱讀:

學3d max 要有什麼基礎?
為什麼反病毒軟體對比瀏覽器等軟體反而更容易被獲得漏洞並利用?
Instagram 為何不使用下拉更新而用按鈕更新?
哪個 App 整體都挺好,但卻因為一個小細節讓你特別討厭?
是否geek精神妨礙了編碼?

TAG:MicrosoftWindows | 操作系統 | 軟體設計 |