4. 動態結構¶
動態元資料是在將檔案和塊分配給檔案時即時建立的。
4.1. 索引節點¶
在常規的 UNIX 檔案系統中,inode 儲存與檔案相關的所有元資料(時間戳、塊對映、擴充套件屬性等),而不是目錄條目。要查詢與檔案關聯的資訊,必須遍歷目錄檔案以查詢與檔案關聯的目錄條目,然後載入 inode 以查詢該檔案的元資料。ext4 似乎透過在目錄條目中儲存檔案型別的副本(通常儲存在 inode 中)來作弊(出於效能原因)。(將所有這些與 FAT 進行比較,FAT 將所有檔案資訊直接儲存在目錄條目中,但不支援硬連結,並且由於其更簡單的塊分配器和對連結串列的廣泛使用,通常比 ext4 更容易進行尋道。)
inode 表是 struct ext4_inode 的線性陣列。表的大小設定為具有足夠的塊來儲存至少 sb.s_inode_size * sb.s_inodes_per_group 位元組。包含 inode 的塊組的編號可以計算為 (inode_number - 1) / sb.s_inodes_per_group,並且組的表中的偏移量為 (inode_number - 1) % sb.s_inodes_per_group。沒有 inode 0。
inode 校驗和是根據 FS UUID、inode 編號和 inode 結構本身計算的。
inode 表條目以 struct ext4_inode 形式佈局。
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le16 |
i_mode |
檔案模式。請參閱下面的 i_mode 表。 |
0x2 |
__le16 |
i_uid |
所有者 UID 的低 16 位。 |
0x4 |
__le32 |
i_size_lo |
以位元組為單位的大小的低 32 位。 |
0x8 |
__le32 |
i_atime |
上次訪問時間,以自 epoch 以來的秒數為單位。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含該值的校驗和。 |
0xC |
__le32 |
i_ctime |
上次 inode 更改時間,以自 epoch 以來的秒數為單位。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含屬性值的引用計數的低 32 位。 |
0x10 |
__le32 |
i_mtime |
上次資料修改時間,以自 epoch 以來的秒數為單位。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含擁有擴充套件屬性的 inode 的編號。 |
0x14 |
__le32 |
i_dtime |
刪除時間,以自 epoch 以來的秒數為單位。 |
0x18 |
__le16 |
i_gid |
GID 的低 16 位。 |
0x1A |
__le16 |
i_links_count |
硬連結計數。通常,ext4 不允許 inode 具有超過 65,000 個硬連結。這適用於檔案和目錄,這意味著一個目錄中不能有超過 64,998 個子目錄(每個子目錄的“..”條目計為一個硬連結,目錄本身的“.”條目也計為一個硬連結)。啟用 DIR_NLINK 功能後,ext4 透過將此欄位設定為 1 來指示硬連結的數量未知,從而支援超過 64,998 個子目錄。 |
0x1C |
__le32 |
i_blocks_lo |
“塊”計數的低 32 位。如果在檔案系統上未設定 huge_file 功能標誌,則該檔案在磁碟上佔用 |
0x20 |
__le32 |
i_flags |
Inode 標誌。請參閱下面的 i_flags 表。 |
0x24 |
4 位元組 |
i_osd1 |
有關更多詳細資訊,請參見 i_osd1 表。 |
0x28 |
60 位元組 |
i_block[EXT4_N_BLOCKS=15] |
塊對映或 extent 樹。請參閱“inode.i_block 的內容”部分。 |
0x64 |
__le32 |
i_generation |
檔案版本(對於 NFS)。 |
0x68 |
__le32 |
i_file_acl_lo |
擴充套件屬性塊的低 32 位。當然,ACL 只是許多可能的擴充套件屬性之一;我認為此欄位的名稱是擴充套件屬性的第一個用途是用於 ACL 的結果。 |
0x6C |
__le32 |
i_size_high / i_dir_acl |
檔案/目錄大小的高 32 位。在 ext2/3 中,此欄位名為 i_dir_acl,儘管它通常設定為零並且從未使用過。 |
0x70 |
__le32 |
i_obso_faddr |
(已過時)片段地址。 |
0x74 |
12 位元組 |
i_osd2 |
有關更多詳細資訊,請參見 i_osd2 表。 |
0x80 |
__le16 |
i_extra_isize |
此 inode 的大小 - 128。或者,原始 ext2 inode 之外的擴充套件 inode 欄位的大小,包括此欄位。 |
0x82 |
__le16 |
i_checksum_hi |
inode 校驗和的高 16 位。 |
0x84 |
__le32 |
i_ctime_extra |
額外的更改時間位。這提供了亞秒級的精度。請參閱 Inode 時間戳部分。 |
0x88 |
__le32 |
i_mtime_extra |
額外的修改時間位。這提供了亞秒級的精度。 |
0x8C |
__le32 |
i_atime_extra |
額外的訪問時間位。這提供了亞秒級的精度。 |
0x90 |
__le32 |
i_crtime |
檔案建立時間,以自 epoch 以來的秒數為單位。 |
0x94 |
__le32 |
i_crtime_extra |
額外的檔案建立時間位。這提供了亞秒級的精度。 |
0x98 |
__le32 |
i_version_hi |
版本號的高 32 位。 |
0x9C |
__le32 |
i_projid |
專案 ID。 |
i_mode 值是以下標誌的組合
值 |
描述 |
|---|---|
0x1 |
S_IXOTH(其他人可以執行) |
0x2 |
S_IWOTH(其他人可以寫入) |
0x4 |
S_IROTH(其他人可以讀取) |
0x8 |
S_IXGRP(組成員可以執行) |
0x10 |
S_IWGRP(組成員可以寫入) |
0x20 |
S_IRGRP(組成員可以讀取) |
0x40 |
S_IXUSR(所有者可以執行) |
0x80 |
S_IWUSR(所有者可以寫入) |
0x100 |
S_IRUSR(所有者可以讀取) |
0x200 |
S_ISVTX(粘滯位) |
0x400 |
S_ISGID(設定 GID) |
0x800 |
S_ISUID(設定 UID) |
這些是互斥的檔案型別 |
|
0x1000 |
S_IFIFO(FIFO) |
0x2000 |
S_IFCHR(字元裝置) |
0x4000 |
S_IFDIR(目錄) |
0x6000 |
S_IFBLK(塊裝置) |
0x8000 |
S_IFREG(常規檔案) |
0xA000 |
S_IFLNK(符號連結) |
0xC000 |
S_IFSOCK(套接字) |
i_flags 欄位是這些值的組合
值 |
描述 |
|---|---|
0x1 |
此檔案需要安全刪除 (EXT4_SECRM_FL)。(未實現) |
0x2 |
如果需要取消刪除,則應保留此檔案 (EXT4_UNRM_FL)。(未實現) |
0x4 |
檔案已壓縮 (EXT4_COMPR_FL)。(未真正實現) |
0x8 |
對檔案的所有寫入必須是同步的 (EXT4_SYNC_FL)。 |
0x10 |
檔案是不可變的 (EXT4_IMMUTABLE_FL)。 |
0x20 |
檔案只能追加 (EXT4_APPEND_FL)。 |
0x40 |
dump(1) 實用程式不應轉儲此檔案 (EXT4_NODUMP_FL)。 |
0x80 |
不要更新訪問時間 (EXT4_NOATIME_FL)。 |
0x100 |
髒壓縮檔案 (EXT4_DIRTY_FL)。 (未使用) |
0x200 |
檔案具有一個或多個壓縮簇 (EXT4_COMPRBLK_FL)。 (未使用) |
0x400 |
不要壓縮檔案 (EXT4_NOCOMPR_FL)。 (未使用) |
0x800 |
加密的 inode (EXT4_ENCRYPT_FL)。此位值以前是 EXT4_ECOMPR_FL(壓縮錯誤),從未被使用過。 |
0x1000 |
目錄具有雜湊索引 (EXT4_INDEX_FL)。 |
0x2000 |
AFS 魔術目錄 (EXT4_IMAGIC_FL)。 |
0x4000 |
檔案資料必須始終透過日誌寫入 (EXT4_JOURNAL_DATA_FL)。 |
0x8000 |
不應合併檔案尾部 (EXT4_NOTAIL_FL)。 (ext4 未使用) |
0x10000 |
所有目錄條目資料都應同步寫入(請參見 |
0x20000 |
目錄層次結構的頂部 (EXT4_TOPDIR_FL)。 |
0x40000 |
這是一個巨大的檔案 (EXT4_HUGE_FILE_FL)。 |
0x80000 |
Inode 使用 extent (EXT4_EXTENTS_FL)。 |
0x100000 |
Verity 保護的檔案 (EXT4_VERITY_FL)。 |
0x200000 |
Inode 在其資料塊中儲存一個大的擴充套件屬性值 (EXT4_EA_INODE_FL)。 |
0x400000 |
此檔案已在 EOF 之後分配了塊 (EXT4_EOFBLOCKS_FL)。 (已棄用) |
0x01000000 |
Inode 是快照 ( |
0x04000000 |
正在刪除快照 ( |
0x08000000 |
快照收縮已完成 ( |
0x10000000 |
Inode 具有內聯資料 (EXT4_INLINE_DATA_FL)。 |
0x20000000 |
使用相同的專案 ID 建立子級 (EXT4_PROJINHERIT_FL)。 |
0x80000000 |
為 ext4 庫保留 (EXT4_RESERVED_FL)。 |
聚合標誌 |
|
0x705BDFFF |
使用者可見的標誌。 |
0x604BC0FF |
使用者可修改的標誌。請注意,雖然可以使用 setattr 設定 EXT4_JOURNAL_DATA_FL 和 EXT4_EXTENTS_FL,但它們不在核心的 EXT4_FL_USER_MODIFIABLE 掩碼中,因為它需要以特殊方式處理這些標誌的設定,並且它們已從直接儲存到 i_flags 的標誌集中遮蔽掉。 |
osd1 欄位具有多個含義,具體取決於建立者
Linux
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
l_i_version |
Inode 版本。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含屬性值的引用計數的高 32 位。 |
Hurd
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
h_i_translator |
?? |
Masix
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
m_i_reserved |
?? |
osd2 欄位具有多個含義,具體取決於檔案系統建立者
Linux
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le16 |
l_i_blocks_high |
塊計數的高 16 位。請參閱附加到 i_blocks_lo 的註釋。 |
0x2 |
__le16 |
l_i_file_acl_high |
擴充套件屬性塊的高 16 位(歷史上是檔案 ACL 位置)。請參見下面的擴充套件屬性部分。 |
0x4 |
__le16 |
l_i_uid_high |
所有者 UID 的高 16 位。 |
0x6 |
__le16 |
l_i_gid_high |
GID 的高 16 位。 |
0x8 |
__le16 |
l_i_checksum_lo |
inode 校驗和的低 16 位。 |
0xA |
__le16 |
l_i_reserved |
未使用。 |
Hurd
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le16 |
h_i_reserved1 |
?? |
0x2 |
__u16 |
h_i_mode_high |
檔案模式的高 16 位。 |
0x4 |
__le16 |
h_i_uid_high |
所有者 UID 的高 16 位。 |
0x6 |
__le16 |
h_i_gid_high |
GID 的高 16 位。 |
0x8 |
__u32 |
h_i_author |
作者程式碼? |
Masix
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le16 |
h_i_reserved1 |
?? |
0x2 |
__u16 |
m_i_file_acl_high |
擴充套件屬性塊的高 16 位(歷史上是檔案 ACL 位置)。 |
0x4 |
__u32 |
m_i_reserved2[2] |
?? |
4.1.1. Inode 大小¶
在 ext2 和 ext3 中,inode 結構大小固定為 128 位元組 (EXT2_GOOD_OLD_INODE_SIZE),並且每個 inode 的磁碟記錄大小為 128 位元組。從 ext4 開始,可以在格式化時為檔案系統中的所有 inode 分配一個更大的磁碟 inode,以便在原始 ext2 inode 的末尾之外提供空間。磁碟上的 inode 記錄大小記錄在超級塊中,為 s_inode_size。struct ext4_inode 實際使用的位元組數超過原始 128 位元組的 ext2 inode 記錄在每個 inode 的 i_extra_isize 欄位中,這允許 struct ext4_inode 為新核心增長,而無需升級所有磁碟上的 inode。對 EXT2_GOOD_OLD_INODE_SIZE 之外的欄位的訪問應驗證是否在 i_extra_isize 範圍內。預設情況下,ext4 inode 記錄為 256 位元組,並且(截至 2019 年 8 月)inode 結構為 160 位元組 (i_extra_isize = 32)。inode 結構末尾和 inode 記錄末尾之間的額外空間可用於儲存擴充套件屬性。每個 inode 記錄可以與檔案系統塊大小一樣大,儘管這效率不高。
4.1.2. 查詢 Inode¶
每個塊組包含 sb->s_inodes_per_group 個 inode。因為 inode 0 被定義為不存在,所以可以使用以下公式來查詢 inode 所在的塊組:bg = (inode_num - 1) / sb->s_inodes_per_group。可以在塊組的 inode 表中找到特定的 inode,其位置為 index = (inode_num - 1) % sb->s_inodes_per_group。要獲取 inode 表中的位元組地址,請使用 offset = index * sb->s_inode_size。
4.1.3. Inode 時間戳¶
在 inode 結構的低 128 位元組中記錄了四個時間戳——inode 更改時間 (ctime)、訪問時間 (atime)、資料修改時間 (mtime) 和刪除時間 (dtime)。這四個欄位是 32 位有符號整數,表示自 Unix epoch(1970-01-01 00:00:00 GMT)以來的秒數,這意味著這些欄位將在 2038 年 1 月溢位。如果檔案系統沒有 orphan_file 功能,則未從任何目錄連結但仍處於開啟狀態的 inode(孤立 inode)會將 dtime 欄位過載以用於孤立列表。超級塊欄位 s_last_orphan 指向孤立列表中的第一個 inode;然後 dtime 是下一個孤立 inode 的編號,如果沒有更多孤立 inode,則為零。
如果 inode 結構大小 sb->s_inode_size 大於 128 位元組並且 i_inode_extra 欄位足夠大以包含相應的 i_[cma]time_extra 欄位,則 ctime、atime 和 mtime inode 欄位將擴充套件為 64 位。在這個“extra” 32 位欄位中,較低的兩位用於將 32 位秒欄位擴充套件為 34 位寬;較高的 30 位用於提供納秒級的時間戳精度。因此,時間戳直到 2446 年 5 月才會溢位。dtime 沒有擴充套件。還有一個第五個時間戳來記錄 inode 建立時間 (crtime);此欄位為 64 位寬,並以與 64 位 [cma]time 相同的方式解碼。crtime 和 dtime 都無法透過常規的 stat() 介面訪問,但 debugfs 會報告它們。
我們使用 32 位有符號時間值加上 (2^32 * (額外 epoch 位))。換句話說
額外的 epoch 位 |
32 位時間的 MSB |
將有符號 32 位調整為 64 位 tv_sec |
解碼後的 64 位 tv_sec |
有效時間範圍 |
|---|---|---|---|---|
0 0 |
1 |
0 |
|
1901-12-13 至 1969-12-31 |
0 0 |
0 |
0 |
|
1970-01-01 至 2038-01-19 |
0 1 |
1 |
0x100000000 |
|
2038-01-19 至 2106-02-07 |
0 1 |
0 |
0x100000000 |
|
2106-02-07 至 2174-02-25 |
1 0 |
1 |
0x200000000 |
|
2174-02-25 至 2242-03-16 |
1 0 |
0 |
0x200000000 |
|
2242-03-16 至 2310-04-04 |
1 1 |
1 |
0x300000000 |
|
2310-04-04 至 2378-04-22 |
1 1 |
0 |
0x300000000 |
|
2378-04-22 至 2446-05-10 |
這是一種有些奇怪的編碼,因為正值的數量實際上是負值的七倍。解碼和編碼超出 2038 年的日期也存在長期存在的錯誤,截至核心 3.12 和 e2fsprogs 1.42.8,這些錯誤似乎尚未修復。 64 位核心錯誤地使用額外的 epoch 位 1,1 來表示 1901 年到 1970 年之間的日期。在某個時候,核心將被修復,e2fsck 將修復這種情況,前提是在 2310 年之前執行它。
4.2. inode.i_block 的內容¶
根據 inode 描述的檔案型別,inode.i_block 中的 60 個位元組的儲存空間可以以不同的方式使用。一般來說,常規檔案和目錄將使用它來儲存檔案塊索引資訊,而特殊檔案將使用它來儲存特殊用途。
4.2.1. 符號連結¶
如果目標字串小於 60 個位元組,符號連結的目標將儲存在此欄位中。否則,將使用 extent 或塊對映來分配資料塊以儲存連結目標。
4.2.2. 直接/間接塊定址¶
在 ext2/3 中,檔案塊號透過(最多)三級 1-1 塊對映對映到邏輯塊號。要查詢儲存特定檔案塊的邏輯塊,程式碼將遍歷這個越來越複雜的結構。請注意,既沒有幻數也沒有校驗和來提供任何程度的信心,證明該塊不是充滿了垃圾。
i.i_block 偏移量 |
它指向哪裡 |
||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 到 11 |
直接對映到檔案塊 0 到 11。 |
||||||||||||
12 |
間接塊:(檔案塊 12 到 (
|
||||||||||||
13 |
雙重間接塊:(檔案塊
|
||||||||||||
14 |
三重間接塊:(檔案塊 (
|
請注意,使用這種塊對映方案,即使對於一個大的連續檔案,也需要填寫大量的對映資料!這種效率低下導致了 extent 對映方案的建立,如下所述。
另請注意,使用此對映方案的檔案不能放置在高於 2^32 個塊的位置。
4.2.3. Extent 樹¶
在 ext4 中,檔案到邏輯塊的對映已被 extent 樹取代。在舊方案下,分配 1,000 個塊的連續執行需要一個間接塊來對映所有 1,000 個條目;使用 extent,對映減少到單個 struct ext4_extent,其中 ee_len = 1000。如果啟用了 flex_bg,則可以使用單個 extent 分配非常大的檔案,從而大大減少元資料塊的使用,並提高磁碟效率。inode 必須設定 extent 標誌 (0x80000) 才能使用此功能。
Extent 被排列成樹。樹的每個節點都以 struct ext4_extent_header 開頭。如果節點是內部節點 (eh.eh_depth > 0),則標頭後跟 eh.eh_entries 個 struct ext4_extent_idx 的例項;這些索引條目中的每一個都指向一個包含 extent 樹中更多節點的塊。如果節點是葉節點 (eh.eh_depth == 0),則標頭後跟 eh.eh_entries 個 struct ext4_extent 的例項;這些例項指向檔案的資料塊。extent 樹的根節點儲存在 inode.i_block 中,這允許記錄前四個 extent,而無需使用額外的元資料塊。
extent 樹標頭記錄在 struct ext4_extent_header 中,長度為 12 位元組
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le16 |
eh_magic |
幻數,0xF30A。 |
0x2 |
__le16 |
eh_entries |
標頭後有效條目的數量。 |
0x4 |
__le16 |
eh_max |
可以跟隨標頭的最大條目數。 |
0x6 |
__le16 |
eh_depth |
extent 節點在 extent 樹中的深度。 0 = 此 extent 節點指向資料塊;否則,此 extent 節點指向其他 extent 節點。 extent 樹最多可以有 5 層深:邏輯塊號最多可以是 |
0x8 |
__le32 |
eh_generation |
樹的世代。(由 Lustre 使用,但不是標準 ext4)。 |
extent 樹的內部節點,也稱為索引節點,記錄為 struct ext4_extent_idx,長度為 12 位元組
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
ei_block |
此索引節點涵蓋從“塊”開始的檔案塊。 |
0x4 |
__le32 |
ei_leaf_lo |
樹中下一層較低的 extent 節點的塊號的低 32 位。 指向的樹節點可以是另一個內部節點,也可以是葉節點,如下所述。 |
0x8 |
__le16 |
ei_leaf_hi |
前一個欄位的高 16 位。 |
0xA |
__u16 |
ei_unused |
extent 樹的葉節點記錄為 struct ext4_extent,長度也為 12 位元組
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
ee_block |
此 extent 涵蓋的第一個檔案塊號。 |
0x4 |
__le16 |
ee_len |
extent 覆蓋的塊數。如果此欄位的值 <= 32768,則 extent 已初始化。如果該欄位的值 > 32768,則 extent 未初始化,實際的 extent 長度為 |
0x6 |
__le16 |
ee_start_hi |
此 extent 指向的塊號的高 16 位。 |
0x8 |
__le32 |
ee_start_lo |
此 extent 指向的塊號的低 32 位。 |
在引入元資料校驗和之前,extent 標頭 + extent 條目始終在每個 extent 樹資料塊的末尾留下至少 4 個位元組的未分配空間(因為 (2^x % 12) >= 4)。因此,32 位校驗和被插入到這個空間中。 inode 中的 4 個 extent 不需要校驗和,因為 inode 已經過校驗和。校驗和是根據 FS UUID、inode 編號、inode 世代和直到(但不包括)校驗和本身的整個 extent 塊計算得出的。
struct ext4_extent_tail 長度為 4 位元組
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
eb_checksum |
extent 塊的校驗和,crc32c(uuid+inum+igeneration+extentblock) |
4.2.4. 內聯資料¶
如果為檔案系統啟用了內聯資料功能,並且為 inode 設定了標誌,則可以將檔案資料的前 60 個位元組儲存在此處。
4.3. 目錄條目¶
在 ext4 檔案系統中,目錄或多或少是一個平面檔案,它將任意位元組字串(通常是 ASCII)對映到檔案系統上的 inode 編號。檔案系統中可能有許多目錄條目引用相同的 inode 編號——這些條目稱為硬連結,這也是硬連結無法引用其他檔案系統上的檔案的原因。因此,透過讀取與目錄檔案關聯的資料塊以查詢所需的特定目錄條目來找到目錄條目。
4.3.1. 線性(經典)目錄¶
預設情況下,每個目錄都以“幾乎線性”的陣列列出其條目。 我寫“幾乎”是因為它不是記憶體意義上的線性陣列,因為目錄條目不會跨檔案系統塊拆分。 因此,更準確的說法是,目錄是一系列資料塊,每個塊都包含一個線性目錄條目陣列。 每個每個塊陣列的結尾都透過到達塊的末尾來表示; 塊中的最後一個條目的記錄長度使其一直延伸到塊的末尾。 整個目錄的結尾當然是透過到達檔案結尾來表示的。 未使用的目錄條目由 inode = 0 表示。 預設情況下,檔案系統使用 struct ext4_dir_entry_2 作為目錄條目,除非未設定“filetype”功能標誌,在這種情況下,它使用 struct ext4_dir_entry。
原始目錄條目格式是 struct ext4_dir_entry,最多 263 位元組長,儘管在磁碟上您需要引用 dirent.rec_len 才能確定。
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
inode |
此目錄條目指向的 inode 的編號。 |
0x4 |
__le16 |
rec_len |
此目錄條目的長度。 必須是 4 的倍數。 |
0x6 |
__le16 |
name_len |
檔名的長度。 |
0x8 |
char |
name[EXT4_NAME_LEN] |
檔名。 |
由於檔名不能超過 255 個位元組,因此新的目錄條目格式縮短了 name_len 欄位,並將該空間用於檔案型別標誌,可能是為了避免在目錄樹遍歷期間必須載入每個 inode。 此格式為 ext4_dir_entry_2,最多 263 位元組長,儘管在磁碟上您需要引用 dirent.rec_len 才能確定。
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
inode |
此目錄條目指向的 inode 的編號。 |
0x4 |
__le16 |
rec_len |
此目錄條目的長度。 |
0x6 |
__u8 |
name_len |
檔名的長度。 |
0x7 |
__u8 |
file_type |
檔案型別程式碼,請參見下面的 ftype 表。 |
0x8 |
char |
name[EXT4_NAME_LEN] |
檔名。 |
目錄檔案型別是以下值之一
值 |
描述 |
|---|---|
0x0 |
未知。 |
0x1 |
常規檔案。 |
0x2 |
目錄。 |
0x3 |
字元裝置檔案。 |
0x4 |
塊裝置檔案。 |
0x5 |
FIFO。 |
0x6 |
套接字。 |
0x7 |
符號連結。 |
為了支援既加密又區分大小寫的目錄,我們還必須在目錄條目中包含雜湊資訊。 我們將 ext4_extended_dir_entry_2 附加到 ext4_dir_entry_2,但點和點點的條目除外,它們保持不變。 該結構緊跟在 name 之後,幷包含在 rec_len 列出的大小中。 如果目錄條目使用此擴充套件,則最多可為 271 位元組。
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
hash |
目錄名稱的雜湊值 |
0x4 |
__le32 |
minor_hash |
目錄名稱的次要雜湊值 |
為了將校驗和新增到這些經典目錄塊,一個偽造的 struct ext4_dir_entry 被放置在每個葉塊的末尾以儲存校驗和。 目錄條目長 12 位元組。 inode 編號和 name_len 欄位設定為零,以欺騙舊軟體忽略表面上為空的目錄條目,並且校驗和儲存在名稱通常所在的位置。 該結構為 struct ext4_dir_entry_tail
偏移量 |
大小 |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
det_reserved_zero1 |
Inode 編號,必須為零。 |
0x4 |
__le16 |
det_rec_len |
此目錄條目的長度,必須為 12。 |
0x6 |
__u8 |
det_reserved_zero2 |
檔名長度,必須為零。 |
0x7 |
__u8 |
det_reserved_ft |
檔案型別,必須為 0xDE。 |
0x8 |
__le32 |
det_checksum |
目錄葉塊校驗和。 |
葉目錄塊校驗和是針對 FS UUID、目錄的 inode 編號、目錄的 inode 世代編號和整個目錄條目塊(直到但不包括)偽造的目錄條目計算得出的。
4.3.2. 雜湊樹目錄¶
目錄條目的線性陣列對於效能而言並不是很好,因此向 ext3 添加了一個新功能,以提供基於目錄條目名稱雜湊的更快(但很奇怪)的平衡樹。 如果 inode 中設定了 EXT4_INDEX_FL (0x1000) 標誌,則此目錄使用雜湊 btree (htree) 來組織和查詢目錄條目。 為了向後與 ext2 只讀相容,此樹實際上隱藏在目錄檔案中,偽裝成“空”目錄資料塊! 先前說過,線性目錄條目表的末尾用指向 inode 0 的條目表示; 這(被濫用)來欺騙舊的線性掃描演算法,使其認為目錄塊的其餘部分是空的,從而繼續前進。
樹的根始終位於目錄的第一個資料塊中。 根據 ext2 慣例,“.”和“..”條目必須出現在第一個塊的開頭,因此將它們作為兩個 struct ext4_dir_entry_2 s 放置在此處,而不是儲存在樹中。 根節點的其餘部分包含有關樹的元資料,最後包含雜湊 -> 塊對映以查詢 htree 中較低的節點。 如果 dx_root.info.indirect_levels 為非零,則 htree 有兩層; 根節點的對映指向的資料塊是一個內部節點,該節點由次要雜湊索引。 此樹中的內部節點包含一個歸零的 struct ext4_dir_entry_2,後跟一個次要雜湊 -> 塊對映以查詢葉節點。 葉節點包含所有 struct ext4_dir_entry_2 的線性陣列; 所有這些條目(大概)都雜湊到相同的值。 如果存在溢位,則條目會簡單地溢位到下一個葉節點中,並且(在內部節點對映中)獲得此下一個葉節點的雜湊的最低有效位已設定。
要將目錄作為 htree 遍歷,程式碼會計算所需檔名的雜湊值,並使用它來查詢相應的塊號。 如果樹是平坦的,則該塊是可以搜尋的目錄條目的線性陣列; 否則,將計算檔名的次要雜湊,並將其與第二個塊進行比較以查詢相應的第三個塊號。 第三個塊號將是目錄條目的線性陣列。
要將目錄作為線性陣列遍歷(例如舊程式碼),程式碼只需讀取目錄中的每個資料塊。 用於 htree 的塊將看起來沒有條目(除了“.”和“..”),因此只有葉節點似乎具有任何有趣的內容。
htree 的根在 struct dx_root 中,該結構是資料塊的完整長度
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
dot.inode |
此目錄的 inode 編號。 |
0x4 |
__le16 |
dot.rec_len |
此記錄的長度,12。 |
0x6 |
u8 |
dot.name_len |
名稱的長度,1。 |
0x7 |
u8 |
dot.file_type |
此條目的檔案型別,0x2(目錄)(如果設定了功能標誌)。 |
0x8 |
char |
dot.name[4] |
“.000” |
0xC |
__le32 |
dotdot.inode |
父目錄的 inode 編號。 |
0x10 |
__le16 |
dotdot.rec_len |
block_size - 12。 記錄長度足夠長,可以覆蓋所有 htree 資料。 |
0x12 |
u8 |
dotdot.name_len |
名稱的長度,2。 |
0x13 |
u8 |
dotdot.file_type |
此條目的檔案型別,0x2(目錄)(如果設定了功能標誌)。 |
0x14 |
char |
dotdot_name[4] |
“..00” |
0x18 |
__le32 |
struct dx_root_info.reserved_zero |
零。 |
0x1C |
u8 |
struct dx_root_info.hash_version |
雜湊型別,請參見下面的 dirhash 表。 |
0x1D |
u8 |
struct dx_root_info.info_length |
樹資訊的長度,0x8。 |
0x1E |
u8 |
struct dx_root_info.indirect_levels |
htree 的深度。 如果設定了 INCOMPAT_LARGEDIR 功能,則不能大於 3; 否則不能大於 2。 |
0x1F |
u8 |
struct dx_root_info.unused_flags |
|
0x20 |
__le16 |
limit |
可以跟隨此標頭的 dx_entries 的最大數量,外加 1 個標頭本身。 |
0x22 |
__le16 |
count |
跟隨此標頭的 dx_entries 的實際數量,外加 1 個標頭本身。 |
0x24 |
__le32 |
block |
與 hash=0 一起的塊號(在目錄檔案中)。 |
0x28 |
struct dx_entry |
entries[0] |
儘可能多的 8 位元組 |
目錄雜湊是以下值之一
值 |
描述 |
|---|---|
0x0 |
舊版。 |
0x1 |
半 MD4。 |
0x2 |
Tea。 |
0x3 |
舊版,無符號。 |
0x4 |
半 MD4,無符號。 |
0x5 |
Tea,無簽名。 |
0x6 |
Siphash。 |
htree 的內部節點記錄為 struct dx_node,它也是資料塊的完整長度
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
fake.inode |
零,使其看起來好像未使用此條目。 |
0x4 |
__le16 |
fake.rec_len |
塊的大小,以隱藏所有 dx_node 資料。 |
0x6 |
u8 |
name_len |
零。 此“未使用”目錄條目沒有名稱。 |
0x7 |
u8 |
file_type |
零。 此“未使用”目錄條目沒有檔案型別。 |
0x8 |
__le16 |
limit |
可以跟隨此標頭的 dx_entries 的最大數量,外加 1 個標頭本身。 |
0xA |
__le16 |
count |
跟隨此標頭的 dx_entries 的實際數量,外加 1 個標頭本身。 |
0xE |
__le32 |
block |
與此塊的最低雜湊值一起使用的塊號(在目錄檔案中)。 此值儲存在父塊中。 |
0x12 |
struct dx_entry |
entries[0] |
儘可能多的 8 位元組 |
存在於 struct dx_root 和 struct dx_node 中的雜湊對映記錄為 struct dx_entry,長度為 8 位元組
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
hash |
雜湊碼。 |
0x4 |
__le32 |
block |
htree 中下一個節點的塊號(在目錄檔案中,而不是檔案系統塊)。 |
(如果您認為這一切都非常聰明和奇怪,作者也是這麼認為的。)
如果啟用了元資料校驗和,則目錄塊的最後 8 個位元組(恰好是一個 dx_entry 的長度)用於儲存 struct dx_tail,其中包含校驗和。 根據需要調整 dx_root/dx_node 結構中的 limit 和 count 條目,以將 dx_tail 放入塊中。 如果沒有空間用於 dx_tail,則會通知使用者執行 e2fsck -D 以重建目錄索引(這將確保有空間用於校驗和。 dx_tail 結構長 8 個位元組,如下所示
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
u32 |
dt_reserved |
零。 |
0x4 |
__le32 |
dt_checksum |
htree 目錄塊的校驗和。 |
校驗和是針對 FS UUID、htree 索引標頭(dx_root 或 dx_node)、所有正在使用的 htree 索引 (dx_entry) 和尾塊 (dx_tail) 計算得出的。
4.4. 擴充套件屬性¶
擴充套件屬性 (xattrs) 通常儲存在磁碟上的單獨資料塊中,並透過 inode.i_file_acl* 從 inode 引用。 擴充套件屬性的第一個用途似乎是儲存檔案 ACL 和其他安全資料 (selinux)。 使用 user_xattr 掛載選項,使用者可以儲存擴充套件屬性,只要所有屬性名稱都以“user”開頭即可; 從 Linux 3.0 開始,此限制似乎已消失。
可以在兩個地方找到擴充套件屬性。 第一個地方是在每個 inode 條目的末尾和下一個 inode 條目的開頭之間。 例如,如果 inode.i_extra_isize = 28 且 sb.inode_size = 256,則有 256 - (128 + 28) = 100 個位元組可用於 inode 內擴充套件屬性儲存。 可以在其中找到擴充套件屬性的第二個位置是在 inode.i_file_acl 指向的塊中。 從 Linux 3.11 開始,此塊不可能包含指向第二個擴充套件屬性塊(甚至群集的其餘塊)的指標。 理論上,每個屬性的值都可以儲存在單獨的資料塊中,但從 Linux 3.11 開始,程式碼不允許這樣做。
通常假定鍵是 ASCIIZ 字串,而值可以是字串或二進位制資料。
擴充套件屬性(儲存在 inode 之後)具有一個長度為 4 位元組的標頭 ext4_xattr_ibody_header
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
h_magic |
用於標識的幻數,0xEA020000。 此值由 Linux 驅動程式設定,儘管 e2fsprogs 似乎不檢查它(?) |
擴充套件屬性塊的起始位置位於 struct ext4_xattr_header 中,其長度為 32 位元組。
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__le32 |
h_magic |
用於識別的魔數,為 0xEA020000。 |
0x4 |
__le32 |
h_refcount |
引用計數。 |
0x8 |
__le32 |
h_blocks |
使用的磁碟塊數量。 |
0xC |
__le32 |
h_hash |
所有屬性的雜湊值。 |
0x10 |
__le32 |
h_checksum |
擴充套件屬性塊的校驗和。 |
0x14 |
__u32 |
h_reserved[3] |
零。 |
校驗和是根據 FS UUID、擴充套件屬性塊的 64 位塊號以及整個塊(header + entries)計算得出的。
在 struct ext4_xattr_header 或 struct ext4_xattr_ibody_header 之後是一個 struct ext4_xattr_entry 陣列;這些條目中的每一個都至少有 16 位元組長。當儲存在外部塊中時,struct ext4_xattr_entry 條目必須按排序順序儲存。排序順序為 e_name_index,然後是 e_name_len,最後是 e_name。儲存在 inode 中的屬性不需要按排序順序儲存。
偏移量 |
Type |
名稱 |
描述 |
|---|---|---|---|
0x0 |
__u8 |
e_name_len |
名稱的長度。 |
0x1 |
__u8 |
e_name_index |
屬性名稱索引。 下面會討論。 |
0x2 |
__le16 |
e_value_offs |
此屬性的值在其儲存的磁碟塊上的位置。 多個屬性可以共享相同的值。 對於 inode 屬性,此值相對於第一個條目的起始位置;對於塊,此值相對於塊的起始位置(即 header)。 |
0x4 |
__le32 |
e_value_inum |
儲存值的 inode。 零表示該值與此條目位於同一塊中。 僅當啟用了 INCOMPAT_EA_INODE 功能時才使用此欄位。 |
0x8 |
__le32 |
e_value_size |
屬性值的長度。 |
0xC |
__le32 |
e_hash |
屬性名稱和屬性值的雜湊值。 核心不會更新 inode 內屬性的雜湊值,因此對於這種情況,該值必須為零,因為 e2fsck 會驗證任何非零雜湊值,無論 xattr 位於何處。 |
0x10 |
char |
e_name[e_name_len] |
屬性名稱。 不包括尾隨 NULL。 |
屬性值可以位於條目表之後。 似乎需要將它們對齊到 4 位元組邊界。 這些值從塊的末尾開始儲存,並向 xattr_header/xattr_entry 表增長。 當兩者發生衝突時,溢位會被放入單獨的磁碟塊中。 如果磁碟塊已滿,則檔案系統將返回 -ENOSPC。
將 ext4_xattr_entry 的前四個欄位設定為零以標記鍵列表的結尾。
4.4.1. 屬性名稱索引¶
從邏輯上講,擴充套件屬性是一系列鍵=值對。 假設鍵是 NULL 結尾的字串。 為了減少鍵佔用的磁碟空間量,鍵字串的開頭與屬性名稱索引匹配。 如果找到匹配項,則設定屬性名稱索引欄位,並從鍵名中刪除匹配的字串。 以下是將名稱索引值對映到鍵字首的對映
名稱索引 |
鍵字首 |
|---|---|
0 |
(無字首) |
1 |
“user.” |
2 |
“system.posix_acl_access” |
3 |
“system.posix_acl_default” |
4 |
“trusted.” |
6 |
“security.” |
7 |
“system.” (僅限 inline_data?) |
8 |
“system.richacl” (僅限 SuSE 核心?) |
例如,如果屬性鍵為 “user.fubar”,則屬性名稱索引設定為 1,並且 “fubar” 名稱記錄在磁碟上。
4.4.2. POSIX ACL¶
POSIX ACL 儲存在 Linux 核心(和 libacl)內部 ACL 格式的簡化版本中。 主要區別在於版本號不同 (1),並且僅為命名的使用者和組 ACL 儲存 e_id 欄位。