1. 程式人生 > >XFS實現原理詳解

XFS實現原理詳解

0 檔案系統

     引用維基百科對檔案系統的定義:“計算機的檔案系統是一種儲存和組織計算機資料的方法,它使得對其訪問和查詢變得容易,檔案系統使用檔案和樹形目錄的抽象邏輯概念代替了硬碟和光碟等物理裝置使用資料塊的概念,使用者使用檔案系統來儲存資料不必關心資料實際儲存在硬碟(或者光碟)的地址為多少的資料塊上,只需要記住這個檔案的所屬目錄和檔名。在寫入新資料之前,使用者不必關心硬碟上的那個塊地址沒有被使用,硬碟上的儲存空間管理(分配和釋放)功能由檔案系統自動完成,使用者只需要記住資料被寫入到了哪個檔案中。”

    把其中核心的東西標記出來,即可看出檔案系統的本質:一種方便管理、組織、訪問資料的軟體。

  • 對於管理來說,主要是磁碟空閒空間的管理
  • 對於組織來說,主要是通過引入檔案(inode)、樹形目錄(dentry)來組織使用者的資料。檔案包含使用者的資料、樹形為使用者提供了一個對資料進行分類的功能。
  • 對於訪問來說,通過目錄+檔名的方式進行檔案建立、刪除、讀、寫(也就是所謂的增、刪、查、改)。

在以上核心的功能之上,加入錯誤異常處理的機制比如通過日誌保證操作的原子性以及資料的一致性、通過fsck機制確保檔案系統的正確,加入許可權控制,加入一些高階特性比如快照、重複資料刪除等,加上檔案的併發控制等就是一個檔案系統了。

1 XFS簡介

XFS最早針對IRIX作業系統開發,是一個高效能的日誌型檔案系統,能夠在斷電以及作業系統崩潰的情況下保證檔案系統資料的一致性。它是一個64位的檔案系統,後來進行開源並且移植到了Linux作業系統中,目前CentOS 7將XFS+LVM作為預設的檔案系統。據官方所稱,XFS對於大檔案的讀寫效能較好。 

效能問題對於大多數檔案系統而言,都是一個頭疼的事情,特別是在高併發大量小檔案這種場景下,而XFS採用了一些好的思想比如引入分配組、B+樹、extent等方法來提高效能,這些將在下面介紹。

注:文中所有實驗均基於test環境server 68中/dev/sdb2上的XFS檔案系統。 

2 XFS實現原理介紹

XFS的一些設計實現文件:

2.1 分配組(Allocation Group)

    分配組是XFS抽象程度最高的概念。XFS檔案系統內部被分為多個“分配組”,它們是檔案系統中的等長線性儲存區。每個分配組各自管理自己的inode和剩餘空間。檔案和資料夾可以跨越分配組。這一機制為XFS提供了可伸縮性和並行特性——多個執行緒和程序可以同時在同一個檔案系統上並行執行I/O操作。這種由分配組帶來的內部分割槽機制在一個檔案系統跨越多個物理裝置時特別有用,使得優化對下級儲存部件的吞吐量利用率成為可能。

    在一個磁碟上建立XFS檔案系統之後,磁碟會被格式化成如下格式:


在CentOS7上預設的是建立4個AG。每個AG都相當於是1個獨立的檔案系統,維護著自己的free space以及inode,其主要包括以下資訊:

  • superblock:描述整個檔案系統的資訊。
  • 空閒空間管理。
  • inode的分配和記錄管理

結構如下:


    解析上面的圖,發現一個AG可以分為5個部分:

1) 第一個區(共1個block):我將其稱為全域性資訊描述區,包括:superblock(SB)、free space描述(AGF)、inode資訊描述(AGI)、預留free space資訊描述(AGFL)。每一部分都對應一個結構,用於描述其整體資訊。

2) 第二個區(共3個block):B+樹根節點描述區,包括:inode對應的B+樹的根節點(通過AGI中對應欄位指向)、管理free space對應的兩顆B+樹的根節點(AGF中對應的欄位指向),這兩顆B+樹的索引key不同,一個以block number為key,一個以block count為key。

3) 第三個區(共4個block):存放預留freespace節點資料的區域,由AGFL中對應的欄位指向。

4) 第四個區:存放inode樹型節點的區域。

5) 第五個區:存放檔案系統具體資料和元資料的區域。

    所以從整個結構來看,核心的結構為superblock、AGF以及AGF中指向的管理freespace的B+樹、AGI以及AGI所包含的inode的B+樹以及AGFL和其對應的B+樹,下面將會依次介紹這些核心元素。 

