Ext4文件系統架構分析(一)


本文描述Ext4文件系統磁盤布局和元數據的一些分析,同樣適用於Ext3和Ext2文件系統,除了它們不支持的Ext4的特性外。整個分析分兩篇博文,分別概述布局和詳細介紹各個布局的數據結構及組織尋址方式等。感興趣的看官敬請留意和指導!

1. Ext4文件系統布局綜述

一個Ext4文件系統被分成一系列塊組。為減少磁盤碎片產生的性能瓶頸,塊分配器盡量保持每個文件的數據塊都在同一個塊組中,從而減少尋道時間。以4KB的數據塊為例,一個塊組可以包含32768個數據塊,也就是128MB。

1.1 磁盤布局

Ext4文件系統的標准磁盤布局如下:

Ext4文件系統主要使用塊組0中的超級塊和塊組描述符表,在其他一些特定塊組中有超級塊和塊組描述符表的冗余備份。如果塊組中不含冗余備份,那么塊組就以數據塊位圖開始。當格式化磁盤成為Ext4文件系統的時候,mkfs將在塊組描述符表后面分配預留GDT表數據塊(“Reserve GDT blocks”)以用於將來擴展文件系統。緊接在預留GDT表數據塊后的是數據塊位圖與inode表位圖,這兩個位圖分別表示本塊組內的數據塊與inode表的使用,inode表數據塊之后就是存儲文件的數據塊了。在這些各種各樣的塊中,超級塊、GDT、塊位圖、Inode位圖都是整個文件系統的元數據,當然inode表也是文件系統的元數據,但是inode表是與文件一一對應的,我更傾向於將inode當做文件的元數據,因為在實際格式化文件系統的時候,除了已經使用的十來個外,其他inode表中實際上是沒有任何數據的,直到創建了相應的文件才會分配inode表,文件系統才會在inode表中寫入與文件相關的inode信息。

1.2 Flexible 塊組(flex_bg)

Flexible 塊組(flex_bg)是從Ext4開始引入的新特性。在一個flex_bg中,幾個塊組在一起組成一個邏輯塊組flex_bg。Flex_bg的第一個塊組中的位圖空間和inode表空間擴大為包含了flex_bg中其他塊組上位圖和inode表。

比如flex_bg包含4個塊組,塊組0將按序包含超級塊、塊組描述符表、塊組0-3的數據塊位圖、塊組0-3的inode位圖、塊組0-3的inode表,塊組0中的其他空間用於存儲文件數據。同時,其他塊組上的數據塊位圖、inode位圖、inode表元數據就不存在了,但是SB和GDT還是存在的。

Flexible塊組的作用是:

(1)  聚集元數據,加速元數據載入;

(2)  使得大文件在磁盤上盡量連續;

即使開啟flex_bg特性,超級塊和塊組描述符的冗余備份仍然位於塊組的開頭。 Flex_bg中塊組的個數由2^ext4_super_block.s_log_groups_per_flex 給出。

1.3 元塊組(Meta Block Groups)

通常,在每個冗余備份的超級塊的后面是一個完整的(包含所有塊組描述符的)塊組描述符表的備份。這樣會產生一個限制,以Ext4的塊組描述符大小64 Bytes計算,文件系統中最多只能有2^21個塊組,也就是文件系統最大為256TB。

使用元塊組Meta Block Groups特性,每個塊組都包含該塊組自己的描述符的冗余備份。因此可以創建2^33個塊組,也就是文件系統最大1EB。48位數據塊,每個塊組128MB,因而可以創建2^33個塊組。

元塊組實際上是可以用一個塊組描述符塊來進行描述的塊組集,簡單的說,它由一系列塊組組成,同時這些塊組對應的塊組描述符存儲在一個塊中。它的出現使得Ext3和Ext4的磁盤布局有了一定的變化,以往超級塊后緊跟的是變長的GDT塊,現在是超級塊依然決定於是否是3,5,7的冪,而一個塊組描述符塊則存儲在元塊組的第一個,第二個和最后一個塊組的開始處(見下圖)

在兩種情況下我們可能會用到這種新布局: 

(1)  文件系統創建時。用戶可以指定使用這種布局。 

(2)  當文件系統增長而且預留的組描述符塊耗盡時。目前超級塊中有一個域s_first_meta_bg用於描述第一個使用元塊組的塊組。 

 當增加新塊組時,我們不需要給組描述符表預留空間,而是在當前文件系統后面直接添加新的元塊組就可以了。

 

1.4 Lazy 塊組初始化

如果塊組中的相應標志已設置,那么塊組中的inode位圖和inode表將不被初始化。這樣可以減少mkfs時間,如果開啟了塊組描述符校驗和功能,甚至連塊組都可以不初始化。

1.5 特殊inodes

