1. 程式人生 > >如何恢復 Linux 上刪除的檔案,第 5 部分

如何恢復 Linux 上刪除的檔案,第 5 部分


級別: 初級

2008 年 3 月 31 日

為 了支援更大的檔案系統,ext4 對 ext3 的現有實現進行了一系列擴充,使用 48 位的塊號來增大塊號定址範圍,並採用 extent 的設計來簡化對資料塊的索引,這勢必會影響到磁碟資料結構的變化,以及刪除檔案的恢復。本文將逐一介紹 ext4 在對大檔案系統支援方面所採用的全新設計,並探討 ext4 檔案系統中檔案的刪除和恢復的相關技術。

ext3 自從誕生之日起,就由於其可靠性好、特性豐富、效能高、版本間相容性好等優勢而迅速成為 Linux 上非常流行的檔案系統,諸如 Redhat 等發行版都將 ext3 作為預設的檔案系統格式。為了儘量保持與 ext2 檔案系統實現更好的相容性,ext3 在設計時採用了很多保守的做法,這些保守的設計為 ext3 贏得了穩定、健壯的聲譽,迅速得到了 Linux 使用者(尤其是原有的 ext2 檔案系統的使用者)的青睞,但同時這也限制了它的可擴充套件能力,無法支援特別大的檔案系統。

隨著硬碟儲存容量越來 越大(硬碟容量每年幾乎都會翻一倍,現在市面上已經有 1TB 的硬碟出售,很快桌面使用者也可以享用這麼大容量的儲存空間了),企業應用所需要和產生的資料越來越多(Lawrence Livermore National Labs 使用的 BlueGene/L 系統上所使用的資料早已超過了 1PB),以及線上重新調整大小特性的支援,ext3 所面臨的可擴充性問題和效能方面的壓力也越來越大。在 ext3 檔案系統中,如果使用 4KB 大小的資料塊,所支援的最大檔案系統上限為16TB,這是由於它使用了 32 位的塊號所決定的(232 * 212 B = 244

B = 16 TB)。為了解決這些限制,從 2006 年 8 月開始,陸續有很多為 ext3 設計的補丁釋出出來,這些補丁主要是擴充了兩個特性:針對大檔案系統支援的設計和 extent 對映技術。不過要想支援更大的檔案系統,就必須對磁碟上的儲存格式進行修改,這會破壞向前相容性。因此為了為龐大的 ext3 使用者群維護更好的穩定性,設計人員決定從 ext3 中另闢一支,設計下一代 Linux 上的檔案系統,即 ext4。

ext4 的主要目標是解決 ext3 所面臨的可擴充套件性、效能和可靠性問題。從 2.6.19 版本的核心開始,ext4 已經正式進入核心原始碼中,不過它被標記為正在開發過程中,即 ext4dev。本文將介紹 ext4 為了支援更好的可擴充套件性方面所採用的設計,並探討由此而引起的磁碟資料格式的變化,以及對恢復刪除檔案所帶來的影響。

可擴充套件性

為 了支援更大的檔案系統,ext4 決定採用 48 位的塊號取代 ext3 原來的 32 位塊號,並採用 extent 對映來取代 ext3 所採用的間接資料塊對映的方法。這樣既可以增大檔案系統的容量,又可以改進大檔案的訪問效率。在使用 4KB 大小的資料塊時,ext4 可以支援最大 248 * 212 = 260 B(1 EB)的檔案系統。之所以採用 48 位的塊號而不是直接將其擴充套件到 64 位是因為,ext4 的開發者認為 1 EB 大小的檔案系統對未來很多年都足夠了(實際上,按照目前的速度,要對 1 EB 大小的檔案系統執行一次完整的 fsck 檢查,大約需要 119 年的時間),與其耗費心機去完全支援 64 位的檔案系統,還不如先花些精力來解決更加棘手的可靠性問題。

將塊號從 32 位修改為 48 位之後,儲存元資料的結構都必須相應地發生變化,主要包括超級塊、組描述符和日誌。下面給出了 ext4 中所使用的新結構的部分程式碼。


清單1. ext4_super_block 結構定義
                
520 /*
521 * Structure of the super block
522 */
523 struct ext4_super_block {
524 /*00*/ __le32 s_inodes_count; /* Inodes count */
525 __le32 s_blocks_count; /* Blocks count */
526 __le32 s_r_blocks_count; /* Reserved blocks count */
527 __le32 s_free_blocks_count; /* Free blocks count */
528 /*10*/ __le32 s_free_inodes_count; /* Free inodes count */
529 __le32 s_first_data_block; /* First Data Block */
530 __le32 s_log_block_size; /* Block size */

594 /* 64bit support valid if EXT4_FEATURE_COMPAT_64BIT */
595 /*150*/ __le32 s_blocks_count_hi; /* Blocks count */
596 __le32 s_r_blocks_count_hi; /* Reserved blocks count */
597 __le32 s_free_blocks_count_hi; /* Free blocks count */

606 };

在 ext4_super_block 結構中,增加了 3 個與此相關的欄位:s_blocks_count_hi、s_r_blocks_count_hi、s_free_blocks_count_hi,它們 分別表示 s_blocks_count、s_r_blocks_count、s_free_blocks_count 高 32 位的值,將它們擴充到 64 位。


清單2. ext4_group_desc 結構定義
                