2.2 superblock

    superblock位於AG資料中的第一個sector,它會包含AG中的所有元資料資訊。其中幾個核心的元資料為:

• sb_blocksize/sb_dblocks,檔案系統中使用的block的大小,以及整個檔案系統用於存放data和metadata的block個數。

• sb_sectsize,指定底層磁碟一個扇區的大小,這個值決定了I/O操作時,資料的最小對齊粒度。

• sb_agblocks/sb_agcount,檔案系統中一個AG包含的block個數,以及整個檔案系統AG的個數。

• sb_inodesize/sb_inopblock,記錄inode的大小以及每個block中包含inode的個數。

• sb_logstart/sb_logblocks,如果使用同一塊盤存放XFS的journal,這兩個值用於表示存放journal的第一個block以及用於存放log的總block個數。

• sb_icount/sb_ifree,檔案系統中已經分配的inode個數以及剩餘可用的inode個數,這個只在Primary的Superblock中維護。

•sb_versionnum/sb_features2,Filesystem的version number,這是一個bitmap型別的變數。用於表示檔案系統中包含的features。如果其中包含了XFS_SB_VERSION_MOREBITSBIT這個位,sb_features2中 也會包含一些擴充套件的資料位。

2.3 AGF以及其指向的B+樹

2.3.1AGF

    AGF是”AG Free SpaceBlock”的縮寫,它包含了兩個空閒空間B+樹的資訊以及剩餘空間的資訊。其對應的資料結構為xfs_agf,具體如下:


    其中agf_roots指向了兩棵B+樹根節點的blocknumber。

2.3.2AGF指向的B+樹

       XFS中每個AG都使用了兩顆B+樹來管理空閒空間,它們的區別是索引的key不同,第一棵B+樹是基於block的偏移(offset)構建的,而第二顆樹則是基於block的size構建的,這樣XFS可以快速的通過offset或者size進行空閒空間的分配。

       B+樹的每個葉子節點都包含兩部分:節點的元資料資訊使用xfs_btree_sblock_t結構來表示,另外是空閒空間資訊,使用xfs_alloc_rec_t, xfs_alloc_key_t結構來表示,它包含了一個排過序的offset/count的key pair陣列作為node的key。

       比如,下圖即為一個深度為1的B+樹的示意圖:


       而非葉子節點是由三部分組成,除了以上兩部分之外,還有一個Node 指標陣列,包含是相對於AG的block pointers。

       下圖是一個深度為2,但只包含一棵B+樹的示意圖:


       其中,左邊為中間節點,右邊為葉子節點。中間節點包含了一些free space的索引資訊(由keys陣列記錄)用於快速檢索,並且通過ptrs指向了所有葉子節點。每個葉子節點都包含了空閒資料塊(由recs陣列記錄)。

2.4 AGFL

    AG Free List(AGFL),存在在每個AG的第四個sector中,它包含了在AG空間內一個存放指向預留空間的block pointers的陣列,這些預留空間的是為了保證Free Space(AGF) B+樹的正常工作。它總共佔4個block,並且這段空間不能被普通使用者用來存放inodes、 data、 direcotries 和extended attributes。隨著free space碎片的出現,它們的空間也將被消耗,這時會有額外的block加入到這個free list 陣列中。

因為free listarray位於一個sector中,一般這個陣列中會包含128個元素。AGF中三個元素agf_flfirst、agf_fllast、agf_flcount。這些預留的block保證了free space B+ trees在一個full的AG中,當有block被釋放之後free space  B+ trees能夠正常更新。


如上面例子中AGF 0 結構中的第二部分就記錄了AGFL的資訊。

2.5 AGI和AGI指向的B+樹

    所有的檔案、目錄和連線都對應inodes,他儲存在磁碟上的。

    每一個AG都管理自己的inodes,Superblock中第三個sector包含了AG的inode資訊,這個資訊簡稱為AGI。

    Inode的分配是以64個inode為一個chunk進行分配的。其組織結構是B+樹,具體使用的資料結構以及方法和AGF非常類似,只是結構的名字不同罷了。所以你只要理解了前面AGF的管理方法,AGI的也就能理解了。這裡針對其管理方式也不再贅述了,只是把具體的資料結構和示意圖列出來。

AGI的結構為:


深度為1級的inode B+樹示意圖:


