開源系統Linux 上比較流行使用的是 ext2 文件系統,我們來了解一下硬盤ext2文件系統分區結構。ext2 文件系統加上日志支持的下一個版本是 ext3 文件系統,它和 ext2 文件系統在硬盤布局上是一樣的,其差別僅僅是 ext3 文件系統在硬盤上多出了一個特殊的 inode(可以理解為一個特殊文件),用來記錄文件系統的日志,也即所謂的 journal。由于本文并不討論日志文件,所以本文的內容對于 ext2 和 ext3 都是適用的,對ext2、ext3、ext4文件系統數據恢復有非常大的幫助。
粗略的描述
對于 ext2 文件系統來說,硬盤分區首先被劃分為一個個的 block,一個 ext2 文件系統上的每個 block 都是一樣大小的,但是對于不同的 ext2 文件系統,block 的大小可以有區別。典型的 block 大小是 1024 bytes 或者 4096 bytes。這個大小在創建 ext2 文件系統的時候被決定,它可以由系統管理員指定,也可以由文件系統的創建程序根據硬盤分區的大小,自動選擇一個較合理的值。這些 blocks 被聚在一起分成幾個大的 block group。每個 block group 中有多少個 block 是固定的。
每個 block group 都相對應一個 group descriptor,這些 group descriptor 被聚在一起放在硬盤分區的開頭部分,跟在 super block 的后面。所謂 super block,我們下面還要講到。在這個 descriptor 當中有幾個重要的 block 指針。我們這里所說的 block 指針,就是指硬盤分區上的 block 號數,比如,指針的值為 0,我們就說它是指向硬盤分區上的 block 0;指針的值為 1023,我們就說它是指向硬盤分區上的 block 1023。我們注意到,一個硬盤分區上的 block 計數是從 0 開始的,并且這個計數對于這個硬盤分區來說是全局性質的。
在 block group 的 group descriptor 中,其中有一個 block 指針指向這個 block group 的 block bitmap,block bitmap 中的每個 bit 表示一個 block,如果該 bit 為 0,表示該 block 中有數據,如果 bit 為 1,則表示該 block 是空閑的。注意,這個 block bitmap 本身也正好只有一個 block 那么大小。假設 block 大小為 S bytes,那么 block bitmap 當中只能記載 8*S 個 block 的情況(因為一個 byte 等于 8 個 bits,而一個 bit 對應一個 block)。這也就是說,一個 block group 最多只能有 8*S*S bytes 這么大。
在 block group 的 group descriptor 中另有一個 block 指針指向 inode bitmap,這個 bitmap 同樣也是正好有一個 block 那么大,里面的每一個 bit 相對應一個 inode。硬盤上的一個 inode 大體上相對應于文件系統上的一個文件或者目錄。關于 inode,我們下面還要進一步講到。
在 block group 的 descriptor 中另一個重要的 block 指針,是指向所謂的 inode table。這個 inode table 就不止一個 block 那么大了。這個 inode table 就是這個 block group 中所聚集到的全部 inode 放在一起形成的。
一個 inode 當中記載的最關鍵的信息,是這個 inode 中的用戶數據存放在什么地方。我們在前面提到,一個 inode 大體上相對應于文件系統中的一個文件,那么用戶文件的內容存放在什么地方,這就是一個 inode 要回答的問題。一個 inode 通過提供一系列的 block 指針,來回答這個問題。這些 block 指針指向的 block,里面就存放了用戶文件的內容。
回顧
現在我們回顧一下。硬盤分區首先被分為好多個 block。這些 block 聚在一起,被分成幾組,也就是 block group。每個 block group 都有一個 group descriptor。所有這些 descriptor 被聚在一起,放在硬盤分區的開頭部分,跟在 super block 的后面。從 group descriptor 我們可以通過 block 指針,找到這個 block group 的 inode table 和 block bitmap 等等。從 inode table 里面,我們就可以看到一個個的 inode 了。從一個 inode,我們通過它里面的 block 指針,就可以進而找到存放用戶數據的那些 block。我們還要提一下,block 指針不是可以到處亂指的。一個 block group 的 block bitmap 和 inode bitmap 以及 inode table,都依次存放在這個 block group 的開頭部分,而那些存放用戶數據的 block 就緊跟在它們的后面。一個 block group 結束后,另一個 block group 又跟著開始。
詳細的分區結構
Super Block
所謂 ext2 文件系統的 super block,就是硬盤分區開頭(開頭的第一個 byte 是 byte 0)從 byte 1024 開始往后的一部分數據。由于 block size 最小是 1024 bytes,所以 super block 可能是在 block 1 中(此時 block 的大小正好是 1024 bytes),也可能是在 block 0 中。
硬盤分區上 ext3 文件系統的 super block 的詳細情況如下。其中 __u32 是表示 unsigned 不帶符號的 32 bits 的數據類型,其余類推。這是 Linux 內核中所用到的數據類型,如果是開發用戶空間(user-space)的程序,可以根據具體計算機平臺的情況,用 unsigned long 等等來代替。下面列表中關于 fragments 的部分可以忽略,Linux 上的 ext3 文件系統并沒有實現 fragments 這個特性。另外要注意,ext3 文件系統在硬盤分區上的數據是按照 Intel 的 Little-endian 格式存放的,如果是在 PC 以外的平臺上開發 ext3 相關的程序,要特別注意這一點。如果只是在 PC 上做開發,倒不用特別注意。
struct ext3_super_block { /*00*/ __u32 s_inodes_count; /* inodes 計數 */ __u32 s_blocks_count; /* blocks 計數 */ __u32 s_r_blocks_count; /* 保留的 blocks 計數 */ __u32 s_free_blocks_count; /* 空閑的 blocks 計數 */ /*10*/ __u32 s_free_inodes_count; /* 空閑的 inodes 計數 */ __u32 s_first_data_block; /* 第一個數據 block */ __u32 s_log_block_size; /* block 的大小 */ __s32 s_log_frag_size; /* 可以忽略 */ /*20*/ __u32 s_blocks_per_group; /* 每 block group 的 block 數量 */ __u32 s_frags_per_group; /* 可以忽略 */ __u32 s_inodes_per_group; /* 每 block group 的 inode 數量 */ __u32 s_mtime; /* Mount time */ /*30*/ __u32 s_wtime; /* Write time */ __u16 s_mnt_count; /* Mount count */ __s16 s_max_mnt_count; /* Maximal mount count */ __u16 s_magic; /* Magic 簽名 */ __u16 s_state; /* File system state */ __u16 s_errors; /* Behaviour when detecting errors */ __u16 s_minor_rev_level; /* minor revision level */ /*40*/ __u32 s_lastcheck; /* time of last check */ __u32 s_checkinterval; /* max. time between checks */ __u32 s_creator_os; /* 可以忽略 */ __u32 s_rev_level; /* Revision level */ /*50*/ __u16 s_def_resuid; /* Default uid for reserved blocks */ __u16 s_def_resgid; /* Default gid for reserved blocks */ __u32 s_first_ino; /* First non-reserved inode */ __u16 s_inode_size; /* size of inode structure */ __u16 s_block_group_nr; /* block group # of this superblock */ __u32 s_feature_compat; /* compatible feature set */ /*60*/ __u32 s_feature_incompat; /* incompatible feature set */ __u32 s_feature_ro_compat; /* readonly-compatible feature set */ /*68*/ __u8 s_uuid[16]; /* 128-bit uuid for volume */ /*78*/ char s_volume_name[16]; /* volume name */ /*88*/ char s_last_mounted[64]; /* directory where last mounted */ /*C8*/ __u32 s_algorithm_usage_bitmap; /* 可以忽略 */ __u8 s_prealloc_blocks; /* 可以忽略 */ __u8 s_prealloc_dir_blocks; /* 可以忽略 */ __u16 s_padding1; /* 可以忽略 */ /*D0*/ __u8 s_journal_uuid[16]; /* uuid of journal superblock */ /*E0*/ __u32 s_journal_inum; /* 日志文件的 inode 號數 */ __u32 s_journal_dev; /* 日志文件的設備號 */ __u32 s_last_orphan; /* start of list of inodes to delete */ /*EC*/ __u32 s_reserved[197]; /* 可以忽略 */ };
我們可以看到,super block 一共有 1024 bytes 那么大。在 super block 中,我們第一個要關心的字段是 magic 簽名,對于 ext2 和 ext3 文件系統來說,這個字段的值應該正好等于 0xEF53。如果不等的話,那么這個硬盤分區上肯定不是一個正常的 ext2 或 ext3 文件系統。從這里,我們也可以估計到,ext2 和 ext3 的兼容性一定是很強的,不然的話,Linux 內核的開發者應該會為 ext3 文件系統另選一個 magic 簽名才對。
在 super block 中另一個重要的字段是 s_log_block_size。從這個字段,我們可以得出真正的 block 的大小。我們把真正 block 的大小記作 B,B = 1 << (s_log_block_size + 10),單位是 bytes。舉例來說,如果這個字段是 0,那么 block 的大小就是 1024 bytes,這正好就是最小的 block 大小;如果這個字段是 2,那么 block 大小就是 4096 bytes。從這里我們就得到了 block 的大小這一非常重要的數據。
我們繼續往下,看跟在 super block 后面的一堆 group descriptors。首先注意到 super block 是從 byte 1024 開始,一共有 1024 bytes 那么大。而 group descriptors 是從 super block 后面的第一個 block 開始。也就是說,如果 super block 是在 block 0,那么 group descriptors 就是從 block 1 開始;如果 super block 是在 block 1,那么 group descriptors 就是從 block 2 開始。因為 super block 一共只有 1024 bytes 那么大,所以不會超出一個 block 的邊界。如果一個 block 正好是 1024 bytes 那么大的話,我們看到 group descriptors 就是緊跟在 super block 后面的了,沒有留一點空隙。而如果一個 block 是 4096 bytes 那么大的話,那么在 group descriptors(從 byte 4096 開始)和 super block 的結尾之間,就有一定的空隙(4096 - 2048 bytes)。
那么硬盤分區上一共有多少個 block group,或者說一共有多少個 group descriptors,這我們要在 super block 中找答案。super block 中的 s_blocks_count 記錄了硬盤分區上的 block 的總數,而 s_blocks_per_group 記錄了每個 group 中有多少個 block。顯然,文件系統上的 block groups 數量,我們把它記作 G,G = (s_blocks_count - s_first_data_block - 1) / s_blocks_per_group + 1。為什么要減去 s_first_data_block,因為 s_blocks_count 是硬盤分區上全部的 block 的數量,而在 s_first_data_block 之前的 block 是不歸 block group 管的,所以當然要減去。最后為什么又要加一,這是因為尾巴上可能多出來一些 block,這些 block 我們要把它劃在一個相對較小的 group 里面。
注意,硬盤分區上的所有這些 group descriptors 要能塞在一個 block 里面。也就是說 groups_count * descriptor_size 必須小于等于 block_size。
知道了硬盤分區上一共有多少個 block group,我們就可以把這么多個 group descriptors 讀出來了。先來看看 group descriptor 是什么樣子的。
struct ext3_group_desc { __u32 bg_block_bitmap; /* block 指針指向 block bitmap */ __u32 bg_inode_bitmap; /* block 指針指向 inode bitmap */ __u32 bg_inode_table; /* block 指針指向 inodes table */ __u16 bg_free_blocks_count; /* 空閑的 blocks 計數 */ __u16 bg_free_inodes_count; /* 空閑的 inodes 計數 */ __u16 bg_used_dirs_count; /* 目錄計數 */ __u16 bg_pad; /* 可以忽略 */ __u32 bg_reserved[3]; /* 可以忽略 */ };
每個 group descriptor 是 32 bytes 那么大。從上面,我們看到了三個關鍵的 block 指針,這三個關鍵的 block 指針,我們已經在前面都提到過了。
前面都準備好了以后,我們現在終于可以開始讀取文件了。首先要讀的,當然是文件系統的根目錄。注意,這里所謂的根目錄,是相對于這一個文件系統或者說硬盤分區而言的,它并不一定是整個 Linux 操作系統上的根目錄。這里的這個 root 目錄存放在一個固定的 inode 中,這就是文件系統上的 inode 2。需要提到 inode 計數同 block 計數一樣,也是全局性質的。這里需要特別注意的是,inode 計數是從 1 開始的,而前面我們提到過 block 計數是從 0 開始,這個不同在開發程序的時候要特別留心。(這一奇怪的 inode 計數方法,曾經讓本文作者大傷腦筋。)
那么,我們先來看一下得到一個 inode 號數以后,怎樣讀取這個 inode 中的用戶數據。在 super block 中有一個字段 s_inodes_per_group 記載了每個 block group 中有多少個 inode。用我們得到的 inode 號數除以 s_inodes_per_group,我們就知道了我們要的這個 inode 是在哪一個 block group 里面,這個除法的余數也告訴我們,我們要的這個 inode 是這個 block group 里面的第幾個 inode;然后,我們可以先找到這個 block group 的 group descriptor,從這個 descriptor,我們找到這個 group 的 inode table,再從 inode table 找到我們要的第幾個 inode,再以后,我們就可以開始讀取 inode 中的用戶數據了。
這個公式是這樣的:block_group = (ino - 1) / s_inodes_per_group。這里 ino 就是我們的 inode 號數。而 offset = (ino - 1) % s_inodes_per_group,這個 offset 就指出了我們要的 inode 是這個 block group 里面的第幾個 inode。
找到這個 inode 之后,我們來具體的看看 inode 是什么樣的。
struct ext3_inode { __u16 i_mode; /* File mode */ __u16 i_uid; /* Low 16 bits of Owner Uid */ __u32 i_size; /* 文件大小,單位是 byte */ __u32 i_atime; /* Access time */ __u32 i_ctime; /* Creation time */ __u32 i_mtime; /* Modification time */ __u32 i_dtime; /* Deletion Time */ __u16 i_gid; /* Low 16 bits of Group Id */ __u16 i_links_count; /* Links count */ __u32 i_blocks; /* blocks 計數 */ __u32 i_flags; /* File flags */ __u32 l_i_reserved1; /* 可以忽略 */ __u32 i_block[EXT3_N_BLOCKS]; /* 一組 block 指針 */ __u32 i_generation; /* 可以忽略 */ __u32 i_file_acl; /* 可以忽略 */ __u32 i_dir_acl; /* 可以忽略 */ __u32 i_faddr; /* 可以忽略 */ __u8 l_i_frag; /* 可以忽略 */ __u8 l_i_fsize; /* 可以忽略 */ __u16 i_pad1; /* 可以忽略 */ __u16 l_i_uid_high; /* 可以忽略 */ __u16 l_i_gid_high; /* 可以忽略 */ __u32 l_i_reserved2; /* 可以忽略 */ };
我們看到在 inode 里面可以存放 EXT3_N_BLOCKS(= 15)這么多個 block 指針。用戶數據就從這些 block 里面獲得。15 個 blocks 不一定放得下全部的用戶數據,在這里 ext3 文件系統采取了一種分層的結構。這組 15 個 block 指針的前 12 個是所謂的 direct blocks,里面直接存放的就是用戶數據。第 13 個 block,也就是所謂的 indirect block,里面存放的全部是 block 指針,這些 block 指針指向的 block 才被用來存放用戶數據。第 14 個 block 是所謂的 double indirect block,里面存放的全是 block 指針,這些 block 指針指向的 block 也被全部用來存放 block 指針,而這些 block 指針指向的 block,才被用來存放用戶數據。第 15 個 block 是所謂的 triple indirect block,比上面說的 double indirect block 有多了一層 block 指針。作為練習,讀者可以計算一下,這樣的分層結構可以使一個 inode 中最多存放多少字節的用戶數據。(計算所需的信息是否已經足夠?還缺少哪一個關鍵數據?)
一個 inode 里面實際有多少個 block,這是由 inode 字段 i_size 再通過計算得到的。i_size 記錄的是文件或者目錄的實際大小,用它的值除以 block 的大小,就可以得出這個 inode 一共占有幾個 block。注意上面的 i_blocks 字段,粗心的讀者可能會以為是這一字段記錄了一個 inode 中實際用到多少個 block,其實不是的。那么這一字段是干什么用的呢,讀者朋友們可以借這個機會,體驗一下閱讀 Linux 內核源代碼的樂趣。現在我們已經可以讀取 inode 的內容了,再往后,我們將要讀取文件系統上文件和目錄的內容。讀取文件的內容,只要把相應的 inode 的內容全部讀出來就行了;而目錄只是一種固定格式的文件,這個文件按照固定的格式記錄了目錄中有哪些文件,以及它們的文件名,和 inode 號數等等。
struct ext3_dir_entry_2 { __u32 inode; /* Inode 號數 */ __u16 rec_len; /* Directory entry length */ __u8 name_len; /* Name length */ __u8 file_type; char name[EXT3_NAME_LEN]; /* File name */ };
上面用到的 EXT3_NAME_LEN 是 255。注意,在硬盤分區上的 dir entry 不是固定長度的,每個 dir entry 的長度由上面的 rec_len 字段記錄。
以上內容主要講解ext2文件系統分區結構,分區結構內容講解也非常詳細,希望對數據恢復愛好者或者同行有點幫助。
分秒必爭 RAID數據恢復成功案例 與個人用戶數據或者公司財務部門數據等不同,RAID服務...
ACCESS是由微軟發布的關聯式數據庫管理系統。它結合了 Microsoft Jet Database Engin...
故障情況: 一塊160G硬盤被誤使用快速分區工具分成了四個區,并且安裝了系統在C盤,后發...
硬盤作為用戶存儲數據的主要場所,其最大的用途莫過于存儲數據,但有時往往由于用戶操作...
raid數據恢復,raid5數據恢復,raid磁盤陣列數據恢復
中國數據恢復市場有著長足發展,數據恢復公司也是遍地開花,普通用戶對這一行業也有了一...
彭先生的希捷320GB硬盤是0磁道損壞,但是分區還在,提示分區未格式化,顯示分區出現邏輯...