121 /*
122 * Structure of a blocks group descriptor
123 */
124 struct ext4_group_desc
125 {
126 __le32 bg_block_bitmap; /* Blocks bitmap block */
127 __le32 bg_inode_bitmap; /* Inodes bitmap block */
128 __le32 bg_inode_table; /* Inodes table block */
129 __le16 bg_free_blocks_count; /* Free blocks count */
130 __le16 bg_free_inodes_count; /* Free inodes count */
131 __le16 bg_used_dirs_count; /* Directories count */
132 __u16 bg_flags;
133 __u32 bg_reserved[3];
134 __le32 bg_block_bitmap_hi; /* Blocks bitmap block MSB */
135 __le32 bg_inode_bitmap_hi; /* Inodes bitmap block MSB */
136 __le32 bg_inode_table_hi; /* Inodes table block MSB */
137 };

類似地,在 ext4_group_desc 中引入了另外 3 個欄位:bg_block_bitmap_hi、bg_inode_bitmap_hi、bg_inode_table_hi,分別表示 bg_block_bitmap、bg_inode_bitmap、bg_inode_table 的高 32 位。

另外,由於日誌中要記錄所修改資料塊的塊號,因此 JBD也需要相應地支援 48 位的塊號。同樣是為了為 ext3 廣大的使用者群維護更好的穩定性,JBD2 也從 JBD 中分離出來,詳細實現請參看核心原始碼。

採 用 48 位塊號取代原有的 32 位塊號之後,檔案系統的最大值還受檔案系統中最多塊數的制約,這是由於 ext3 原來採用的結構決定的。回想一下,對於 ext3 型別的分割槽來說,在每個分割槽的開頭,都有一個引導塊,用來儲存引導資訊;檔案系統的資料一般從第 2 個數據塊開始(更確切地說,檔案系統資料都是從 1KB 之後開始的,對於 1024 位元組大小的資料塊來說,就是從第 2 個數據塊開始;對於超過 1KB 大小的資料塊,引導塊與後面的超級塊等資訊共同儲存在第 1 個數據塊中,超級塊從 1KB 之後的位置開始)。為了管理方便,檔案系統將剩餘磁碟劃分為一個個塊組。塊組前面儲存了超級塊、塊組描述符、資料塊點陣圖、索引節點點陣圖、索引節點表,然後 才是資料塊。通過有效的管理,ext2/ext3 可以儘量將檔案的資料放入同一個塊組中,從而實現檔案資料在磁碟上的最大連續性。

在 ext3 中,為了安全性方面的考慮,所有的塊描述符資訊全部被儲存到第一個塊組中,因此以預設的 128MB (227 B)大小的塊組為例,最多能夠支援 227 / 32 = 222 個塊組,最大支援的檔案系統大小為 222 * 227 = 249 B= 512 TB。而ext4_group_desc 目前的大小為 44 位元組,以後會擴充到 64 位元組,所能夠支援的檔案系統最大隻有 256 TB。

為 瞭解決這個問題,ext4 中採用了元塊組(metablock group)的概念。所謂元塊組就是指塊組描述符可以儲存在一個數據塊中的一些連續塊組。仍然以 128MB 的塊組(資料塊為 4KB)為例,ext4 中每個元塊組可以包括 4096 / 64 = 64 個塊組,即每個元塊組的大小是 64 * 128 MB = 8 GB。

採 用元塊組的概念之後,每個元塊組中的塊組描述符都變成定長的,這對於檔案系統的擴充套件非常有利。原來在 ext3 中,要想擴大檔案系統的大小,只能在第一個塊組中增加更多塊描述符,通常這都需要重新格式化檔案系統,無法實現線上擴容;另外一種可能的解決方案是為塊組 描述符預留一部分空間,在增加資料塊時,使用這部分空間來儲存對應的塊組描述符;但是這樣也會受到前面介紹的最大容量的限制。而採用元塊組概念之後,如果 需要擴充檔案系統的大小,可以在現有資料塊之後新新增磁碟資料塊,並將這些資料塊也按照元塊組的方式進行管理即可,這樣就可以突破檔案系統大小原有的限制 了。當然,為了使用這些新增加的空間,在 superblock 結構中需要增加一些欄位來記錄相關資訊。(ext4_super_block 結構中增加了一個 s_first_meta_bg 欄位用來引用第一個元塊組的位置,這樣還可以解決原有塊組和新的元塊組共存的問題。)下圖給出了 ext3 為塊組描述符預留空間和在 ext4 中採用元塊組後的磁碟佈局。


圖 1. ext3 與 ext4 磁碟佈局對比
圖 1. ext3 與 ext4 磁碟佈局對比






ext2/ext3 檔案系統與大部分經典的 UNIX/Linux 檔案系統一樣,都使用了直接、間接、二級間接和三級間接塊的形式來定位磁碟中的資料塊。對於小檔案或稀疏檔案來說,這非常有效(以 4KB 大小的資料塊為例,小於 48KB 的檔案只需要通過索引節點中 i_block 陣列的前 12 個元素一次定位即可),但是對於大檔案來說,需要經過幾級間接索引,這會導致在這些檔案系統上大檔案的效能較差。

測 試表明,在生產環境中,資料不連續的情況不會超過10%。因此,在 ext4 中引入了 extent 的概念來表示檔案資料所在的位置。所謂 extent 就是描述儲存檔案資料使用的連續物理塊的一段範圍。每個 extent 都是一個 ext4_extent 型別的結構,大小為 12 位元組。定義如下所示:


清單3. ext4 檔案系統中有關 extent 的結構定義
                
