【梳理】簡明操作系統原理 第十四章 UNIX文件系統(內附文檔高清截圖)

參考教材:
Operating Systems: Three Easy Pieces
Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
在線閱讀:
http://pages.cs.wisc.edu/~remzi/OSTEP/
University of Wisconsin Madison 教授 Remzi Arpaci-Dusseau 認爲課本應該是免費的
————————————————————————————————————————
這是專業必修課《操作系統原理》的複習指引。
在本文的最後附有複習指導的高清截圖。需要掌握的概念在文檔截圖中以藍色標識,並用可讀性更好的字體顯示 Linux 命令和代碼。代碼部分語法高亮。
操作系統原理不是語言課,本複習指導對用到的編程語言的語法的講解也不會很細緻。如果不知道代碼中的一些關鍵字或函數的具體用法,你應該自行查找相關資料。

十四 UNIX文件系統

1、最早的UNIX文件系統的結構是這樣的:

S區域記錄了整個文件系統的基本信息,包括卷(volume)大小、索引節點數、指向空閒區域開頭的指針,等等。索引節點區包括全部文件的索引節點。數據區則記錄文件內容。
這個文件系統結構簡單,而且爲文件與目錄的層次性的實現打下了基礎。但是,這種結構的文件系統的性能很糟。因爲它並沒有考慮機械盤的結構,當磁盤讀取索引節點後,可能需要尋道至較遠的位置才能讀到文件。硬盤使用久了以後,還會產生許多碎片(fragment),使得性能問題雪上加霜。
產生碎片的機理有:
(1)硬盤儘可能在距離磁頭最近的空白位置開始寫入文件,寫入完畢後磁頭會懸停,不會主動移動到另一片空白區域。
(2)文件被刪除以後,釋放出來的空間不會與其它空閒空間合併。
(3)當一段空閒空間寫滿後,如果文件尚未寫入完畢,必須尋找另外的空閒空間繼續寫入。
可見,隨着磁盤的使用,磁盤碎片將會越來越多:

磁盤碎片整理(defragmentation)程序能夠將文件的多個不連續部分儘量合併,從而減少碎片、恢復磁盤性能。
此外,最初的文件系統的塊大小很小(512 Bytes),數據傳輸速率受到影響。

2、後來,Berkeley的一個研究小組研發了快速文件系統(Fast File System,FFS)。FFS充分考慮的磁盤的物理結構,以求儘量提升性能。FFS保持相關API(open()、read()、write()、close()等)不變,而大改內部結構。
FFS引發了文件系統研究的新紀元。事實上,所有的現代文件系統都保持接口的不變來確保兼容性,而在內部結構的改進上下功夫。

3、FFS將磁盤劃分爲大量的柱面(cylinder)。一個柱面指的是不同盤面上相同位置(離圓心的距離相同)的磁道的集合。若干個連續的柱面又歸爲一個柱面組。
當然,磁盤不會向文件系統透露太多的信息。文件系統不知道一個柱面到底怎樣被使用。磁盤只是簡單地將邏輯地址報告給文件系統,而隱去了盤片的幾何細節。因此,現代文件系統(例如ext2、ext3、ext4)將磁盤劃分爲塊組(block group)。一個塊組包含許多個塊,對應磁盤上的一段連續空間。如果兩個文件存放在同一個柱面組或塊組中,FFS就能保證依次訪問它們不用進行長時間的尋道。

4、每個柱面組的結構是這樣的:

與最初的UNIX文件系統一樣,S區爲文件系統的裝載所需。而且這個區域有多個備份,如果一個副本損壞了,由於還有其它副本,掛載和訪問文件系統不受影響。
在組內,FFS需要追蹤每個索引節點和數據塊。索引節點位映像(inode bitmap,ib)和數據位映像(data bitmap,db)負責這個功能。位映像是管理空閒空間的優秀方式,原因是:通過位映像,可以迅速找到一個大塊的空閒區域,並將其分配給一個文件。這種方法也許還可以避免在老式文件系統上的空閒列表本身的碎片問題。