Ext4預留了一些inode做特殊特性使用,見下表:

表 1  Ext4的特殊inode

Inode號    用途

0      不存在0號inode

1      損壞數據塊鏈表

2      根目錄

3      ACL索引

4      ACL數據

5      Boot  loader

6      未刪除的目錄

7      預留的塊組描述符inode

8      日志inode

11     第一個非預留的inode,通常是lost+found目錄

1.6 數據塊和Inode分配策略

在機械磁盤上,保持相關的數據塊相互接近可以總的磁頭移動時間,因而可以加速磁盤IO。在SSD上雖然沒有磁頭轉動,數據局部性可以增加每次IO請求的傳輸的數據大小,因而減少響應IO請求的傳輸次數。數據的局部性對單個擦除塊的寫入產生影響,可以加速文件重寫的速度。因而盡可能減少碎片是必要的。inode和數據塊的分配策略可以保證數據的局部集中。以下為inode和數據塊的分配策略:

(1)  多塊分配可以減少磁盤碎片。當文件初次創建的時候,塊分配器預測性地分配8KB的磁盤空間給文件。當文件關閉的時候,未使用的空間當然也就釋放了。但是如果推測是正確的,那么文件數據將寫到一個多個塊的extent中。

(2)  延遲分配。當一個文件需要更多的數據塊引起寫操作時,文件系統推遲決定新數據在磁盤上的存放位置,直到臟的buffer寫到磁盤為止。

(3)  盡量保持文件的數據塊與其inode在同一個塊組中。可以減少磁盤尋道時間.

(4)  盡量保持同一個目錄中的所有inodes與目錄位於同一個塊組中。這樣的假設前提是一個目錄中的文件是相關的。

(5)  磁盤卷被分成128MB的塊組。當在根目錄中創建目錄時,inode分配器掃描塊組並將新目錄放到它找到的使用負荷最小的塊組中。這可以保證目錄在磁盤上的分散性。

(6)  即使上述機制無效,仍然可以使用e4defrag整理碎片文件。


1.7 超級塊

超級塊記錄整個文件系統的大量信息,如數據塊個數、inode個數、支持的特性、管理信息,等待。

如果設置sparse_super特性標志,超級塊和塊組描述符表的冗余備份僅存放在編號為0或3、5、7的冪次方的塊組中。如果未設置sparse_super特性標志,冗余備份存在與所有的塊組中。以下是2.6.32.18內核中對Ext4超級塊的描述:

 



3.0的內核中,Ext4的超級塊加入了以下相關元數據:快照、文件系統錯誤處理相關、掛載選項、配額文件inode、超級塊校驗和等,見下圖。目前沒有深入研究這些新的元數據。

 

1.8 塊組描述符

一個塊組中,具有固定位置的數據結構是超級塊和塊組描述符。其他數據結構位置都可以不固定。Flex_bg機制使用這個性質將幾個塊組聚合成一個flex塊組,將flex_bg中所有位圖和inode 表放到flex_bg的第一個塊組中。詳細情況可以參考我的上一篇Ext4分析博文的Flexible 塊組(flex_bg)部分。


      如果設置了meta_bg特性標志,幾個塊組結合成一個meta group。在meta_bg的情況下,在meta group中的第一個和最后兩個塊組中僅包含meta group中的塊組的塊組描述符。Flex_bg和Meta_bg互斥因而不能共同出現。

 

1.9 數據塊位圖與inode位圖

數據塊位圖跟蹤塊組中數據塊使用情況。Inode位圖跟蹤塊組中Inode使用情況。每個位圖一個數據塊,每一位用0或1表示一個塊組中數據塊或inode表中inode的使用情況。如果一個數據塊大小是4KB的話,那一個位圖塊可以表示4*1024*8個數據塊的使用情況,這也是單個塊組具有的最大數據塊個數。這樣可以算出一個塊組大小是128MB。當然一個位圖塊也可以表示4*1024*8個inode的使用情況,但是實際上一個塊組中即使存滿了文件,也不會用到這么多的inode,因為實際系統中基本不會出現所有文件大小都小於等於1個數據塊大小的情況。實際上一個塊組中有多少個inode,在塊組描述符中是確定的,在文件系統格式化過程中也會看到這個數值,如果沒記錯的話,大概是每4個還是8個數據塊分配一個inode空間。

 

1.10 Inode表

為了找到與一個文件相關的信息,必須遍歷目錄文件找到與文件相關的目錄項,然后加載inode找到該文件的元數據。Ext4在目錄項中用一位存儲了文件類型(通常存儲在inode中)的拷貝,這對性能提升有益。Inode表的大小為ext4_super_block.s_inode_size * ext4_super_block.s_inodes_per_group Bytes。 


    
 Ext4的inode的數據結構大小為156 bytes,但是Ext4的標准inode的大小是256 bytes。

 