69 /*
70 * This is the extent on-disk structure.
71 * It's used at the bottom of the tree.
72 */
73 struct ext4_extent {
74 __le32 ee_block; /* first logical block extent covers */
75 __le16 ee_len; /* number of blocks covered by extent */
76 __le16 ee_start_hi; /* high 16 bits of physical block */
77 __le32 ee_start; /* low 32 bits of physical block */
78 };
79
80 /*
81 * This is index on-disk structure.
82 * It's used at all the levels except the bottom.
83 */
84 struct ext4_extent_idx {
85 __le32 ei_block; /* index covers logical blocks from 'block' */
86 __le32 ei_leaf; /* pointer to the physical block of the next *
87 * level. leaf or next index could be there */
88 __le16 ei_leaf_hi; /* high 16 bits of physical block */
89 __u16 ei_unused;
90 };
91
92 /*
93 * Each block (leaves and indexes), even inode-stored has header.
94 */
95 struct ext4_extent_header {
96 __le16 eh_magic; /* probably will support different formats */
97 __le16 eh_entries; /* number of valid entries */
98 __le16 eh_max; /* capacity of store in entries */
99 __le16 eh_depth; /* has tree real underlying blocks? */
100 __le32 eh_generation; /* generation of the tree */
101 };
102
103 #define EXT4_EXT_MAGIC cpu_to_le16(0xf30a)

每個 ext4_extent 結構可以表示該檔案從 ee_block 開始的 ee_len 個數據塊,它們在磁碟上的位置是從 ee_start_hi<<32 + ee_start 開始,到 ee_start_hi<<32 + ee_start + ee_len – 1 結束,全部都是連續的。儘管 ee_len 是一個 16 位的無符號整數,但是其最高位被在預分配特性中用來標識這個 extent 是否被初始化過了,因此可以一個 extent 可以表示 215 個連續的資料塊,如果採用 4KB 大小的資料塊,就相當於 128MB。

如 果檔案大小超過了一個 ext4_extent 結構能夠表示的範圍,或者其中有不連續的資料塊,就需要使用多個 ext4_extent 結構來表示了。為了解決這個問題,ext4 檔案系統的設計者們採用了一棵 extent 樹結構,它是一棵高度固定的樹,其佈局如下圖所示:


圖 2. ext4 中 extent 樹的佈局結構
圖 2. ext4 中 extent 樹的佈局結構

在 extent 樹中,節點一共有兩類:葉子節點和索引節點。儲存檔案資料的磁碟塊資訊全部記錄在葉子節點中;而索引節點中則儲存了葉子節點的位置和相對順序。不管是葉子 節點還是索引節點,最開始的 12 個位元組總是一個 ext4_extent_header 結構,用來標識該資料塊中有效項(ext4_extent 或 ext4_extent_idx 結構)的個數(eh_entries 域的值),其中 eh_depth 域用來表示它在 extent 樹中的位置:對於葉子節點來說,該值為 0,之上每層索引節點依次加 1。extent 樹的根節點儲存在索引節點結構中的 i_block 域中,我們知道它是一個大小為 60 位元組的陣列,最多可以儲存一個 ext4_extent_header 結構以及 4 個 ext4_extent 結構。對於小檔案來說,只需要一次定址就可以獲得儲存檔案資料塊的位置;而超出此限制的檔案(例如很大的檔案、碎片非常多的檔案以及稀疏檔案)只能通過遍 歷 extent 樹來獲得資料塊的位置。







索引節點

索引節點是 ext2/ext3/ext4 檔案系統中最為基本的一個概念,它是檔案語義與資料之間關聯的橋樑。為了最大程度地實現向後相容性,ext4 儘量保持索引節點不會發生太大變化。ext4_inode 結構定義如下所示:


清單4. ext4_inode 結構定義
                
284 /*
285 * Structure of an inode on the disk
286 */
287 struct ext4_inode {
288 __le16 i_mode; /* File mode */
289 __le16 i_uid; /* Low 16 bits of Owner Uid */
290 __le32 i_size; /* Size in bytes */
291 __le32 i_atime; /* Access time */
292 __le32 i_ctime; /* Inode Change time */
293 __le32 i_mtime; /* Modification time */
294 __le32 i_dtime; /* Deletion Time */
295 __le16 i_gid; /* Low 16 bits of Group Id */
296 __le16 i_links_count; /* Links count */
297 __le32 i_blocks; /* Blocks count */
298 __le32 i_flags; /* File flags */

310 __le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
311 __le32 i_generation; /* File version (for NFS) */
312 __le32 i_file_acl; /* File ACL */
313 __le32 i_dir_acl; /* Directory ACL */
314 __le32 i_faddr; /* Fragment address */

339 __le16 i_extra_isize;
340 __le16 i_pad1;
341 __le32 i_ctime_extra; /* extra Change time (nsec << 2 | epoch) */
342 __le32 i_mtime_extra; /* extra Modification time(nsec << 2 | epoch) */
343 __le32 i_atime_extra; /* extra Access time (nsec << 2 | epoch) */
344 __le32 i_crtime; /* File Creation time */
345 __le32 i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
346 };

與 ext3 檔案系統中使用的 ext3_inode 結構對比一下可知,索引節點結構並沒有發生太大變化,不同之處在於最後添加了 5 個與時間有關的欄位,這是為了提高時間戳的精度。在 ext2/ext3 檔案系統中,時間戳的精度只能達到秒級。隨著硬體效能的提升,這種精度已經無法區分在同一秒中建立的檔案的時間戳差異,這對於對精度要求很高的程式來說是 無法接受的。在 ext4 檔案系統中,通過擴充索引節點結構解決了這個問題,可以實現納秒級的精度。最後兩個新增欄位 i_crtime 和 i_crtime_extra 用來表示檔案的建立時間,這可以用來滿足某些應用程式的需求。

前面已經介紹 過,儘管索引節點中的 i_block 欄位保持不變,但是由於 extent 概念的引入,對於這個陣列的使用方式已經改變了,其前 3 個元素一定是一個 ext4_extent_header 結構,後續每 3 個元素可能是一個 ext4_extent 或 ext4_extent_idx 結構,這取決於所表示的檔案的大小。這種設計可以有效地表示連續存放的大檔案,但是對於包含碎片非常多的檔案或者稀疏檔案來說,就不是那麼有效了。為了解 決這個問題,ext4 的設計者們正在討論設計一種新型的 extent 來表示這種特殊檔案,它將在葉子節點中採用類似於 ext3 所採用的間接索引塊的形式來儲存為該檔案分配的資料塊位置。該型別的 ext4_extent_header 結構中的 eh_magic 欄位將採用一個新值,以便與目前的 extent 區別開來。