5、以創建一個空文件爲例,我們來說明文件系統用到的數據結構如何更新。
雖然文件是空的,但是它依然需要佔用一個塊(一般爲4 KB)。由於是新創建的文件,所以需要一個新的索引節點。索引節點區和索引節點位映像區都需要更新。文件本身也有一些數據,這些寫入數據區,數據位映像也要修改。到這裏爲止,創建一個文件需要在所在的柱面組進行4次寫入(這些寫入操作可能先存入緩衝區裏)。
光是這些還沒完,當創建新文件時,必須將其寫入正確的層級,即目錄樹需要正確更新。文件所在的目錄中,必須添加新文件的相關信息;這個更新可能在父目錄所在的數據塊內就可以完成,也可能需要申請新的數據塊,並更新位映像。父目錄的索引節點也要更新,因爲目錄的長度和修改日期都改變了。

6、FFS放置文件、目錄和元數據的原則是:將相關聯的信息儘量靠近。於是,具有相關關係的信息儘可能被放在同一個塊組;不具有相關性的內容則不放在同一塊組。爲了實現這個目標,FFS應用了一些啓發式算法。
對於目錄,FFS儘量找到一個已分配目錄較少、空閒的索引節點較多的柱面組,將目錄數據和索引節點放入。當然,也可以運用其它的算法(比如考慮空閒數據塊的數量)。
對於文件,FFS做兩件事。首先,(一般情況下)確保將數據塊和索引節點分配在同一組,避免訪問索引節點和數據時需要長時間尋道。其次,它將同一目錄下的所有文件都放在目錄所在的柱面組中。
以下是一種分配 /、/a、/b、/a/c、/a/d、/a/e、/b/f的方式:

與之相對的是將索引節點儘量分散的分配方式,這種方式使得一個柱面組的索引節點表不會很快被寫滿:

訪問一個目錄時,一併訪問目錄下的文件是很常見的:想一想編譯、鏈接的過程。一個目錄下的一些代碼文件被編譯,在同一目錄下生成若干目標文件,然後再把這些目標文件鏈接起來。因此,很多時候FFS都能提升性能,因爲在訪問具有相關性的文件時避免了要到很遠的位置去尋道。

7、在FFS中,如果存儲的文件比較大,那麼文件很容易填滿數據區,造成無法將相關聯的文件寫入同一個柱面組:

將大段連續文件打散成許多大塊,分散在不同的柱面組中,可以解決此問題:

這種思想稱爲均攤(amortization),在計算機科學中十分常見。不過,這種存儲方式會降低順序讀取的性能。如果需要減小性能削弱的幅度,就要把大塊的大小增加。
不過,FFS不使用這種方法,而是將前12個數據塊與索引節點放在同一個柱面組,每個塊都跟一個間接塊,一個間接塊指向一個不同的柱面組,在這個柱面組繼續存儲着這個大文件。
近年來,機械硬盤的容量增長得非常快,傳輸速率亦然;但是受到磁頭臂的制約尋道時間則進步緩慢,因此尋道時間在總的讀寫時間中的佔比持續上升。爲了減小尋道次數,兩次尋道之間要儘量傳輸更多的數據。

8、大量事實證明,計算機中的大多數文件都是小文件,而且不到2 KB。而塊大小通常不小於4 KB,於是存儲小文件時,大量的磁盤空間就被內部碎片浪費了。FFS引入了子塊(sub-block)解決此問題。在4 KB塊大小的基礎上,將其進一步劃分爲8個512 Bytes的子塊。如果文件或文件的一部分佔用空間不到4 KB,當文件增大但又沒有填滿一個塊時,文件系統就繼續分配子塊,直至用到塊內的最後一個子塊。
在前面的VSFS的討論中,相信你已經發現了:這樣做是很低效的,因爲大量的IO並沒有花費在數據讀寫本身。FFS修改了libc庫(C標準庫),使得通過緩存機制儘量將子塊的讀寫湊成塊。
在一條磁道上,如果將相鄰的扇區相鄰編號,那麼在順序讀取的時候會出現這樣的情況:例如讀取完扇區0之後準備讀取相鄰的扇區1,但是扇區1在讀取扇區0期間已經隨着盤片旋轉而移走了,於是要讀取扇區1只能再等盤片轉完差不多一圈。解決方案是將下圖左側的編號方式換成右側的:兩個相鄰編號的扇區之間至少隔開一個扇區。在劃分扇區時,FFS會根據磁盤的性能參數確定劃分的具體方式。