1.11 查找inode

每個塊組包含ext4_super_block.s_inodes_per_group個inodes。因為0號inode不存在,可以通過如下的算式計算inode所在的塊組:

bg=(inode_num -1)/ ext4_super_block.s_inodes_per_group

inode在塊組中inode表中的索引index可以通過如下的算式計算:

index=(inode_num -1) % ext4_super_block.s_inodes_per_group

inode在inode表中的地址偏移為:

offset=index * ext4_super_block.s_inode _size

 

1.12 inode.i_block0[]s的內容

取決於文件類型,inode.i_blocks[]使用的方式不同。一般來說,常規文件和目錄用inode.i_blocks[]作為文件數據塊索引信息,特殊文件將inode.i_blocks[]用於特殊用途。常規文件用inode.i_blocks[]作為文件數據塊索引信息的三級索引結構會在后面直接、間接塊地址中詳細介紹。

 

1.13  符號鏈接

如果符號鏈接的目標字符串長度小於60字節,那么就將其存儲在inode.i_blocks[]中,inode中inode.i_blocks[]占據的大小剛好是60KB。這里要注意到的是,有些文件其內容是跟文件的元數據放在一起的,因而就沒有了數據塊。也就是說不是每個文件數據都必然占據着一個數據塊。

 

1.14 直接/間接塊地址

 Ext2/Ext3中數據塊映射方式如下表

 

 

1.15   Extent 樹

Ext4中用extent樹代替了邏輯塊映射。使用extents,用一個struct ext4_extent結構就可以映射多個數據塊,減少元數據塊的使用。如果設置了flex_bg,甚至可以用一個extent分配一個非常大的文件。使用extent特性,inode必須設置extents flag。

Extents以樹的方式安排。Extent樹的每個節點都以一個ext4_extent_header開頭,如果節點是內部節點(ext4_extent_header.eh_depth>0),ext4_extent_header后面緊跟的是ext4_extent_header .eh_entries個索引項struct ext4_extent_idx,每個索引項指向該extent樹中一個包含更多的節點的數據塊。如果節點是葉子節點(ext4_extent_header.eh_depth==0),ext4_extent_header后面緊跟的是ext4_extent_header .eh_entries個struct ext4_extent數據結構。這些ext4_extent結構指向文件數據塊。Extent樹的根結點存儲在inode.i_blocks中,可以存儲文件的前4個extents而不需額外的元數據塊。

ext4_extent_header:

struct ext4_extent_idx:extent樹的內部節點,也稱為索引節點。

ext4_extent:extent樹的葉子節點。

 

 

1.16 Extent樹數據塊校驗和:可能加入的新元數據

由於extent樹的根在inode中,因而Extent樹數據塊指extent樹的除根據節點外的所有內部節點和葉子節點。Extent的樹根節點和葉子節點的數據塊中存儲完xt4_extent_idx和xt4_extent數據結構后至少會留下4 ((2^x%12)>=4) bytes的空間。因而可以加入一個結構struct ext4_extent_tail,其中存儲32位的校驗和。位於inode中的4個extents無需校驗和,因為inode已經做了校驗和。

 

1.17 目錄項

Ext4文件系統中,一個目錄差不多是一個平面文件,映射任意長度的字符串到文件系統中的一個inode。文件系統中存在多個目錄項引用同一個inode——硬鏈接,這也是硬鏈接不能鏈接其他文件系統中的文件的原因。

 

1.18 線性(經典)目錄

缺省地,目錄文件中包含一個線性的目錄項數組。未使用的目錄項標記為inode =0。Ext4文件系統默認地使用struct ext4_dir_entry_2記錄目錄項,除非沒有設置filetype特性標志。在沒有設置filetype特性標志的情況下,使用struct ext4_dir_entry記錄目錄項。

 

 

1.19 哈希樹目錄

線性目錄項不利於系統性能提升。因而從ext3開始加入了快速平衡樹哈希目錄項名稱。如果在inode中設置EXT4_INDEX_FL標志,目錄使用哈希的B樹(hashed btree ,htree)組織和查找目錄項。為了向后只讀兼容Ext2,htree實際上隱藏在目錄文件中。