採用這種結構的索引節點還存在一個問題:我們知道,在 ext3 中 i_blocks 是以扇區(即 512 位元組)為單位的,因此單個檔案的最大限制是 232 * 512 B = 2 TB。為了支援更大的檔案,ext4 的 i_blocks 可以以資料塊大小為單位(這需要 HUGE_FILE 特性的支援),因此檔案上限可以擴充到 16TB(資料塊大小為 4KB)。同時為了避免需要對整個檔案系統都需要進行類似轉換,還引入了一個 EXT4_HUGE_FILE_FL 標誌,i_flags 中不包含這個標誌的索引節點的 i_blocks 依然以 512 位元組為單位。當檔案所佔用的磁碟空間大小增大到不能夠用以512位元組為單位的i_blocks來表示時,ext4自動啟用 EXT4_HUGE_FILE_FL標誌,以資料塊為單位重新計算i_blocks的值。該轉換是自動進行的,對使用者透明。







目錄項

ext4 檔案系統中使用的目錄項與 ext2/ext3 並沒有太大的區別。所使用的結構定義如下所示:


清單5. 目錄項結構定義
                
737 /*
738 * Structure of a directory entry
739 */
740 #define EXT4_NAME_LEN 255
741
742 struct ext4_dir_entry {
743 __le32 inode; /* Inode number */
744 __le16 rec_len; /* Directory entry length */
745 __le16 name_len; /* Name length */
746 char name[EXT4_NAME_LEN]; /* File name */
747 };
748
749 /*
750 * The new version of the directory entry. Since EXT4 structures are
751 * stored in intel byte order, and the name_len field could never be
752 * bigger than 255 chars, it's safe to reclaim the extra byte for the
753 * file_type field.
754 */
755 struct ext4_dir_entry_2 {
756 __le32 inode; /* Inode number */
757 __le16 rec_len; /* Directory entry length */
758 __u8 name_len; /* Name length */
759 __u8 file_type;
760 char name[EXT4_NAME_LEN]; /* File name */
761 };

與 ext2/ext3 類似,當目錄項被刪除時,也會將該目錄項的空間合併到上一個目錄項中。與 ext2/ext3 不同的地方在於,在刪除目錄項時,該目錄項中的索引節點號並沒有被清空,而是得以保留了下來,這使得在恢復刪除檔案時,檔名就可以通過查詢目錄項中匹配 的索引節點號得以正確恢復。詳細資料如下所示:


清單6. 刪除檔案前後目錄項的變化
                
[[email protected] ext4]# ./read_dir_entry root.block.547.orig 4096
offset | inode number | rec_len | name_len | file_type | name
=================================================================
0: 2 12 1 2 .
12: 2 12 2 2 ..
24: 11 20 10 2 lost+found
44: 12 16 5 1 hello
60: 13 32 12 1 testfile.35K
80: 14 12 4 1 hole
92: 15 4004 4 1 home
[[email protected] ext4]# ./read_dir_entry root.block.547.deleted 4096
offset | inode number | rec_len | name_len | file_type | name
=================================================================
0: 2 12 1 2 .
12: 2 12 2 2 ..
24: 11 36 10 2 lost+found
44: 12 16 5 1 hello
60: 13 32 12 1 testfile.35K
80: 14 12 4 1 hole
92: 15 4004 4 1 home

上面給出了儲存根目錄的資料塊(547) 在刪除 hello 檔案前後的變化,從中我們可以看出,唯一的區別在於 hello 所使用的 16 個位元組的空間後來被合併到 lost+found 目錄項所使用的空間中了,而索引節點號等資訊都得以完整地保留了下來。清單中使用的 read_dir_entry 程式用來顯示目錄項中的資料,其原始碼可以在本文下載部分中獲得。有關如何抓取儲存目錄資料的資料塊的方法,請參看本系列文章第 2 部分的介紹。

在 ext2/3 檔案系統中,一個目錄下面最多可以包含 32,000 個子目錄,這對於大型的企業應用來說顯然是不夠的。ext4 決定將其上限擴充到可以支援任意多個子目錄。然而對於這種連結串列式的儲存結構來說,目錄項的查詢和刪除需要遍歷整個目錄的所有目錄項,效率顯然是相當低的。 實際上,從 ext2 開始,檔案系統的設計者引入了一棵 H-樹來對目錄項的 hash 值進行索引,速度可以提高 50 - 100 倍。相關內容已經超出了本文的範圍,感興趣的讀者可自行參考 Linux 核心原始碼中的相關實現。







目前,ext4 檔案系統仍然處於非常活躍的狀態,因此核心在相應的地方都加上了 DEV 標誌。在編譯核心時,需要在核心的 .config 檔案中啟用 EXT4DEV_FS 選項才能編譯出最終使用的核心模組 ext4dev.ko。

由於 ext4 內部採用的關鍵資料結構與 ext3 並沒有什麼關鍵區別,因此在建立檔案系統時依然是使用 mkfs.ext3 命令,如下所示:


清單7. 建立 ext4 檔案系統,目前與建立 ext3 檔案系統沒什麼兩樣
                
[[email protected] ~]# mkfs.ext3 /dev/sda3

為了保持向前相容性,現有的 ext3 檔案系統也可以當作 ext4 檔案系統進行載入,命令如下所示:

清單8. 掛載 ext4 檔案系統