你可能在想:這樣子不是會降低性能嗎?畢竟在盤片旋轉一圈的時間內不是所有的時候都在讀取。不過,這個問題已經有了合適的對策:現代的磁盤會先一次性讀取整條磁道,放到磁盤緩存(磁道緩衝區)裏,然後把緩衝區中的內容按照扇區號遞增的順序整理好再傳送給DMA控制器。於是,文件系統就不用考慮這些低級層面的細節了。

9、FFS還是第一個能夠支持長文件名的文件系統。此外,FFS還引進了原子性的rename()操作。

10、與內存中的內容不同,對文件系統中的數據的持久性要求更高。在長時間的存放後,這些數據必須要保留下來;而斷電或系統故障發生後,文件系統也要儘量確保數據安全。
以在上一章的VSFS下爲一個文件追加4 KB內容爲例,如果寫入過程未完畢就發生了故障,那麼可以分爲以下六種情況:
(1)只寫入了數據區。從一致性的角度來看,這種情況沒有問題。但是寫入依然是失敗的,因爲沒有相應的索引節點和位映像說明數據已經在此處寫入。
(2)只寫入了索引節點。這種情況符合不一致性(inconsistency),因爲索引節點表明這裏發生了寫入,但是位映像卻表明這裏未寫入。如果在重新寫入之前就讀取了索引節點指向的位置,那麼就會讀到錯誤的數據(這個位置上存在的舊內容)。
(3)只寫入了位映像。這種情況同樣符合不一致性。如果不修復錯誤,就會導致空間泄漏:由於位映像標記此處爲已分配,因此這個塊將永遠不會被其它程序寫入。
(4)只寫入了索引節點和位映像。滿足一致性,但沒有真正把數據寫入數據區,所以一旦在修復前讀取了原先要寫入的塊,就會讀到錯誤的數據。
(5)只寫入了索引節點和數據塊。不滿足一致性,並且如果有程序申請分配新的磁盤空間,那麼這個數據塊寫入的數據可能會被覆蓋。
(6)只寫入了位映像和數據塊。不滿足一致性,並且我們不知道這一塊的數據屬於哪個文件,因爲沒有索引節點。

11、fsck(file system checker)是一個UNIX工具,用於查找文件系統中的不一致性並予以修復。其它系統也會有類似的工具(比如Windows下的chkdsk)。需要強調,這類工具並不能修復所有問題:例如上面所說的情況(2)和(4)。畢竟它們不知道你嘗試在一個塊寫入的數據到底是什麼。
fsck在被檢查的文件系統裝載前運行,這樣就沒有任何其它文件系統活動干擾檢查與修復。
·fsck首先檢查Superblock是否合理,比如已使用空間是否大於所有已分配塊的空間之和。當發現問題後,fsck用另一個副本替換出現問題的超級塊。
·接下來,fsck檢查索引節點和間接指針塊、二重間接指針塊等,確定哪些塊已被使用。這一步可以修正位映像的問題。
之後檢查的是索引節點的內部結構。如果索引節點的結構出現問題,那麼這個索引節點會被清除(索引節點位映像也要相應修改)。
·再之後檢查索引節點鏈接。fsck檢驗每個文件的全部鏈接是否都正確且與引用計數匹配。爲了驗證鏈接數量的正確性,fsck掃描整個目錄樹,並將目錄樹臨時重新構建一遍,比對引用計數是否不同。引用計數不正確時,將其修正;如果一個索引節點對應的目錄沒有找到,那麼它就被移動到lost+found文件夾。
·然後,fsck檢查是否有多個索引節點指向同一個塊。如果是,清除多餘的節點,或將指向的塊複製一個副本。
·下一步檢查壞塊。當一個指針指向它應該指向的範圍之外的內容,就被認爲已損壞,比如一個指針指示的位置比分區總大小還大。這種情況下,fsck不能做什麼,除了將錯誤的指針移除。
·最後是目錄檢查。fsck不知道用戶文件的內容,然而fsck知道系統創建的那部分記錄怎樣纔是正確的。fsck檢查每個目錄的記錄條目的完整性,比如.和…一定是前2項,每一項中記錄的索引節點都已分配,等等。
fsck有一個問題:它太慢了。當卷的容量很大時,運行一次fsck常常需要幾十分鐘乃至數小時。而且fsck執行起來的花銷比較大。比如如果只在寫入幾個塊的過程中斷電,爲了找出存在問題的部分,就要對整個分區運行一次fsck。就好像你在體育館掉了鑰匙,結果得找遍整個館。爲了解決fsck開銷過長的問題,研究人員與從業者研發了新的方案。