Ext2的慣例,樹的根總是在目錄文件的第一個數據塊中。“.”和“..”目錄項必須出現在第一個數據塊的開頭。因而這兩個目錄項在數據塊的開頭存放兩個struct ext4_dir_entry_2結構,且它們不存到樹中。根結點的其他部分包含樹的元數據,最后一個hash->block map查找到htree中更低的節點。如果dx_root.info.indirect_levels不為0,那么htree有兩層;htree根結點的map指向的數據塊是一個內部節點,由一個minor hash索引。Htree中的內部節點的minor_hash->block map之后包含一個零化的(zeroed out) structext4_dir_entry_2找到葉子節點。葉子節點包括一個線性的struct ext4_dir_entry_2數組;所有這些項都哈希到相同的值。如果發生溢出,目錄項簡單地溢出到下一個葉子節點,哈希的least-significant位(內部節點的map)做相應設置。

       以htree的方式遍歷目錄,計算要查找的目錄文件名稱的哈希值,然后使用哈希值找到對應的數據塊號。如果樹是flat,該數據塊是目錄項的線性數組,因而可被搜索到;否則,計算文件名稱的minor hash,並使用minor hash查找相應的第三個數據塊號。第三個數據塊是目錄項線性數組。

Htree的根 :struct dx_root


Htree的內部節點: struct dx_node

Htree 樹根和節點中都存在的 Hash map struct dx_entry

 

1.20 擴展屬性EA

擴展屬性(xattrs)通常存儲在磁盤上的一個單獨的數據塊中,通過inode.i_file_acl*引用。擴展屬性的第一應用是存儲文件的ACL以及其他安全數據(selinux)。使用user_xattr掛載選項就可為用戶存儲以“user”開頭的所有擴展屬性。這樣的限制在3.0內核中已經消失。

可以在兩個地方找到擴展屬性:一是在一個inode項結尾到下一個inode項開頭的地方;二是inode.i_file_acl指向 的數據塊之中,到3.0為止,這個數據塊中不包含指向第二個擴展屬性數據塊的指針。理論上可以將每個屬性值存儲到一個單獨的數據塊中,但是3.0內核為止仍然沒有這樣做。

當擴展屬性不存儲在一個inode之后的時候,就會有一個頭部ext4_xattr_ibody_header

擴展屬性數據塊的開頭是ext4_xattr _header

緊跟在ext4_xattr_ibody_header或者ext4_xattr _header后面的是結構數組 struct ext4_xattr_entry

擴展屬性值可以緊跟在ext4_xattr_entry項表后面。考慮4 bytes對齊。擴展屬性值從擴展屬性數據塊的末尾開始向ext4_xattr _header / ext4_xattr_entry表的方向增長。當發生溢出時,溢出的部分放到一個單獨的磁盤數據塊上。

 

1.21 日志(JBD2)

文件系統在磁盤上保留一段小的連續區域(默認128MB),作為盡可能需要快速寫入磁盤的“重要”數據的存放地。一旦該重要數據事務完全寫到磁盤,將其從磁盤寫緩存中刷出。被提交的數據一份記錄也被寫到日志。一段時間后,日志在擦除提交記錄前將事務寫到它們在磁盤上的最終位置(可能包含大量的尋道或者大量的讀-寫-擦除)。

從性能方面考慮,Ext4默認直接將文件系統元數據寫到日志。因而不能保證文件數據塊的一致性。

       日志的inode為8。日志inode的前68 bytes復制了ext4 超級塊。日志文件在文件系統中是普通文件,但是隱藏不可見。日志文件通常消耗一個完整的塊組,可以通過mke2fs將日志文件放在磁盤的中間。

Ext4和Ocfs2都使用JBD2。

1.21.1 布局

日志布局

一個事務以描述符和一些數據或者block revocation鏈表開始。一個結束的事務總是以一個提交塊結束。如果沒有提交記錄(或者校驗和不匹配),事務在日志重演的時候將被丟棄。

1.21.2  數據塊頭部

日志中的每個數據塊的開頭都是一個12 bytes的數據結構 struct journal_header_s

1.21.3  超級塊

日志的超級塊比Ext4的超級塊簡單。保存在日志的超級塊中是日志的關鍵數據。日志超級塊使用數據結構struct journal_superblock_s表示,大小為1024 bytes。

1.21.4  描述數據塊Descriptor Block

Descriptor Block包含一個日志數據塊tags的數組,這些tags描述了日志中接下來的數據塊的最終位置。

日志數據塊tags具有如下格式:由數據結構struct journal_block_tag_s表示,可以是8,12,24或38bytes。

1.21.5  數據塊Data Block

存放的是通過日志寫到磁盤的數據塊。但是如果數據塊的前4 bytes與jbd2的魔數匹配,那么這些4 bytes用0代替,並且在Descriptor Block中設置escaped。

 

1.21.6  Revocation Block

 

Revocation block用於記錄本事務中的數據塊鏈表,取代任何潛在日志中的更陳舊的數據塊這樣可以加速恢復,因為陳舊的數據塊不必寫到磁盤。

Revocation block使用 structjbd2_journal_revoke_header_s結構表示

 

1.21.7  提交塊

提交快表明了一個事務已完整寫到日志。一旦提交塊到達日志,存儲在該事務中的數據可以寫到它們在磁盤中的最終位置。

提交快由數據結構struct commit_header表示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM