前言: 目前XFS已成為Linux主流的文件系統,所以有必要了解下其數據結構和原理。
XFS文件系統
XFS是一個日志型的文件系統,能在斷電以及操作系統崩潰的情況下保證數據的一致性。XFS最早是針對IRIX操作系統開發的,后來移植到linux上,目前CentOS 7已將XFS作為默認的文件系統。使用XFS已成為了潮流,所以很有必要了解下其數據結構和原理。
XFS官方說明文檔參考:https://xfs.org/docs/xfsdocs-xml-dev/XFS_Filesystem_Structure//tmp/en-US/html/index.html
接下來將介紹XFS的一些概念,包括分配組、超級塊、inode等等,過程中會結合xfs_db(xfs提供的輸出文件系統信息的工具)打印一些信息,了解當前XFS的實時數據。
分配組(Allocation Group)
XFS將空間分為若干個分配組,每個分配組大小相等(最后一個可能不等)。分配組包含有超級塊、inode管理和剩余空間管理等,所以分配組可以認為是一個單獨的文件系統。正是分配組這樣的設計,使得XFS擁有了並行IO的能力。在單個分區上使用XFS體現不了這種並行IO能力,但是如果文件系統跨越多個物理硬件比如ceph,並行IO將大大提高吞吐量利用率。
上圖為分配組的結構圖,重點關注前面4個扇區,從上到下分別為超級塊、空閑塊信息、inode信息和內部空閑列表。
超級塊(superblock)
超級塊位於分配組的第一個扇區,包含了分配組和文件系統的全部元數據信息,由於結構體比較大,這里就不作列舉,可去官方文檔中查看https://xfs.org/docs/xfsdocs-xml-dev/XFS_Filesystem_Structure//tmp/en-US/html/Allocation_Groups.html
xfs_db查看超級塊內容,執行xfs_db -r /dev/xxx(xxx為XFS所在的分區),輸入sb再輸入p即可,如下圖所示(鑒於篇幅未盡列出輸出):
超級塊有幾個核心的元數據為:
blocksize:塊大小,一般為4KB;
dblocks:一個分配組含有的塊數目;
agcount:整個文件系統含有的分配組數目;
sectsize:扇區大小,一般為512B;
inodesize:inode節點大小,一般為512B;
icount:整個文件系統目前已經分配的inode數目;
ifree:整個文件系統空閑的inode數目,由於XFS不是格式化的時候預分配所有的inode,而是根據使用情況動態構造inode,所以該值為動態值。
空閑塊信息(AG free block info)
位於分配組的第二個扇區,主要描述兩個空閑空間B+樹和剩余空間信息,結構體如下:
typedef struct xfs_agf { __be32 agf_magicnum; __be32 agf_versionnum; __be32 agf_seqno; __be32 agf_length; __be32 agf_roots[XFS_BTNUM_AGF]; __be32 agf_spare0; __be32 agf_levels[XFS_BTNUM_AGF]; __be32 agf_spare1; __be32 agf_flfirst; __be32 agf_fllast; __be32 agf_flcount; __be32 agf_freeblks; __be32 agf_longest; __be32 agf_btreeblks; } xfs_agf_t;
核心成員如下:
agf_roots:XFS_BTNUM_AGF為2,指明了2棵空閑空間B+樹在哪個block,通過查找這兩棵樹找到合適的空閑block;
agf_levels:樹高;
agf_freeblks:分配組目前空閑block數目
xfs_db輸入agf可查看空閑塊信息,如下圖所示:
空閑空間B+樹
空閑塊信息包含了兩顆空閑空間B+樹,分別以block序號和block數目為關鍵字,滿足兩種不同的需求。
B+樹貫徹了整個XFS,對其有所了解才能更好的理解XFS的運作,網上有很多關於B+樹的資料,請自行查閱,這里只描述一些核心概念:
屬於多叉平衡排序樹;
有m個關鍵字的中間節點有m個子節點;
m個子節點的關鍵字集合包含父節點的關鍵字,B+樹有點像跳表;
中間節點只含有關鍵字不含數據,葉子節點含有所有的關鍵字和數據;
葉子節點含有左右節點指針,所有葉子節點實際上是一條有序鏈表。
B+樹在基於磁盤查找的軟件中應用廣泛,如數據庫,文件系統也一樣,這些軟件都要讀取磁盤數據再查找,所以一次讀取盡量多的關鍵字尤為重要,B+樹的單節點多關鍵字可滿足該需求。
接下來將通過xfs_db去探索這兩棵樹的內容,從agf的打印信息可看到bnoroot=1和cntroot=2,它們分別是以block序號和block數目為關鍵字的B+樹的根節點所在的block序號。
跟蹤以block序號為關鍵字的B+樹操作如下:
由於agf中level為1,所以該B+樹沒有中間節點,直接就是葉子節點,包含有空閑block數據,圖中的recs數組便是,可見目前有3大塊空閑空間。
跟蹤以block數目為關鍵字的B+樹操作如下:
Inode B+樹信息
位於分配組的第三個扇區,主要描述inode B+樹的根block、已構造的inode個數以及空閑個數,數據結構如下:
typedef struct xfs_agi { __be32 agi_magicnum; __be32 agi_versionnum; __be32 agi_seqno __be32 agi_length; __be32 agi_count; __be32 agi_root; __be32 agi_level; __be32 agi_freecount; __be32 agi_newino; __be32 agi_dirino; __be32 agi_unlinked[64]; } xfs_agi_t;
核心成員如下:
agi_root:inode B+樹的根block;
agi_level:樹高;
agi_count:已構造的inode數目;
agi_freecount:空閑的inode數目。
xfs_db輸入agi可讀取到Inode B+樹信息,如下圖所示:
由上圖可知B+樹的根block為root=3,跟蹤該block便可找到具體的inode數據。
Inode信息
每一個文件或目錄都對應一個inode,用於描述文件的基本信息,除了目錄或鏈接,inode不攜帶文件數據。
inode分為3部分,如下;
xfs_dinode_core_t:固定信息,描述文件類型、屬性、訪問時間等;
data fork:存放數據位置信息;
extended attribute fork:存放擴展數據位置信息;
Inode Core
描述文件的基本信息,數據結構定義如下:
typedef struct xfs_dinode_core { __uint16_t di_magic; /* inode magic # = XFS_DINODE_MAGIC */ __uint16_t di_mode; /* mode and type of file */ __int8_t di_version; /* inode version */ __int8_t di_format; /* format of di_c data */ __uint16_t di_onlink; /* old number of links to file */ __uint32_t di_uid; /* owner's user id */ __uint32_t di_gid; /* owner's group id */ __uint32_t di_nlink; /* number of links to file */ __uint16_t di_projid; /* owner's project id */ __uint8_t di_pad[8]; /* unused, zeroed space */ __uint16_t di_flushiter; /* incremented on flush */ xfs_timestamp_t di_atime; /* time last accessed */ xfs_timestamp_t di_mtime; /* time last modified */ xfs_timestamp_t di_ctime; /* time created/inode modified */ xfs_fsize_t di_size; /* number of bytes in file */ xfs_drfsbno_t di_nblocks; /* # of direct & btree blocks used */ xfs_extlen_t di_extsize; /* basic/minimum extent size for file */ xfs_extnum_t di_nextents; /* number of extents in data fork */ xfs_aextnum_t di_anextents; /* number of extents in attribute fork*/ __uint8_t di_forkoff; /* attr fork offs, <<3 for 64b align */ __int8_t di_aformat; /* format of attr fork's data */ __uint32_t di_dmevmask; /* DMIG event mask */ __uint16_t di_dmstate; /* DMIG state info */ __uint16_t di_flags; /* random flags, XFS_DIFLAG_... */ __uint32_t di_gen; /* generation number */ } xfs_dinode_core_t;
核心成員如下:
di_mode:指定文件類型和訪問權限,具體解析參考https://www.xuebuyuan.com/1106749.html
di_format:指定data fork的數據格式,有以下類型:
typedef enum xfs_dinode_fmt { XFS_DINODE_FMT_DEV, // 用於字符和塊設備 XFS_DINODE_FMT_LOCAL, // 用於目錄和鏈接,表明數據就存放在inode的data fork中 XFS_DINODE_FMT_EXTENTS, // data fork存放extent,extent指向具體的block,block存放數據 XFS_DINODE_FMT_BTREE, // data fork存放B+樹根block XFS_DINODE_FMT_UUID // 該值不再使用,忽略 } xfs_dinode_fmt_t;
di_uid:擁有者id;
di_gid:擁有者的組id;
di_atime、di_mtime、di_ctime:訪問時間、修改時間、修改屬性時間;
di_size:數據大小,比如文件的大小;
di_forkoff:實際值要乘以8,是data fork和extended attribute fork的分界線,以data fork為起始,初始值為0,代表沒有extend attribute fork。
Inode number
Inode有個唯一表明身份的number,有兩種格式:相對格式(32位)和絕對格式(64位)。
從上圖可見,絕對格式比相對格式多了AG number部分,中間部分為block序號,右側部分為inode在該block內的序號,可見根據inode number便可得到inode在磁盤的具體位置。sb_agblklog和sb_inoplog的值位於超級塊中。
計算inode所在位置方法:
假設block序號為A,inode序號為B,AG number為C,每個分配組的block數目為D,Inode大小為E,block大小為F(這些值可以計算或通過超級塊得到)
則inode所在的block序號 x = C D + A,則inode地址為 y = x F + E * B
data fork
不同文件類型的data fork形式有所不同,同樣類型的文件根據大小也會有不同的數據結構,接下來將一一描述。
普通文件
普通文件的數據不會放在data fork中,視extent數目大小,有兩種數據形式:
Extent List:每個extent指明存放數據的block地址,遍歷該list便可得到全部文件數據;
B+tree Extent List:由於data fork的容量有限,如果extent數量太多,將采用B+樹的形式存放extent,亦即采用額外的block存放extent。
目錄
有四種形式的目錄:
Shortform目錄:目錄下文件不多或者文件名短,也就是data fork能容納下文件名和文件inode,則目錄的數據放在data fork中;
Block目錄:data fork存放不下目錄的內容,采用額外的一個block存放;
Leaf目錄:一個block存放不下目錄的內容,把索引信息從block中抽離,索引信息用額外一個block存放;
Node目錄:一個block存放不下索引信息,采用多個block存放索引信息;
B+樹目錄:data fork已經存放不下數據block extent,采用B+樹方式存放block extent。
鏈接
有兩種形式的鏈接:
Shortform鏈接:data fork能存放下鏈接的內容,內容即目標文件的路徑;
Extent鏈接:采用額外的block存放鏈接的內容,extent存放block信息。
結束語
以上就是XFS文件系統的一些基本數據結構,了解了基本的數據結構使我們能更深入的了解和探索XFS系統,當系統出現問題時也可以更好地分析原因。