12、預寫入日誌(write-ahead logging),這個方案似乎是從DBMS那邊“偷”來的。在文件系統中,由於歷史原因,一般稱爲journaling。第一個引入日誌機制的文件系統是Cedar。Linux ext3、ext4,ReiserFS,IBM的JFS,SGI的XFS,Windows的NTFS都採用了這個機制。
預寫日誌的基本思想是:在寫入之前,先在日誌中寫入相應記錄。當寫入意外中止時,可以從日誌中找到相關信息,然後重試未完成的步驟。雖然記錄日誌會使得總的寫入量大一點,但是預寫日誌大大降低了重試寫入操作的成本。

13、接下來我們學習ext3的預寫日誌。ext3的許多結構和ext2一樣,都是把磁盤空間分成很多個組:

ext3多了一個區域專門用於預寫日誌(有時候可能將日誌寫入專門的文件,或者單獨的設備):

日誌記錄的結構是這樣的:

TxB即事務開始(transaction begin),TxE即事務結束(transaction end)。TxB包含了準備進行的對文件系統的更新,以及事務標識符(TID)。中間三個塊包含準備寫入的內容,這種記錄稱爲物理日誌(physical logging);也有另一種方案,就是邏輯記錄(logical logging),更簡略地記下準備更新的內容,例如“更新數據塊Db至文件X”。這種記法複雜些,但省空間。TxE標記結束,也包含TID。
於是,寫入過程有兩步:
(1)寫入日誌。把事務寫在日誌區或日誌文件裏。
(2)檢查點(checkpoint)。將待寫入的數據和元數據正式寫入到磁盤。
那麼,如果在預寫日誌期間就斷電了,會出現什麼情況呢?爲了防止寫入錯誤的日誌,可以把日誌記錄的這幾個塊一個一個寫入,上一個寫完了再寫下一個;但是,這很慢。我們希望這5塊內容一次性併發地寫完。不過,如果併發地寫入,可能會這樣:例如,磁盤將寫入請求調整了順序,先寫入了1、2、3、5塊。在日誌中寫入待更新數據之前,系統斷電了。重啓後,文件系統並不能識別出第4塊沒有正確寫入,從而直接按照日誌記錄把未進行的真實寫入執行。如果寫入的是關鍵數據,那就糟了:比如寫入了錯誤的超級塊,這會導致文件系統無法掛載。
文件系統引入的應對方法是:將TxE塊在其它塊之後寫入。這一步由磁盤來保證,最終文件系統執行寫入動作分成了三步:日誌寫入、日誌提交(commit)、檢查點。

14、寫入屏障(write barrier)能確保在及屏障之前的寫入操作都被正式執行,而不是先報告寫入完畢,再於隨後補完所有的寫入動作。許多機器都對磁盤操作的正確性提出了相當高的要求,但不幸的是,一些硬盤廠商所謂的“高性能”硬盤忽略了全部的寫入屏障。這種做法雖然讓磁盤看起來更快,但是也帶來了數據可靠性方面的風險。似乎快的東西總是能打敗慢的,即便快意味着出錯。

15、在預寫日誌期間,先寫入其它塊,後寫入結束塊,可能會導致性能問題:通常需要額外等待磁盤再轉一圈(想一想,爲什麼)。Vijayan Prabhakaran解決了這個問題:在向預寫日誌一次性寫入事務時,一併寫入一個校驗和(checksum)。預寫日誌期間若發生意外,在系統恢復後讀取預寫日誌時,就檢驗其校驗和;若不符合,就意味着日誌寫入錯誤,此次更新將被跳過。
這個方法引起了Linux文件系統開發人員的注意,後來被引入到了ext4文件系統中。現在,這種方法應用於至少數百萬臺Linux(包括Android)機器上。

15、當日志寫入錯誤時,系統在重啓後可以放棄更新;如果日誌寫入正確,可以將被打斷的更新重做(redo)。如果是在正式寫入的過程中遭遇故障,也可以將這次寫入重做。這種額外的寫入是小概率事件,因爲故障是小概率的;而且,爲了保持數據的正確性,這點成本不值一提。

16、爲了防止頻繁的少量數據寫入嚴重拖累性能,一些文件系統(如ext3)一般會將更新請求屯起來,把它們統一寫入到在內存中暫存的預寫日誌裏。正式存盤的時候,這一項日誌才被執行。

17、如果不斷地寫入日誌而不釋放空間,那麼記錄日誌的空間遲早會用完。一種常見方法是將日誌的結構改成循環(circular)的(想一想循環隊列)的:當空間寫滿之後,自動從頭開始繼續寫,就覆蓋了之前的日誌。於是寫入操作分成了四步:預寫日誌、提交日誌、檢查點、釋放空間(刪除相應條目也好,直接循環寫入也好)。

18、實現了預寫日誌功能後,從故障中恢復數據是快了,但是通常的寫入就慢了,因爲每次寫入數據都需要先寫入相應的日誌。爲了減少這種影響,人們嘗試了不同的方法。比如可以只在寫入用戶數據之前寫入日誌(ext3)。但另一個更簡單、更通用的方法是:元數據日誌(metadata journaling),也稱ordered journaling。當需要寫入的數據非常長的時候,這種方法十分有效:日誌中不包含數據的部分(注意:目錄文件中記錄的內容也被視爲元數據!)。

不過問題又來了。因爲是先寫入日誌再寫入數據的,如果在存盤途中斷電了,會怎麼樣?當系統恢復時,由於數據內容不在日誌中,文件系統最多隻會重寫索引節點及位映像。於是,索引節點又指向不正確的數據了。
解決辦法是將數據區在寫入日誌之前就先行寫入。也就是說,一次完整的寫入過程分爲5步:寫入數據區、寫入僅包含元數據的日誌、提交日誌、檢查點、釋放空間。
NTFS和XFS都實現了這種日誌方式。而ext3可以讓用戶選擇不同的日誌方式,包括data,ordered和unordered三種。unordered模式下,數據區的寫入不一定在寫入日誌之前。不過,對數據區的寫入必須在日誌提交之前完成,否則還是有可能會出現上面所說的問題:索引節點指向錯誤的數據。

19、Linux ext3引入了一種新的日誌,稱爲廢除(revoke)記錄。刪除文件時,會先寫入相應的日誌。系統恢復時,先掃描這種日誌,使得原先寫入被刪除的數據的日誌不會被用於重新執行寫入,確保被刪除的文件不會被意外恢復,導致覆蓋了其它有用的文件。

20、基於後向指針的一致性(backpointer-based consistency,BBC)是一種新的檢驗一致性的技術。BBC爲每一個塊都配備一個後向指針。當訪問文件時,文件系統可以檢查索引節點或直接指針指向的數據塊是否有後向指針指向索引節點或直接指針本身。如果沒有,則意味着寫入未完成,返回一個錯誤。
還有一個新方法optimistic crash consistency,這個方法通過通用化形式的事務校驗和(transaction checksum)儘可能一次進行更多的磁盤寫入,並應用了幾項新技術來探測不一致性。對於一些負載,這種方法能夠提升一個數量級的性能。但是爲了發揮好作用,磁盤接口需要做一點改變。詳見Vijay Chidambaram所著SOSP’13論文《Optimistic Crash Consistency》。