其中xfs_inobt_block_t實際上和AGF中的xfs_btree_sblock_t結構一樣,存在於葉子節點和非葉子節點中作為節點的header,記錄節點的元資訊。xfs_inobt_rec_t和AGF中的xfs_alloc_rec_t結構有同樣的作用,存放了節點包含的資料資訊,它是一個數組。陣列中的每一項都通過startino、freecnt和fmask三個元素管理著一個包含若干inode資料資訊的chunk,每個chunk會包含64個inodes,不論大小。其中startino標識了這個chunk中包含的inode的number,freecnt表示該chunk還有多少個可用的entry,fmask是一個64位的bit array,用於指示chunk中具體哪些entry是free的。

深度為2級的inode B+樹示意圖:


至此AG中的前三個區都介紹完了,接下來介紹剩下的inode區以及data區的組織和管理。

2.6 inode資料區

瞭解了inode管理B+樹之後,大概可以瞭解到分配一個inode過程:

  • 找到AG的全域性資訊描述區,從中查詢AG的inode個數,如果還有剩餘,則會找到AGI結構,從中找到inode B+樹的根節點。
  • 根據根節點遍歷整棵B+樹,從每個節點中讀取節點的head資訊,然後從recs陣列中定位一個包含inode的chunk
  • 從chunk中找到一個可用的inode,並分配出去。

當然,這裡有一個問題,就是inode number是在建立檔案時就指定好的,然後根據這個指定好的inode numberinode B+樹中查詢這個inode。還是先去B+樹中查詢一個可用的inode之後,在把這個inode number 分配給上面的檔案,這個需要進一步確認

2.6.1inode number

我們都知道每個inode都是一個整數,那麼它就是簡單遞增的嗎?當然不是,這個整數是按照bit位分為好幾個段的,每個段都有自己的含義。

XFS中的inodenumber分為兩種,一種是相對於AG的,一種是全域性絕對的。前者儲存在AG的inode結構中,後者儲存在directory entry的結構中。具體分段如下圖所示:


可以看到絕對的inode number就是在MSB(mostsignificant bit)中加入具體的AG number。而relative inode number包含兩部分,它們的長度分別有superblock中的sb_agblklog位和sb_inoplog位決定。

但具體這兩部分的值怎麼計算出來的,目前還不得而知。

在瞭解了inode number的結構之後,我們接著看一下inode的結構是什麼樣的。 

2.6.2inode結構

檔案系統中的每一個儲存在磁碟上檔案、目錄、連線都會對應一個inode的結構,所以inode可以說是一個檔案系統最核心的元資料之一了。

一個inode的機構可以分成四個部分:inodecore、inode unlink pointer、data for以及extended attribute fork,如下圖所示。接下來依次介紹這四部分的結構

 

1) inode core(di_core):core相當於一個inode的結構的元資訊,它定義了這個inode所描述的檔案的資訊、檔案的屬性以及確定了data fork和extended attribute fork的結構。具體結構如下所示:


其中需要特殊注意到是di_format這個結構。這個結構可以是以下幾個值:


對於檔案、資料夾和目錄而言,可以使用”local”這個值,意味值所有的metadata和資料都儲存在inode結構結構裡。也可以使用”extents”這個值,意味著inode包含了一個extents的陣列,每個extent都指向若干blocks,metadata和資料都儲存在這些blocks中。還可以使用”btree”,意味著inode中包含了一個B+tree的根節點,這個B+樹維護的blocks中儲存了具體的metadata和data。”dev”主要用於字元和塊裝置,”uuid”目前沒有在用。

2) inode unlink pointer(di_next_unlinked):這個值用於記錄那些已經被刪除(unlinked)但仍然還被引用的inode。當有這種情況出現時,inode會被新增到AGI的agi_unlinkedhash buckets中,AGI的unlinked bucket指向了一個inode,而這個inode的di_next_unlinked值會指向下一個inode,依次形成一個連結串列,最後一個inode的di_next_unlinked值被置為NULL。如下圖所示:


一旦inode的最後一個reference釋放之後,inode會從這個unlinked hash chain中移除。當檔案系統crash時,XFS的recovery執行緒會處理完這個list中的所有inode節點。

3) data fork:定義檔案的具體data儲存區域。它的結構以及大小由inode的type以及inode core中的di_format域共同確定。data fork的起始地址是inode空間偏移100個位元組的位置,這也被稱為是inode的literal area的起始地址。data fork的結束位置(最大值)由inode的大小和inode core中的di_forkoff(擴充套件屬性的起始地址)決定。

XFS系統中的資料是以extents這種結構來為檔案分配儲存空間,用於存放檔案的資料的。extents中也包含了其相對於檔案的邏輯偏移,這使得檔案能夠通過extent map(Fiemap)支援sparse檔案。在分配檔案資料空間時,XFS會盡可能連續分配,如果當前AG沒有空間或者busy,也可能會在其他AG內分配空間。

inode中的di_u結構中存放了找到檔案資料對應的extents的索引資訊,其結構如下所示:


它是一個union型別的結構,具體使用哪個結構取決於組織extents的方式。不同型別的inode:檔案、目錄、連線它們的組織方式也不盡相同:

  • 對於檔案來說,datafork指定了檔案的data extents,extents指定了檔案的具體資料儲存在檔案系統的位置。extents有兩種型別,具體有di_format的值確定:
    • XFS_DINODE_FMT_EXTENTS:extent索引資料完全包含在inode結構裡,inode包含了一個檔案指向檔案系統資料block的extents陣列
    • XFS_DINODE_FMT_BTREE:extent索引資料包含在一個B+樹的葉節點中。inode中包含了樹的根節點。
  • 對於目錄來說,Datafork包含了目錄的entries和相關的資料。entries的格式由di_format的值來確定,大概分成3種:
    • XFS_DINODE_FMT_LOCAL:directory entries完全包含在inode。
    • XFS_DINODE_FMT_EXTENTS:directory entries存放在檔案系統的block中,inode中包含了一個extents的陣列,這些extents指向了檔案系統的blocks中。
    • XFS_DINODE_FMT_BTREE:directory entries包含在一個B+樹的葉子節點中。
  • 對於符號連線來說,Datafork包含了連線的所有內容。link的結構可以通過di_format的值來確定:
    • XFS_DINODE_FMT_LOCAL:符號連線的內容完全包括在inode中。
    • XFS_DINODE_FMT_EXTENTS:符號連線儲存在另外一個block中,inode包含了指向這些block的extents陣列。

其中常用的是使用extent list(XFS_DINODE_FMT_EXTENTS)或者B+樹(XFS_DINODE_FMT_BTREE)來儲存資料的兩種情況詳細介紹inode中di_u的結構。而擴充套件屬性還經常使用XFS_DINODE_FMT_LOCAL這種方式,比如Ceph中將object info等資訊以local的方式儲存在object檔案的擴充套件屬性中(inline xattr)。在data fork先著重介紹前兩個。

在介紹這個之前我們先認識一下extent:

extent是用來組織inode資料的一種方式,是一個 128bit的數字,但這128bit的數字是需要按照bit解析的,它對應的結構是xfs_bmbt_rec_t,並且使用 大端方式儲存。xfs_bmbt_rec_32_t和xfs_bmbt_rec_64_t結構類似,只是extent結構表示方式不同。


而在inode core中管理的extents使用了xfs_bmbt_irec_t結構:


Ø  XFS_DINODE_FMT_EXTENTS

對於使用XFS_DINODE_FMT_EXTENTS這種方式來管理inode對應的資料資訊時,其di_u的組織結構如下圖所示:


檔案中所能包含的extent的個數由inode的大小,以及inodecore中的di_forkoff確定。對於一個256Byte大小的inode來說,如果沒有擴充套件屬性它能包含多少個extent呢?(256-100)/128 = 9,這裡extent使用128bit的結構儲存。當檔案需要的extent超過inode中di_u中能夠包含的個數時,超出部分會以B+樹的形式儲存。

在這種組織方式下,查詢檔案的資料就非常簡單了。

  • 根據資料相對於檔案的偏移,在di_bmx中找到對應的項,並讀出block number
  • 根據blocknumber以及block count進行資料訪問。

Ø  XFS_DINODE_FMT_ BTREE

對於使用B+樹的方式管理extent時,inode的data fork會儲存B+樹的root node,B+樹中使用的blockpointer均為64bit的絕對block number。

B+樹的組織形勢無非在於節點資料結構的使用,通常分為三種root node、intermediate nodes以及leaf nodes。其中對於root node而言,包括以下三種結構:

分別對應di_bmbt,bb_keys以及bb_ptrs,bb_keys和bb_pts為兩個陣列。bb_keys陣列的元素即為extent相對於檔案的偏移,它是查詢資料時使用的key。bb_ptrs指向了包含extents資訊的葉子節點,或者B+樹的中間節點。

對於intermediate nodes來說,其包含以下三種結構:


其中xfs_bmbt_block_t變數維護了該節點的B+樹的元資料。bb_keys陣列中的元素即為extent相對於檔案的偏移,它是查詢資料時使用的key。bb_ptrs陣列中的元素指向了包含extents資料的葉子節點。中間節點和root節點的資料結構很像,唯一的區別就是節點header部分的定義。

