下圖為ext2文件系統的存儲布局。
The Second Extended File System(ext2)文件系統是Linux系統中的標准文件系統,是通過對Minix的文件系統進行擴展而得到的,其存取文件的性能極好。
在ext2文件系統中,文件由inode(包含有文件的所有信息)進行唯一標識。一個文件可能對應多個文件名,只有在所有文件名都被刪除后,該文件才會被刪除。此外,同一文件在磁盤中存放和被打開時所對應的inode是不同的,並由內核負責同步。
ext2文件系統采用三級間接塊來存儲數據塊指針,並以塊(block,默認為1KB)為單位分配空間。其磁盤分配策略是盡可能將邏輯相鄰的文件分配到磁盤上物理相鄰的塊中,並盡可能將碎片分配給盡量少的文件,以從全局上提高性能。ext2文件系統將同一目錄下的文件(包括目錄)盡可能的放在同一個塊組中,但目錄則分布在各個塊組中以實現負載均衡。在擴展文件時,會盡量一次性擴展8個連續塊給文件(以預留空間的形式實現)。
一、磁盤組織
在ext2系統中,所有元數據結構的大小均基於“塊”,而不是“扇區”。塊的大小隨文件系統的大小而有所不同。而一定數量的塊又組成一個塊組,每個塊組的起始部分有多種多樣的描述該塊組各種屬性的元數據結構。ext2系統中對各個結構的定義都包含在原始碼的include/linux/ext2_fs.h文件中。
1、超級塊
每個ext2文件系統都必須包含一個超級塊,其中存儲了該文件系統的大量基本信息,包括塊的大小、每塊組中包含的塊數等。同時,系統會對超級塊進行備份,備份被存放在塊組的第一個塊中。超級塊的起始位置為其所在分區的第1024個字節,占用1KB的空間,其結構如下:
struct ext2_super_block {
__le32 s_inodes_count; // 文件系統中inode的總數
__le32 s_blocks_count; // 文件系統中塊的總數
__le32 s_r_blocks_count; // 保留塊的總數
__le32 s_free_blocks_count; // 未使用的塊的總數(包括保留塊)
__le32 s_free_inodes_count; // 未使用的inode的總數
__le32 s_first_data_block; // 塊ID,在小於1KB的文件系統中為0,大於1KB的文件系統中為1
__le32 s_log_block_size; // 用以計算塊的大小(1024算術左移該值即為塊大小)
__le32 s_log_frag_size; // 用以計算段大小(為正則1024算術左移該值,否則右移)
__le32 s_blocks_per_group; // 每個塊組中塊的總數
__le32 s_frags_per_group; // 每個塊組中段的總數
__le32 s_inodes_per_group; // 每個塊組中inode的總數
__le32 s_mtime; // POSIX中定義的文件系統裝載時間
__le32 s_wtime; // POSIX中定義的文件系統最近被寫入的時間
__le16 s_mnt_count; // 最近一次完整校驗后被裝載的次數
__le16 s_max_mnt_count; // 在進行完整校驗前還能被裝載的次數
__le16 s_magic; // 文件系統標志,ext2中為0xEF53
__le16 s_state; // 文件系統的狀態
__le16 s_errors; // 文件系統發生錯誤時驅動程式應該執行的操作
__le16 s_minor_rev_level; // 局部修訂級別
__le32 s_lastcheck; // POSIX中定義的文件系統最近一次檢查的時間
__le32 s_checkinterval; // POSIX中定義的文件系統最近檢查的最大時間間隔
__le32 s_creator_os; // 生成該文件系統的操作系統
__le32 s_rev_level; // 修訂級別
__le16 s_def_resuid; // 報留塊的默認用戶ID
__le16 s_def_resgid; // 保留塊的默認組ID
// 僅用於使用動態inode大小的修訂版(EXT2_DYNAMIC_REV)
__le32 s_first_ino; // 標准文件的第一個可用inode的索引(非動態為11)
__le16 s_inode_size; // inode結構的大小(非動態為128)
__le16 s_block_group_nr; // 保存此超級塊的塊組號
__le32 s_feature_compat; // 兼容特性掩碼
__le32 s_feature_incompat; // 不兼容特性掩碼
__le32 s_feature_ro_compat; // 只讀特性掩碼
__u8 s_uuid[16]; // 卷ID,應盡可能使每個文件系統的格式唯一
char s_volume_name[16]; // 卷名(只能為ISO-Latin-1字符集,以’\0’結束)
char s_last_mounted[64]; // 最近被安裝的目錄
__le32 s_algorithm_usage_bitmap; // 文件系統采用的壓縮算法
// 僅在EXT2_COMPAT_PREALLOC標志被設置時有效
__u8 s_prealloc_blocks; // 預分配的塊數
__u8 s_prealloc_dir_blocks; // 給目錄預分配的塊數
__u16 s_padding1;
// 僅在EXT3_FEATURE_COMPAT_HAS_JOURNAL標志被設置時有效,用以支持日志
__u8 s_journal_uuid[16]; // 日志超級塊的卷ID
__u32 s_journal_inum; // 日志文件的inode數目
__u32 s_journal_dev; // 日志文件的設備數
__u32 s_last_orphan; // 要刪除的inode列表的起始位置
__u32 s_hash_seed[4]; // HTREE散列種子
__u8 s_def_hash_version; // 默認使用的散列函數
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; // 塊組的第一個元塊
__u32 s_reserved[190];
};
2、塊組描述符
一個塊組描述符用以描述一個塊組的屬性。塊組描述符組由若干塊組描述符組成,描述了文件系統中所有塊組的屬性,存放於超級塊所在塊的下一個塊中。一個塊組描述符的結構如下:
struct ext2_group_desc
{
__le32 bg_block_bitmap; // 塊位圖所在的第一個塊的塊ID
__le32 bg_inode_bitmap; // inode位圖所在的第一個塊的塊ID
__le32 bg_inode_table; // inode表所在的第一個塊的塊ID
__le16 bg_free_blocks_count; // 塊組中未使用的塊數
__le16 bg_free_inodes_count; // 塊組中未使用的inode數
__le16 bg_used_dirs_count; // 塊組分配的目錄的inode數
__le16 bg_pad;
__le32 bg_reserved[3];
};
3、塊位圖和inode位圖
塊位圖和inode位圖的每一位分別指出塊組中對應的那個塊或inode是否被使用。
4、inode表
一個inode占128字節,其中60個字節用於指向存放文件內容的數據塊指針。每個指針4字節,那么有15個指針。最后3個指針用分級間接尋址。 假設block為1KB。
12個直接指向,可以有12條記錄。
一級間接尋址:1024/4=256,可以有256條記錄。
二級間接尋址,可以有256*256條記錄。
三級間接尋址,可以有256*256*256條記錄。
所以對於1KB的塊大小最大可以表示(256^3+256^2+256+12)*1KB≈16GB的文件。
inode表用於跟蹤定位每個文件,包括位置、大小等(但不包括文件名),一個塊組只有一個inode表。一個inode的結構如下:
struct ext2_inode {
__le16 i_mode; // 文件格式和訪問權限
__le16 i_uid; // 文件所有者ID的低16位
__le32 i_size; // 文件字節數
__le32 i_atime; // 文件上次被訪問的時間
__le32 i_ctime; // 文件創建時間
__le32 i_mtime; // 文件被修改的時間
__le32 i_dtime; // 文件被刪除的時間(如果存在則為0)
__le16 i_gid; // 文件所有組ID的低16位
__le16 i_links_count; // 此inode被連接的次數
__le32 i_blocks; // 文件已使用和保留的總塊數(以512B為單位)
__le32 i_flags; // 此inode訪問數據時ext2的實現方式
union {
struct {
__le32 l_i_reserved1; // 保留
} linux1;
struct {
__le32 h_i_translator; // “翻譯者”標簽
} hurd1;
struct {
__le32 m_i_reserved1; // 保留
} masix1;
} osd1; // 操作系統相關數據
__le32 i_block[EXT2_N_BLOCKS]; // 定位存儲文件的塊的數組,前12個為塊號,第13個為一級間接塊號,第14個為二級間接塊號,第15個為三級間接塊號
__le32 i_generation; // 用於NFS的文件版本
__le32 i_file_acl; // 包含擴展屬性的塊號,老版本中為0
__le32 i_dir_acl; // 表示文件的“High Size”,老版本中為0
__le32 i_faddr; // 文件最后一個段的地址
union {
struct {
__u8 l_i_frag; // 段號
__u8 l_i_fsize; // 段大小
__u16 i_pad1;
__le16 l_i_uid_high; // 文件所有者ID的高16位
__le16 l_i_gid_high; // 文件所有組ID的高16位
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; // 段號
__u8 h_i_fsize; // 段大小
__le16 h_i_mode_high;
__le16 h_i_uid_high; // 文件所有者ID的高16位
__le16 h_i_gid_high; // 文件所有組ID的高16位
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; // 段號
__u8 m_i_fsize; // 段大小
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; // 操作系統相關數據
};
5、數據塊
數據塊中存放文件的內容,包括目錄表、擴展屬性、符號鏈接等。
二、目錄結構
在ext2文件系統中,目錄是作為文件存儲的。根目錄總是在inode表的第二項,而其子目錄則在根目錄文件的內容中定義。目錄項在include/linux/ext2_fs.h文件中定義,其結構如下:
struct ext2_dir_entry_2 {
__le32 inode; // 文件入口的inode號,0表示該項未使用
__le16 rec_len; // 目錄項長度
__u8 name_len; // 文件名包含的字符數
__u8 file_type; // 文件類型
char name[255]; // 文件名
};
文件的屬性大多數是位於該文件的inode結構中的標准屬性,也還包含其他一些擴展屬性(於系統中所有的inode相關,通常用於增加額外的功能),在fs/ext2/xattr.h文件中定義。
inode的i_file_acl字段中保存擴展屬性的塊的塊號。屬性頭部項位於屬性塊的起始位置,其后為屬性入口項,而屬性值能根據屬性入口項找到所在位置。
1、屬性頭部項
struct ext2_xattr_header {
__le32 h_magic; // 標識碼,為0xEA020000
__le32 h_refcount; // 屬性塊被鏈接的數目
__le32 h_blocks; // 用於擴展屬性的塊數
__le32 h_hash; // 所有屬性的哈希值
__u32 h_reserved[4];
};
2、屬性入口項
struct ext2_xattr_entry {
__u8 e_name_len; // 屬性名長度
__u8 e_name_index; // 屬性名索引
__le16 e_value_offs; // 屬性值在值塊中的偏移量
__le32 e_value_block; // 保存值的塊的塊號
__le32 e_value_size; // 屬性值長度
__le32 e_hash; // 屬性名和值的哈希值
char e_name[0]; // 屬性名
};
Ext3是一種日志式文件系統,是對ext2系統的擴展,它兼容ext2。日志式文件系統的優越性在於:由於文件系統都有快取層參與運作,如不使用時必須將文件系統卸下,以便將快取層的資料寫回磁盤中。因此每當系統要關機時,必須將其所有的文件系統全部shutdown后才能進行關機。
如果在文件系統尚未shutdown前就關機如(停電)時,下次重開機后會造成文件系統的資料不一致,故這時必須做文件系統的重整工作,將不一致與錯誤的地方修復。然而,此一重整的工作是相當耗時的,特別是容量大的文件系統,而且也不能百分之百保證所有的資料都不會流失。
為了克服此問題,使用所謂“日志式文件系統 (Journal File System)” 。此類文件系統最大的特色是,它會將整個磁盤的寫入動作完整記錄在磁盤的某個區域上,以便有需要時可以回溯追蹤。
由於資料的寫入動作包含許多的細節,像是改變文件標頭資料、搜尋磁盤可寫入空間、一個個寫入資料區段等等,每一個細節進行到一半若被中斷,就會造成文件系統的不一致,因而需要重整。
然而,在日志式文件系統中,由於詳細紀錄了每個細節,故當在某個過程中被中斷時,系統可以根據這些記錄直接回溯並重整被中斷的部分,而不必花時間去檢查其他的部分,故重整的工作速度相當快,幾乎不需要花時間。
1、高可用性
系統使用了ext3文件系統后,即使在非正常關機后,系統也不需要檢查文件系統。宕機發生后,恢復ext3文件系統的時間只要數十秒鍾。
2、數據的完整性
ext3文件系統能夠極大地提高文件系統的完整性,避免了意外宕機對文件系統的破壞。在保證數據完整性方面,ext3文件系統有2種模式可供選擇。其中之一就是“同時保持文件系統及數據的一致性”模式。采用這種方式,你永遠不再會看到由於非正常關機而存儲在磁盤上的垃圾文件。
3、文件系統的速度
盡管使用ext3文件系統時,有時在存儲數據時可能要多次寫數據,但是,從總體上看來,ext3比ext2的性能還要好一些。這是因為ext3的日志功能對磁盤的驅動器讀寫頭進行了優化。所以,文件系統的讀寫性能較之Ext2文件系統並來說,性能並沒有降低。
4、數據轉換
由ext2文件系統轉換成ext3文件系統非常容易,只要簡單地鍵入兩條命令即可完成整個轉換過程,用戶不用花時間備份、恢復、格式化分區等。用一個ext3文件系統提供的小工具tune2fs,它可以將ext2文件系統輕松轉換為ext3日志文件系統。另外,ext3文件系統可以不經任何更改,而直接加載成為ext2文件系統。
5、多種日志模式
Ext3有多種日志模式,一種工作模式是對所有的文件數據及metadata(定義文件系統中數據的數據,即數據的數據)進行日志記錄(data=journal模式);另一種工作模式則是只對metadata記錄日志,而不對數據進行日志記錄,也即所謂data=ordered或者data=writeback模式。系統管理人員可以根據系統的實際工作要求,在系統的工作速度與文件數據的一致性之間作出選擇。
Ext4(The fourth extended file system)
Ext4是一種針對ext3系統的擴展日志式文件系統,是專門為 Linux 開發的原始的擴展文件系統(ext 或 extfs)的第四版。 Linux kernel 自 2.6.28 開始正式支持新的文件系統 Ext4。 Ext4 是 Ext3 的改進版,修改了 Ext3 中部分重要的數據結構,而不僅僅像 Ext3 對 Ext2 那樣,只是增加了一個日志功能而已。Ext4 可以提供更佳的性能和可靠性,還有更為豐富的功能。
相對於Ext3,特點如下:
1. 與 Ext3 兼容。 執行若干條命令,就能從 Ext3 在線遷移到 Ext4,而無須重新格式化磁盤或重新安裝系統。原有 Ext3 數據結構照樣保留,Ext4 作用於新數據,當然,整個文件系統因此也就獲得了 Ext4 所支持的更大容量。
2. 更大的文件系統和更大的文件。 較之 Ext3 目前所支持的最大 16TB 文件系統和最大 2TB 文件,Ext4 分別支持 1EB(1,048,576TB, 1EB=1024PB, 1PB=1024TB)的文件系統,以及 16TB 的文件。
3. 無限數量的子目錄。 Ext3 目前只支持 32,000 個子目錄,而 Ext4 支持無限數量的子目錄。
4. Extents。 Ext3 采用間接塊映射,當操作大文件時,效率極其低下。比如一個 100MB 大小的文件,在 Ext3 中要建立 25,600 個數據塊(每個數據塊大小為 4KB)的映射表。而 Ext4 引入了現代文件系統中流行的 extents 概念,每個 extent 為一組連續的數據塊,上述文件則表示為“該文件數據保存在接下來的 25,600 個數據塊中”,提高了不少效率。
5. 多塊分配。 當 寫入數據到 Ext3 文件系統中時,Ext3 的數據塊分配器每次只能分配一個 4KB 的塊,寫一個 100MB 文件就要調用 25,600 次數據塊分配器,而 Ext4 的多塊分配器“multiblock allocator”(mballoc) 支持一次調用分配多個數據塊。
6. 延遲分配。 Ext3 的數據塊分配策略是盡快分配,而 Ext4 和其它現代文件操作系統的策略是盡可能地延遲分配,直到文件在 cache 中寫完才開始分配數據塊並寫入磁盤,這樣就能優化整個文件的數據塊分配,與前兩種特性搭配起來可以顯著提升性能。
7. 快速 fsck。 以前執行 fsck 第一步就會很慢,因為它要檢查所有的 inode,現在 Ext4 給每個組的 inode 表中都添加了一份未使用 inode 的列表,今后 fsck Ext4 文件系統就可以跳過它們而只去檢查那些在用的 inode 了。
8. 日志校驗。 日志是最常用的部分,也極易導致磁盤硬件故障,而從損壞的日志中恢復數據會導致更多的數據損壞。Ext4 的日志校驗功能可以很方便地判斷日志數據是否損壞,而且它將 Ext3 的兩階段日志機制合並成一個階段,在增加安全性的同時提高了性能。
9. “無日志”(No Journaling)模式。 日志總歸有一些開銷,Ext4 允許關閉日志,以便某些有特殊需求的用戶可以借此提升性能。
10. 在線碎片整理。 盡管延遲分配、多塊分配和 extents 能有效減少文件系統碎片,但碎片還是不可避免會產生。Ext4 支持在線碎片整理,並將提供 e4defrag 工具進行個別文件或整個文件系統的碎片整理。
11. inode 相關特性。 Ext4 支持更大的 inode,較之 Ext3 默認的 inode 大小 128 字節,Ext4 為了在 inode 中容納更多的擴展屬性(如納秒時間戳或 inode 版本),默認 inode 大小為 256 字節。Ext4 還支持快速擴展屬性(fast extended attributes)和 inode 保留(inodes reservation)。
12. 持久預分配(Persistent preallocation)。 P2P 軟件為了保證下載文件有足夠的空間存放,常常會預先創建一個與所下載文件大小相同的空文件,以免未來的數小時或數天之內磁盤空間不足導致下載失敗。 Ext4 在文件系統層面實現了持久預分配並提供相應的 API(libc 中的 posix_fallocate()),比應用軟件自己實現更有效率。
13. 默認啟用 barrier。 磁 盤上配有內部緩存,以便重新調整批量數據的寫操作順序,優化寫入性能,因此文件系統必須在日志數據寫入磁盤之后才能寫 commit 記錄,若 commit 記錄寫入在先,而日志有可能損壞,那么就會影響數據完整性。Ext4 默認啟用 barrier,只有當 barrier 之前的數據全部寫入磁盤,才能寫 barrier 之后的數據。(可通過 "mount -o barrier=0" 命令禁用該特性。)