BTRFS - A Forest,extent分配樹,同步與併發

一、A Forest

文件系統由樹木森林構造(a forest of trees),超級塊定位在一個固定的磁盤位置。它指向一個樹的根樹節點,該節點索引構成文件系統的b樹。The trees are:

Sub-volumns:存儲用戶可見文件和目錄。每個子卷有一個單獨的tree實現。子卷可以被快照和克隆,創建額外的b-trees。The roots of all sub-volumes are indexed by the tree of tree roots.

Extent allocation tree: 跟蹤在extent items中已分配的extents,且爲磁盤上的free-space映射服務。 所有的extent的back-reference被記錄在extent item中。需要的話,這個允許移動一個extent,或者從一個損壞的磁盤塊中恢復。back-reference將磁盤引用指針的數目乘以2。對每個向前的指針,就確信的有一個向後的指針。

Checksum tree: 對每個已分配的extent維護一個checksum。在extent的每個page中item包含一系列的checksums。

Chunk and device tree:處理物理設備的間接層。允許鏡像/條帶化和RAID。 

Reloc tree: 涉及移動extents的特殊的操作。

舉個例子:下圖展示了一個特殊文件系統的高級的結構。爲了簡單reloc和chunk trees被忽略。

下圖展示了用戶寫文件系統之後發生的改變。

綠色的是被修改的頁。

修改用戶可見的文件和目錄會引起pages和extents的更新。這會波及到子卷樹知道它的根。 改變也會發生在extent 分配, ref-counts 和 back-pointers。這會波及extent 樹。數據和元數據校驗改變,這些更新修改了校驗樹葉子,引起修改產生漣漪效果。

所有這些樹的修改都將作爲樹根樹中的新根在最頂層在頂層被捕獲(at the top most level as a new root in the tree of tree roots),修改在內存中是加速的,且在一個超時之後,或者充足的paes改變,以塊的形式寫道磁盤的新。位置,構成一個checkpoint檢查點。默認的超時時間是30秒。一旦檢查點被寫,超級塊被修改指向新的檢查點;這是唯一一種修改磁盤塊的情況。如果發生了crash,文件系統通過讀取超級塊恢復,且follow指針到上一個有效的磁盤檢查點。當一個檢查點被初始化,所有的髒的內存數據都被標記位不可變的。檢查點運行是收到的用戶更新會導致不可變的pages被重新COWed。這個允許用戶可見文件系統操作去執行而不需要損壞檢查點的完整。

子卷樹可以被快照和克隆,他們因此也是ref-counted的。其他的樹在每個磁盤範圍內保留元數據,他們從不會快照。參考計數對他們來說是沒必要的。

文件系統更新影響許多磁盤結構。舉個例子,寫一個4KB數據到文件會改變文件i-node,file-extents,checksums and back-references.這些改變導致在它們各自的書裏的一整個路徑的改變。  如果用戶執行整個隨機更新,這個對文件系統而言是很昂貴的。 幸運的是,用戶行爲在很多地方都是正常的。如果一個文件被更新,可能會用新數據去更新;相同目錄下的文件有一個很高的co-access的可能性(大概是互相可以訪問的意思吧)這就允許在樹中合併的修改路徑。儘管如此,錯誤情況文件系統代碼中也有考慮。 樹結構被阻止以至於文件操作通常是單個路徑的修改。 大範圍的操作會被打斷分成多個部分,因此檢查點不會增長的太大。 最後,特殊的塊被預留使用,因此一個檢查點將總是在磁盤上有一個home,保證進步。

使用寫時複製作爲爲一個更新策略又有點也有缺點。優勢就是保證操作原子是比較簡單的,並且數據結構是完整的。 掠食就是性能依賴維護空閒連續磁盤空間的large extents的能力。額外的,隨即更新一個文件趨向於碎片化它,順序摧毀。一個好的碎片整理算法是需要的。

校驗在塊寫到磁盤的時刻被計算。在checkpoint的結尾,所有的checksum匹配,root block的checksum反映了整棵樹。 元數據節點在他們被創建的時候計略generation point。這個是他們的checkpoint的serial number。B-tree 指針存儲期望的target eneration number,這個允許在媒體上探測幻影和寫錯的位置。Generation number 和校驗服務一起去驗證磁盤塊內容

二、Extent allocation tree

    Extent 分配樹持有extent-items,每個描述一個特別的連續的磁盤區域。對於一個extent而言可能有許多的引用,每個引用只涉及其中一部分。舉個例子,考慮文件foo有一個cipanextent 100KB-120KB。文件 foo被克隆去創建文件bar。 稍後,一個10KB大小範圍在bar文件中被重寫。 這個會導致下面的情景:

Foo和他的克隆文件共享extent100-128KB部分的數據。Bar在中間有一個extent被重寫,現在位於磁盤更遠的地方。

有三個指針指向extent 100-128KB, 覆蓋不同部分。extent-item 持續追蹤所有這樣的引用,在後續的時間裏允許移動整個extent。 一個extent可以可能有大量的back reference(難道叫作向後引用?),在這種情況下extent-items 不適用於單個的b-tree葉子節點。這種情況下,item泄露並佔用超過一個葉子。

一個back reference是邏輯的而不是物理上的。它由root_object_id,generation_id, tree level, and 在指向塊中的最低級object_id組成。這允許找到指針,在一個在從root_object_id開始的查找遍歷之後找到指針。

三、Fsync

  fsync是一個操作會將一個特殊的文件的所有的髒的數據flush到磁盤。 一個重要的使用例子是在提交事務之前通過數據庫去保證數據庫日記在磁盤上。延遲是很重要的;一個事務直到日誌都在磁盤上纔會提交。一個天真的fsync實現是去校驗整個文件系統。但是,這得忍受高延遲。取而代之的,修改設計特殊文件的數據和元數據被寫到一個特殊的log-tree。如果系統崩潰了,log-tree將會作爲恢復序列的一部分被讀取。 這個保證只有最小的相應的修改會成爲fsync代碼路徑的一部分。

四、Concurrency

現代系統有多個CPU,多核。通過並行性充分利用計算能力是一個重要考慮因素。

老一輩在磁盤上是一成不變的,並且他們的訪問不需要鎖。 在內存中修改的頁面需要保護。因爲數據被組織成樹,策略是使用一個read/write 鎖表。 樹遍歷在read 模式下被初始化。 當一個節點需要更新時, 鎖被轉換成模式。 如果一個塊B需要COW, 遍歷會被重啓。 新的遍歷在parent(B)停止, COWs B,修改父指針,並且繼續向下。

樹遍歷是自頂向下的。他們在頂部開始,且走在樹下,沒必要走回去。

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