[[email protected] ~]# mount -t ext4dev -o extents /dev/sda3 /tmp/test

-o extents 選項就是指定要啟用 extent 特性。如果不在這個檔案系統中執行任何寫入操作,以後這個檔案系統也依然可以按照 ext3 或 ext4 格式正常掛載。但是一旦在這個檔案系統中寫入檔案之後,檔案系統所使用的特性中就包含了 extent 特性,因此以後再也不能按照 ext3 格式進行掛載了,如下所示:


清單9. 寫入檔案前後 ext4 檔案系統特性的變化據
                
[[email protected] ext4]# umount /tmp/test; mount -t ext4dev -o extents /dev/sda3 /tmp/test; /
dumpe2fs /dev/sda3 > sda3.ext4_1
[[email protected] ext4]# umount /tmp/test; mount -t ext4dev -o extents /dev/sda3 /tmp/test; /
echo hello > /tmp/test/hello; dumpe2fs /dev/sda3 > sda3.ext4_2
[[email protected] ext4]# diff sda3.ext4_1 sda3.ext4_2
6c6
< Filesystem features: has_journal resize_inode dir_index filetype /
needs_recovery sparse_super large_file
---
> Filesystem features: has_journal resize_inode dir_index filetype /
needs_recovery extents sparse_super large_file

[[email protected] ext4]# umount /tmp/test; mount -t ext3 /dev/sda3 /tmp/test
mount: wrong fs type, bad option, bad superblock on /dev/sda3,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so







在 本系列前面的文章中,我們已經初步體驗了 e2fsprogs 包中提供的諸如 debugfs、dumpe2fs 之類的工具對於深入理解檔案系統和磁碟資料來說是如何方便。作為一種新生的檔案系統,ext4 檔案系統要想得到廣泛應用,相關工具的支援也非常重要。從 1.39 版本開始,e2fsprogs 已經逐漸開始加入對 ext4 檔案系統的支援,例如建立檔案系統使用的 mkfs.ext3 命令以後會被一個新的命令 mkfs.ext4 所取代。但是截止到本文撰寫時為止,e2fsprogs 的最新版本(1.40.7)對於 ext4 的支援尚不完善,下面的例子給出了 debugfs 檢視 hello 檔案時的結果:


清單10. debugfs 命令對 ext4 檔案系統的支援尚不完善
                
[[email protected] ext4]# echo "hello world" > /tmp/test/hello
[[email protected] ext4]# debugfs /dev/sda3
debugfs 1.40.2 (12-Jul-2007)
debugfs: stat hello
Inode: 12 Type: regular Mode: 0644 Flags: 0x80000 Generation: 827135866
User: 0 Group: 0 Size: 12
File ACL: 0 Directory ACL: 0
Links: 1 Blockcount: 8
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x47ced460 -- Thu Mar 6 01:12:00 2008
atime: 0x47ced460 -- Thu Mar 6 01:12:00 2008
mtime: 0x47ced460 -- Thu Mar 6 01:12:00 2008
BLOCKS:
(0):127754, (1):4, (4):1, (5):28672
TOTAL: 4

從上面的輸出結果中我們可以看出,儘管這 個索引節點的 i_flags 欄位值為 0x80000,表示使用 extent 方式來儲存資料,而不是原有的直接/間接索引模式來儲存資料(此時 i_flags 欄位值為 0),但是對 i_block 陣列中內容的顯示卻依然沿用了原有的模式。如果檔案佔用多個 extent 進行儲存,會發現 debugfs 依然嘗試將 i_block[12]、i_block[13]、i_block[14] 分別作為一級、二級和三級間接索引使用,顯然從中讀出的資料也是毫無意義的。

索引節點中使用的 i_flags 值是在核心原始碼的 /include/linux/ext4_fs.h 中定義的,如下所示:


清單11. i_flags 值定義節選
                
#define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */







在 ext4 檔案系統中刪除檔案時,所執行的操作與在 ext2/ext3 檔案系統中非常類似,也不會真正修改儲存檔案資料所使用的磁碟資料塊的內容,而是僅僅刪除或修改了相關的元資料資訊,使檔案資料無法正常索引,從而實現刪 除檔案的目的。因此,在 ext4 檔案系統中恢復刪除檔案也完全是可能的。

前文中已經介紹過,在 ext4 檔案系統中刪除檔案時,並沒有將目錄項中的索引節點號清空,因此通過遍歷目錄項的方式完全可以完整地恢復出文件名來。

對於檔案資料來說,實際資料塊中的資料在檔案刪除前後也沒有任何變化,這可以利用本系列文章第一部分中介紹的直接比較資料塊的方法進行驗證。然而由於 extent 的引入,在 ext4 中刪除檔案與 ext3 也有所區別。下面讓我們通過一個例項來驗證一下。

在下面的例子中,我們要建立一個非常特殊的檔案,它每 7KB 之後的都是一個數字(7 的倍數),其他地方資料全部為 0。


清單12. 建立測試檔案
                