21、1990年代初,一個Berkeley的研究組(John K. Ousterhout和Mendel Rosenblum帶隊)開發了一種新型文件系統——日誌結構文件系統(log-structured file system,LFS)。設計LFS主要是出於以下原因:
·系統內存容量正在增長。這使得更多數據能夠在內存中緩存,也使得越來越多的數據要寫入磁盤。因此,寫入速率對文件系統性能的影響越來越大
·隨機IO和順序IO性能差距大。硬盤容量密度在增加,但是旋轉延遲和尋道時間降低緩慢。製造又小又便宜的高速電機很困難。
·當時已有的文件系統在通用的負載下表現很差。例如FFS在創建不到4 KB的小文件的時候需要進行大量的寫入:索引節點及其位映像、所在目錄本身及其的索引節點、數據塊及其位映像。這個過程就會引發多次尋道,並伴隨着旋轉延遲。
·文件系統未考慮RAID。例如RAID-4和RAID-5都在寫入小文件時非常慢。當時的文件系統並未盡力去避免這種情況。

22、當寫入磁盤時,LFS會先將全部更新(包括元數據)都暫存在內存的一個段中;當段寫滿後,就將整個段一次性寫入磁盤。LFS從不覆寫已有數據,而總是將段寫入空閒位置。因爲段比較大,因此寫入效率較高,文件系統的性能儘可能被髮揮出來。

23、LFS的基本思想很簡單:把需要寫入的數據儘量寫在一起。例如寫入數據塊時要更新索引節點,就把它們連續放置:

(注意:索引節點的大小遠小於數據塊。這裏爲了作圖方便,畫成一樣大的塊。)
當然,思想倒是說得簡單,實現起來並不容易,因爲要注意很多細節。
假設在某時刻請求寫入塊A,過了很短的時間,又請求在塊A + 1處寫入。但這時候,A + 1已經轉到別的位置去了。要寫入這個塊,得等盤片再轉回來。這反而會降低寫入速率。
LFS解決這個問題的策略也是老生常談的寫入緩存技術。“段”這個詞都給用爛了,不過用在這裏還是比較合適:代表一大段連續的數據。LFS湊出來的一個段大約有幾MB。

24、在舊的UNIX文件系統中,查找索引節點很容易,因爲它們被放在固定的位置。而且,只需要知道索引節點號×每個索引節點的大小,就可以在索引節點區定位到需要的索引節點。如果是在FFS中,查找索引節點也只是稍複雜一點:FFS將索引節點表分成很多個部分,放到了不同的柱面組裏。但在LFS中,查找索引節點就困難了:由上圖可見,它們被分散在磁盤的各個位置。更糟糕的是,因爲LFS從不覆寫,因此如果一個索引節點有更新,更新版本的索引節點會被寫到其它位置去,而不是覆蓋原有的索引節點。
LFS引入了索引節點映射(inode map,imap)。向imap輸入索引節點號,可以查到該索引節點的最新版本的地址。imap可以用一個線性表實現,每一項都存放了一個磁盤指針。每次寫入索引節點時,索引節點映射都要更新。
如果將索引節點映射放在固定的位置,那麼在寫入時,當寫完其它區域以後,又要經過尋道來到這個區域更新映射。這對性能較爲不利。因此,LFS在更新完數據和索引節點後,就將索引節點映射的一部分寫在後面:

索引節點映射分散在磁盤的各個角落,怎麼找到它們呢?其實大家應該也能想到了:總要留一些固定的區域來專門指示其位置。LFS稱其爲檢查點區(checkpoint region,CR)。CR包含了指向最新版本的索引節點映射的指針。CR區僅定期更新(比如每隔30秒),所以對總體性能影響不太大。使用LFS的磁盤分區的大致結構就是下面這樣子(實際上,LFS可能含有2個CR):

25、下面講述LFS讀取文件的過程。一開始內存裏什麼都沒有,然後LFS的CR最先被讀取,接着在磁盤上找到imap的每一個部分,將它們都讀入內存。之後,根據文件的索引節點號,LFS在索引節點映射中查找索引節點。索引節點可以包含直接指針、間接指針或二重間接指針,這一點與典型UNIX文件系統相同。一般來說,LFS在讀取文件的過程產生的IO數與典型的文件系統相同。

26、LFS存儲目錄的方式是這樣的:

索引節點映射包含了目錄文件(索引節點號dir)和新建文件(索引節點號k)的信息。當訪問該目錄下的文件時,先在索引節點映射中查找目錄的索引節點的存儲位置,然後通過讀取目錄的索引節點讀到目錄文件的位置,然後讀取目錄文件並根據要訪問的文件的名稱找到其索引節點號,然後再到索引節點映射中查到文件的索引節點的位置,最終讀到需要訪問的文件的數據。