對於葉子節點來說,包含以下兩種結構:


extents的陣列即為xfs_bmbt_rec_t結構的,具體指向包含資料的block。

B+樹的非葉子節點都會包含一個keys陣列,用於加快資料查詢速度。Key都是以資料相對檔案的邏輯偏移構建的。 

瞭解了這些資訊之後,我們來看一下,inode的資料是如何通過B+樹來管理的。對於一級的B+樹來說,其管理extent的示意圖如下所示:


整體上分為三部分,最上面是rootnode,儲存在inode的di_u結構裡,bb_keys中的元素,是每一個葉子節點包含的起始extents元素指向資料相對於檔案的偏移。bb_ptr中的元素指向了中間的葉子節點。中間葉子節點的head部分會通過bb_leftsib/bb_rightsib將節點串聯起來。而extents陣列中包含了所有extent資訊,每一個extent都指向了具體存放資料的block。

對於一個多級的B+樹,其對應的示意圖如下所示:

 

總結一下,使用B+樹管理extent時,資料的查詢過程:

  • 根據資料相對於檔案的偏移,在inode的di_u結構中找到對應的bb_keys陣列中的項
  • 根據bb_keys中的項從bb_ptrs陣列中找到指向對應節點的blocknumber。
  • 以同樣的方法遍歷B+樹的中間節點,直到找到葉子節點。
  • 讀出extents陣列中對應的extent項,找到存放資料的block,從中讀出資料。 

4) extended attribute fork

   檔案系統的擴充套件屬性為使用者提供了一種可以在inode中存放key/value鍵值對結構的資料。這種結構可以用於儲存一個檔案的元資料資訊。鍵值對的key可以是一個大到256byte的字串,而values可以達到64KB的大小。

   目前為止,ACL(Access Control List)以及DMF(Data Migration Facility)都使用了擴充套件屬性來儲存資料。

   XFS的擴充套件屬性有兩個版本:"attr1"和"attr2",attribute version通過superblock中sb_features2的XFS_SB_VERSION2_ATTR2BIT標誌來標記。version決定了inode中di_u和di_a中的額外空間如何組織,當然也決定了di_forkoff的值如何在inode core中如何被管理,下圖比較清晰的說明了兩者組織方式的不同:


Inode中的extended attributefork包含了和inode關聯的擴充套件屬性的存放位置。extended attribute fork的起始地址通過inode core中的di_forkoff域指定,該值是相對於inode的literal area的,也就是datafork的起始地址的。如果di_forkoff的值為0,表明該inode沒有擴充套件屬性,如果非零,起始地址為di_forkoff * 8(以位元組為單位),其最大值為2048 byte。

extendedattribute fork的結構由inode core中的di_aformat結構決定,它可以是以下的任意一種,另外attribute必須以64bit為單位進行分配。

  • XFS_DINODE_FMT_LOCAL:extended attributes完全包含在inode中,可以通過將XFS_DFORK_APTR指向的指標轉換為xfs_attr_shortform_t*來訪問擴充套件屬性的值。
  • XFS_DINODE_FMT_EXTENS:屬性儲存在另外的block中,inode中包含了指向這些block的一個指標陣列。可以通過將XFS_DFORK_APTR指向的指標轉換為xfs_bmbt_rec_t*的型別來訪問。
  • XFS_DINODE_FMT_BTREE:包含attributes的extents儲存在一個B+樹的葉子節點中。inode中包含了這個樹的根節點,可以通過將XFS_DFORK_APTR的指標轉化為xfs_bmdr_block_t*的型別來訪問。

這幾種方式的區別就是擴充套件屬性的存放位置以及存放管理方式不同。下面著重介紹以下常用的幾種方式:

Ø  XFS_DINODE_FMT_LOCAL

這種方式下,inode的di_aformat被設定為”local”,inode的所有擴充套件屬效能夠儲存在inode的di_a中,而對應的di_a使用一個稱之為shortform的結構。


其和inode中對應的示意圖為:


其中namelen和valuelen標識了擴充套件屬性中的key/value陣列的長度。而key/value兩者的資料統一儲存在nameval結構中。

當inode的attributefork空間被shortform attributes使用完時,attribute format會遷移到”extents”模式。

Ø  XFS_DINODE_FMT_EXTENTS

因為這中方式目前還沒有太多的使用場景,所以,這裡僅貼出其資料組織圖:


3 總結

至此,XFS的核心架構及實現已經基本介紹完了。