[[email protected] ext4]# cat -n create_extents.sh
1 #!/bin/bash
2
3 if [ $# -ne 2 ]
4 then
5 echo "$0 [filename] [size in kb]"
6 exit 1
7 fi
8
9 filename=$1
10 size=$2
11 i=0
12
13 while [ $i -lt $size ]
14 do
15 i=`expr $i + 7`
16 echo -n "$i" | dd of=$1 bs=1024 seek=$i
17 dones
[[email protected] ext4]# ./create_extents.sh /tmp/test/sparsefile.70K 70
[[email protected] ext4]# ls -li /tmp/test/sparsefile.70K
13 -rw-r--r-- 1 root root 71682 2008-03-06 10:49 /tmp/test/sparsefile.70K
[[email protected] ext4]# hexdump -C /tmp/test/sparsefile.70K
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001c00 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |7...............|
00001c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00003800 31 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |14..............|
00003810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00005400 32 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |21..............|
00005410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00007000 32 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |28..............|
00007010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00008c00 33 35 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |35..............|
00008c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0000a800 34 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |42..............|
0000a810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0000c400 34 39 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |49..............|
0000c410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0000e000 35 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |56..............|
0000e010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0000fc00 36 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |63..............|
0000fc10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00011800 37 30 |70|
00011802

之所以要使用這個檔案當作測試檔案,完全為了迴避 extent 的優點,否則在 4KB 大小資料塊的 ext4 檔案系統中,一個 extent 就可以表示 128MB 的空間,因此要想測試 extent 樹的變化情況就必須建立非常大的檔案才行。

由於 debugfs 對 ext4 的支援尚不完善,我們自己編寫了一個小程式(list_extents)來遍歷 extent 樹的內容,並顯示索引節點和葉子節點的資料塊的位置。該程式的原始碼可以在本文下載部分中獲得,其用法如下:


清單13. 檢視測試檔案使用的 extent 樹資訊
                
[[email protected] ext4]# ./list_extents /dev/sda3 13
root node: depth of the tree: 1, 1 entries in root level
idx: logical block: 1, block: 20491
- logical block: 1 - 1, physical block: 20481 - 20481
- logical block: 3 - 3, physical block: 20483 - 20483
- logical block: 5 - 5, physical block: 20485 - 20485
- logical block: 7 - 8, physical block: 20487 - 20488
- logical block: 10 - 10, physical block: 20490 - 20490
- logical block: 12 - 12, physical block: 20492 - 20492
- logical block: 14 - 15, physical block: 20494 - 20495
- logical block: 17 - 17, physical block: 20497 - 20497

list_extents 程式會對指定磁碟進行搜尋,從其中的索引節點表中尋找搜尋指定的索引節點號(13)所對應的項,並將其 i_block 陣列當作一棵 extent 樹進行遍歷。從輸出結果中我們可以看出,這棵 extent 樹包括 1 個索引節點和 1個包含了8個 ext4_extent 結構的葉子節點,其中索引節點儲存 i_block 陣列中,而葉子節點則儲存在20491 這個資料塊中。下面讓我們來看一下該檔案的索引節點在刪除檔案前後的變化:


清單14. ext4 檔案系統中刪除檔案前後文件索引節點的變化
                
[[email protected] ext4]# echo "stat <13>" | debugfs /dev/sda3
debugfs 1.40.2 (12-Jul-2007)
debugfs: Inode: 13 Type: regular Mode: 0644 Flags: 0x80000
Generation: 2866260918
User: 0 Group: 0 Size: 71682
File ACL: 0 Directory ACL: 0
Links: 1 Blockcount: 88
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x47cf5bb1 -- Thu Mar 6 10:49:21 2008
atime: 0x47cf5bb0 -- Thu Mar 6 10:49:20 2008
mtime: 0x47cf5bb1 -- Thu Mar 6 10:49:21 2008
BLOCKS:
(0):127754, (1):65540, (3):1, (4):20491, (6):3, (7):1,
(8):20483, (9):5, (10):1, (11):20485, (IND):7, (12):32775,
(13):98311, (14):163847, (15):229383, (DIND):2, (IND):32770,
(IND):98306, (IND):163842, (IND):229378, (TIND):20487, (DIND):14386
TOTAL: 22

[[email protected] ext4]# rm -f /tmp/test/sparsefile.70K
[[email protected] ext4]# sync
[[email protected] ext4]# echo "stat <13>" | debugfs /dev/sda3
debugfs 1.40.2 (12-Jul-2007)
debugfs: Inode: 13 Type: regular Mode: 0644 Flags: 0x80000
Generation: 2866260918
User: 0 Group: 0 Size: 0
File ACL: 0 Directory ACL: 0
Links: 0 Blockcount: 0
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x47cf5ebc -- Thu Mar 6 11:02:20 2008
atime: 0x47cf5bb0 -- Thu Mar 6 10:49:20 2008
mtime: 0x47cf5bb1 -- Thu Mar 6 10:49:21 2008
dtime: 0x47cf5ebc -- Thu Mar 6 11:02:20 2008
BLOCKS:
(0):62218, (1):4, (3):1, (4):20491, (6):3, (7):1,
(8):20483, (9):5, (10):1, (11):20485, (IND):7, (12):32775,
(13):98311, (14):163847, (15):229383, (DIND):2, (IND):32770,
(IND):98306, (IND):163842, (IND):229378, (TIND):20487, (DIND):14386
TOTAL: 22

首先需要注意的一點是,對於上面這棵 extent 樹來說,只需要使用 i_block 陣列的前 6 個元素就可以儲存一個 ext4_extent_header 和一個 ext4_extent_idx 結構了,而 i_block 陣列中的所有元素卻都是有資料的。之所以出現這種情況是因為在建立這個特殊的測試檔案的過程中,我們是不斷建立一個檔案並在此檔案尾部追加資料從而生成新 檔案的。當該檔案使用的 extent 超過 4 個時,便擴充成一棵 extent 樹,但是剩餘 3 個 extent 的內容(i_block 陣列的後 9 個元素)並沒有被清空。

對比刪除檔案前後的變化會發現,ext4 與 ext3 非常類似,也都將檔案大小設定為 0,這使得 debugfs 的 dump 命令也無從正常工作了。不過與 ext3 不同的是,ext4 並沒有將 i_block 陣列的元素全部清空,而是將 ext4_extent_header 結構中有效項數設定為 0,這樣就將 extent 樹破壞掉了。另外,比較葉子節點(資料塊 20491)中的資料變化會發現,下面這些域在刪除檔案時也都被清除了:

  • ext4_extent_header 結構中的 eh_entries。
  • ext4_extent 結構中的 ee_len、ee_start_hi 以及 ee_start。

圖3. 刪除檔案前後 extent 樹中葉子節點資料塊的變化
圖3. 刪除檔案前後 extent 樹中葉子節點資料塊的變化

瞭解清楚這些變化之後,我們會發現在 ext4 中恢復刪除檔案的方法與 ext3 基本類似,也可以使用全文匹配、提前備份元資料和修改核心實現 3 種方法。

正 如前面介紹的一樣,由於 ext4 檔案系統中採用了 extent 的設計,試圖最大程度地確保檔案資料會被儲存到連續的資料塊中,因此在 ext2/ext3 恢復刪除檔案時所介紹的正文匹配方法也完全適用,正常情況下采用這種方式恢復出來的資料會比 ext2/ext3 中更多。詳細內容請參看本系列文章第 4 部分的介紹,本文中不再贅述。

儘管 ext4 是基於 extent 來管理空間的,但是在 ext3 中備份資料塊位置的方法依然完全適用,下面給出了一個例子。


清單15. 備份檔案資料塊位置
                
[[email protected] ext4]# export LD_PRELOAD=/usr/local/lib/libundel.so
[[email protected] ext4]# rm -f /tmp/test/sparsefile.70K
[[email protected] ext4]# tail -n 1 /var/e2undel/e2undel
8,3::13::71682::4096::(1-1): 20481-20481,(3-3): 20483-20483,
(5-5): 20485-20485,(7-8): 20487-20488,(10-10): 20490-20490,
(12-12): 20492-20492,(14-15): 20494-20495,
(17-17): 20497-20497::/tmp/test/sparsefile.70K

當然,如果核心實現中可以在刪除檔案 時,extent樹(其中包括i_block陣列,其他extent索引節點和葉子節點)中的資料保留下來,那自然恢復起來就更加容易了。由於 ext4 的開發依然正在非常活躍地進行中,相關程式碼可能會頻繁地發生變化,本文就不再深入探討這個話題了,感興趣的讀者可以自行嘗試。







小結

本 文從 ext3 的在支援大檔案系統方面的缺陷入手,逐漸介紹了 ext4 為了支援大檔案系統而引入的一些設計特性,並探討了這些特性對磁碟資料格式引起的變化,以及對恢復刪除檔案所帶來的影響,最終討論了在 ext4 檔案系統中如何恢復刪除檔案的問題。在本系列的下一篇文章中,我們將開始討論另外一個設計非常精巧的檔案系統 reiserfs 上的相關問題。







參考資料

  • Theodore Ts'o 對 ext4 的討論: http://kerneltrap.org/node/6776,其中介紹了為什麼要將 ext4 與 ext3 的開發分離開來
  • “Planned Extensions to the Linux Ext2/Ext3 Filesystem”: http://e2fsprogs.sourceforge.net/extensions-ext23/,Theodore Ts'o 在 2002 年就針對當時 ext2/ext3 的問題提出了一系列改進計劃,包括目錄索引、線上調整檔案系統大小、索引節點擴充、採用 extent 對映以更好地支援大檔案等特性,這些設計現在有很多已經進入主流核心中了。
  • “ext4 merge plans for 2.6.25”:http://lwn.net/Articles/266191/,其中介紹了在 2.6.25 版本的核心中將正式採納的對 ext4 特性改進的實現
  • 本文中節選的原始碼均參考的是 2.6.23 版本的核心
  • “Ext4 Development Wiki”: http://ext4.wiki.kernel.org,這是最初為 ext4 開發人員準備的一個 wiki 頁面
  • e2fsprogs 專案主頁: http://e2fsprogs.sourceforge.net/,其中包含了 e2fsprogs 包的最新實現,最新版本中已經包含了對 ext4 檔案系統的支援







下載

描述名字大小下載方法
建立本文使用的特殊測試檔案create_extents.sh1KBHTTP
檢視 ext4 中檔案的 extent 樹資訊list_extents.c16KBHTTP
檢視 ext4 中檔案的 extent 樹資訊read_dir_entry.c2KBHTTP


作者簡介

馮銳,軟體工程師,目前在 IBM 開發中心從事 AIX 效能測試方面的工作。您可以通過 [email protected] 與他聯絡。


沈林峰,軟體工程師,目前在 IBM 開發中心從事 Linux for Power 方面的測試工作,您可以通過 [email protected] 與他聯絡。

相關推薦

如何恢復 Linux 刪除檔案 5 部分

級別: 初級2008 年 3 月 31 日為 了支援更大的檔案系統,ext4 對 ext3 的現有實現進行了一系列擴充,使用 48 位的塊號來增大塊號定址範圍,並採用 extent 的設計來簡化對資料塊的索引,這勢必會影響到磁碟資料結構的變化,以及刪除檔案的恢復。本文將逐一介紹 ext4 在對大檔案系統支

Linux恢復誤刪刪除檔案釋放刪除空間

參考網址https://www.cnblogs.com/z-sm/p/6108689.html 鍵入命令:lsof |grep deleted 找到刪除的檔案。 恢復程序號為1464的檔案 需要先建立路徑/home/hadoop/zktmp/version-2 cat /

如何恢復 Linux 刪除檔案 ext2

            <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>            要想恢復誤刪除的檔案,必須清楚資

Linux刪除檔案磁碟空間未釋放問題追蹤

    在客戶使用我們產品後,發現一個問題:在刪除了檔案後,磁碟空間卻沒有釋放。是有程序在開啟這個檔案,還是其他情況?我們一起來看看一下兩個場景 一. 場景一:程序開啟此檔案     當一個檔案正在被一個程序使用時,使用者刪除此檔案,檔案只會從目錄結構中刪除,但並沒有從磁

高階 Linux 命令精通指南 3 部分:資源管理

感謝原創Arup Nanda 在此部分中,瞭解用於監視物理元件的高階 Linux 命令 Linux 系統由若干主要物理元件組成,如 CPU、記憶體、網絡卡和儲存裝置。要有效地管理 Linux 環境,您應該能夠以合理的精度測量這些資源的各種指標 — 每個元件處理多少資源、是否存

java代碼linux刪除文件

return ret try flag 作文 exc post tac trace 1、其實在linux上和window是一樣的 2、path 傳入的路徑(直接從根目錄到你的文件的位置) public static boolean delFile(String path)

github二次檔案分支檔案刪除資料夾

看了太多GitHub入門教程,終於搞通了,樂在分享。 部分參考:第一個GitHub專案https://blog.csdn.net/wangyan_z/article/details/79148059 git上傳本地分支到github專案分支  https://blog.csdn.

C#如何操控FTP獲取FTP檔案或資料夾列表獲取FTP檔案大小FTPFTP刪除檔案FTP新建資料夾、刪除資料夾

C#如何操控FTP 出處:http://www.cnblogs.com/rond/archive/2012/07/30/2611295.html,http://www.cnblogs.com/rond   關於FTP的應用免不了要對FTP進行增刪查改什麼的。通過搜尋,整理和修改

資料結構--C語言--逆序建立單鏈表遍歷單鏈表在單鏈表5個元素前插入一個值為999的元素刪除單鏈表5個元素

#include<stdio.h> #include<stdlib.h> #define OK 1 #define ERROR 0 #define LEN sizeof(struct LNode) struct LNode{ int data; struct LNode

linux檢視使用者刪除使用者檢視使用者許可權

檢視使用者: use mysql;select user,host,password from mysql.user; 檢視使用者許可權 show grants for 使用者名稱@'host';   刪除使用者 use mysql;Delete FROM user Where Use

遷移 Linux 系統 1 部分 如何遷移備份和裸機恢復 Linux 系統

當硬體升級,更換儲存裝置或是遇到硬體故障時,需要遷移原來的作業系統及應用軟體到新的硬體裝置上。這個過程包含系統的遷移備份和裸機恢復,本文詳細描述了整個過程的細節。 災 難恢復 , 指自然或人為災害後,重新啟用資訊系統的資料、硬體及軟體裝置,恢復正常商業運作的過程。災難恢復是

linuxphp安裝外掛ixed.5.4.lin和檢視現有的外掛

Linux的上PHP安裝外掛ixed.5.4.lin,和檢視現有的外掛 1:修改配置檔案:    sed -i  '897c extension = ixed.5.4.lin'  /usr/local/php/etc/php.ini php.ini配置

SQL Server事務日誌管理的進階5級:在完全恢復模式下管理日誌

SQL Server事務日誌管理的進階,第5級:在完全恢復模式下管理日誌   原文連結:http://www.sqlservercentral.com/articles/Stairway+Series/73785/   託尼·戴維斯(Tony Davis)著,2012年1月27日

SQL Server中事務日誌管理的步驟5級:完全恢復模式管理日誌(譯)

維護計劃 recover 最小 替代 關心 每日 工作方式 檢查 耗時 SQL Server中事務日誌管理的步驟,第5級:完全恢復模式管理日誌 作者:Tony Davis,2012/01/27 系列 本文是進階系列的一部分:SQL Server中事務日誌管理的步驟 當事情進

SQL Server中事務日誌管理的步驟5級:完全恢復模式管理日誌

語句 targe .aspx 頻率 良好的 popu 這樣的 模式 insert SQL Server中事務日誌管理的步驟,第5級:完全恢復模式管理日誌 作者:Tony Davis,2012/01/27 系列 本文是進階系列的一部分:SQL Server中事務日誌管理的

翻譯《Stairway to SQL Server Replication: Level 5- Managing the Log in Full Recovery Mode》 SQL Server事務日誌管理的進階5級:在完全恢復模式下管理日誌

SQL Server事務日誌管理的進階,第5級:在完全恢復模式下管理日誌 SQL Server事務日誌管理的進階,第5級:在完全恢復模式下管理日誌 作者:託尼·戴維斯(Tony Davis) 時間:2012年1月27日  原文連結:http://www.sqlser

五次翻譯:SQL Server事務日誌管理的進階5級:在完全恢復模式下管理日誌

 SQL Server中事務日誌管理的階梯,第5級:在完全恢復模式下管理日誌 作者:Tony Davis,2012/01/27 文章轉載自:http://www.sqlservercentral.com/articles/Stairway+Series/73785/   該系列

File Input多次新增檔案動態刪除檔案用來實現傳等操作

1.需求圖示 2.按圖索驥 新增 實際上,新增附件就是<input type="file" id="myFile">的控制元件,var fileList = getElemen

linux刪除檔案後的恢復操作

下面介紹具體方法: 先說下我準備刪除的檔案為/root/silencewolf/silencewolf.sh 裡面內容如下: 執行rm -f silencewolf.sh,用ls檢視資料夾為空了 下面開始恢復檔案,具體操作如下: 1、輸入debugfs,開

linux檔案管理類命令有哪些常用的使用方法及其相關例項演示

Linux 一切皆檔案。個人理解 在linux下的命令操作都算是對檔案操作 那麼檔案管理命令類命令可以分為下面幾類 目錄操作: 特殊目錄解釋: . 代表此層目錄;.. 代表上一層目錄;- 代表前一個目錄;~ 代表當前使用者的主資料夾 也可以稱為家目錄