27、LFS總是將更新(包括修改、追加)後的文件在新的空白位置寫入。舊版本怎麼辦?也許可以一直保留,方便用戶恢復(特別是在不小心覆蓋一個文件時)。這種文件系統稱爲版本式文件系統(versioning file system)。不過LFS不這樣做。它總是隻保留最新版本,於是它會在後臺週期性地搜尋舊版本的文件,連同其索引節點和其它結構一起清除。這個過程稱爲垃圾收集(garbage collection)。
清除舊版本文件時,LFS按照分段的基本策略來執行。爲了判斷一段數據是否最新,LFS在每一段開頭都添加了段摘要塊(segment summary block)。這一塊包括了每個數據塊的索引節點號、文件內偏移等信息。判斷期間,對每一個塊,從所在段的段摘要塊中查找其索引節點號和偏移。下一步,在索引節點映射(通過檢查點區找到)中查找該索引節點號並讀取其指向的內容(如果這部分信息在內存那就更好了)。最後,通過偏移,查找索引節點(或間接指針塊)內部,看看到底將這個偏移指向哪個位置。如果正好指向該數據塊,就認爲它是最新版本,不能清除。如果沒有指向這個數據塊,就能判斷出它是舊版本,已經不再需要。
LFS還使用了別的方法來加速判定。當一個文件被刪除時,LFS也會增加其版本號並在索引節點映射中記錄新版本的信息。如果段內也記錄了這個信息,那麼當讀到這個段時,就可以比對段內的版本號和索引節點映射中的版本號來判定這個段是否爲最新版本。

28、LFS中,決定何時清理舊版本很容易:週期性清理,或者空閒時清理,或者磁盤空間即將耗盡時清理均可。而選擇清理哪些塊就比較具有挑戰性了,很多研究論文就是圍繞這一點的。在LFS原本的論文中,作者介紹了一個區分熱段和冷段的方法。一個熱段是時常被更新的段,而一個冷段的大多數內容基本保持不變。因此,冷段需要先被清理。當然,這個策略並不完美。後來的研究也給出了更佳的方法。

29、萬一在LFS寫入的過程中,系統發生致命錯誤了,怎麼辦?
通常的操作中,LFS會緩衝一個段的寫入動作,稍後(比如段滿了,或者等待時間到了)再統一執行。LFS也會把這些寫入操作記錄在日誌中,比如檢查點區指向頭段和尾段,每一段都指向下一個要寫入的段(最後一段指向下一段的指針爲空)。LFS會週期性更新檢查點區。系統失敗在這些動作期間都有可能發生。
對於CR的更新,LFS實際上維護兩個CR,每個在磁盤的一端,交替寫入。LFS也實現了一個很謹慎的協議,用於通過指向imap和其它信息的最新指針更新CR。具體而言,先寫入一個頭(帶有時間戳(timestamp)),然後寫入CR的主體,再寫入最後一個塊(一樣帶有時間戳)。如果在CR更新期間斷電了或者OS炸了,由於最後的時間戳沒有寫入,LFS就能知道CR寫入過程出現了失敗。LFS總是選擇使用時間戳一致的CR,這確保了CR的一致性。
LFS定期寫入CR,所以上一個成功的更新可能比較久了。所以在重啓時,LFS能夠輕易通過讀取檢查點區、檢查點區指向的索引節點映射片段、相應的文件和目錄來進行恢復。不過,在上一次的成功存盤到致命錯誤之前的這段時間的更新就丟失了。
爲了改進這一點,LFS引入了數據庫中的常用技術——roll forward。其基本思路是:從上一次更新完畢的檢查點區開始,找到日誌記錄的末尾(在CR裏),然後通過日誌不斷讀取下一個數據段,考察是否有有效更新。如果有,LFS就更新文件系統,於是許多在上一次檢查點區更新以後才更新的數據和元數據就得以恢復。詳情請查閱Mendel Rosenblum的獲獎畢業論文《Design and Implementation of the Log-structured File System》。
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

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