深入解析 ext2 文件系統

很久以來,就想寫一篇關於ext 家族文件系統的文章,源於我剛工作的時候,曾經一不小心rm -rf,誤刪除了很多文件,當時真想有個數據恢復軟件能幫我把數據回覆了。當然學習數據恢復,首先要學習文件系統。最近工作原因,好長時間沒看學習Linux kernel 相關的東西,感覺面目可憎。扯遠了,開始我們的ext2 文件系統的探索之旅。

那些介紹ext2特徵的套話我就不說了,任何一本靠譜的linux教程中都可以找到,我們直接單刀直入,開始探索。

首先生成一個ext2文件系統。我在我磁盤空間有限的Ubuntu中,劃出500M的空間來從頭學習ext2 文件系統。

dd命令用來創建一個文件,不多說了,通過執行這個dd命令生成了一個全零的大小爲512000*1KB的文件,即500MB 的文件。

losetup是設定循環設備(loop service)的,循環設備可以將文件模擬成塊設備。然後在塊設備上建立我們的ext2文件系統,來進行我們的學習。所以下面用mke2fs命令將loop設備格式化成ext2文件系統。 Oh,yeah,我們終於有了ext2文件系統。

這裏需要強調下,我們調用了mke2fs的默認選項其中:

root@libin:~# dd if=/dev/zero of=bean bs=1K count=512000
記錄了512000 0 的讀入
記錄了512000 0 的寫出
524288000字節(524 MB)已複製,9.40989 秒,55.7 MB/秒
root@libin:~# ll bean
-rw-r--r-- 1 root root 524288000 2012-07-06 22:24 bean
root@libin:~# ll -h bean
-rw-r--r-- 1 root root 500M 2012-07-06 22:24 bean
root@libin:~#
root@libin:~#
root@libin:~# losetup /dev/loop0 bean

root@libin:~# cat /proc/partitions
major minor #blocks name

7 0 512000 loop0
8 0 312571224 sda
8 1 49182966 sda1
.......

oot@libin:~# mke2fs /dev/loop0
mke2fs 1.41.11 (14-Mar-2010)
文件系統標籤=
操作系統inux
塊大小=1024 (log=0)
分塊大小=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
128016 inodes, 512000 blocks
25600 blocks (5.00%) reserved for the super user
第一個數據塊=1
Maximum filesystem blocks=67633152
63 block groups
8192 blocks per group, 8192 fragments per group
2032 inodes per group
Superblock backups stored on blocks:
8193, 24577, 40961, 57345, 73729, 204801, 221185, 401409

正在寫入inode表: 完成
Writing superblocks and filesystem accounting information: 完成

This filesystem will be automatically checked every 24 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
但是這樣還沒完,我們還是不能訪問我們新建的ext2文件系統,因爲還沒有掛載,我決定將loop 設備掛載在/mnt/bean 目錄下。

mkdir /mnt/bean
mount -t ext2 /dev/loop0 /mnt/bean

root@libin:/mnt/bean# mount
.........
/dev/loop0 on /mnt/bean type ext2 (rw)

root@libin:/mnt/bean# ll
總用量 17
drwxr-xr-x 3 root root 1024 2012-07-06 22:31 ./
drwxr-xr-x 4 root root 4096 2012-07-06 22:32 ../
drwx------ 2 root root 12288 2012-07-06 22:31 lost found/
經過我們的努力,我們終於創建好了我們的ext2文件系統。下面需要講講ext2文件系統的結構是什麼樣的了。

下面這張圖是經典的ext2文件系統的結構圖。網上到處可以找到這種類似的圖片,但是我非要畫這個圖片的原因是爲了澄清2個問題:

1 並不是所有的塊組都有超級塊和快組描述符。
2 塊組描述符GDT並不是只管理自己這個塊組的信息,相反,它管理的是所有的塊組的信息。

(inode表和數據塊的個數不一定相等,我這個圖畫多少有點問題)

 我們知道,超級塊是很重要的,因爲它告訴了linux 這個塊設備是怎樣組織的,它告訴linux我這個文件系統是什麼文件系統,每個塊的大小是多大(1024、2048 or 4096),每個塊組有多少個塊,inode佔多少個字節。等等的信息。正是因爲超級塊很重要,所以我們不能將這些信息只保存1份。試想一下,如果超級塊壞掉了,而我們只有一個塊組有超級塊,那麼就徹底完蛋了,後面接近500M的空間及裏面的數據我們都沒辦法獲得了。這是比較容易理解的。但是,是不是每個塊組都要有啓動塊呢。這就沒必要了,這也有點空間浪費。那到底把超級塊放到那些塊組呢?

Superblock backups stored on blocks:
8193, 24577, 40961, 57345, 73729, 204801, 221185, 401409
這是格式化loop設備輸出到終端的result信息,因爲每個塊組是8192個塊(原因後面講),所以第0個塊組 ,第1塊組,第3個塊組 第5個塊組,第7個塊組,第9個塊組,第25個塊組,第27個塊組,第49個塊組存儲有超級塊。

怎麼計算出來的,爲什麼非要存在這些塊組?計算規則是3 5 和7的冪,這樣的塊組保存超級塊。

解釋塊組描述符之前我們先看下超級塊的相關信息:

struct ext2_super_block {
u32 s_inodes_count;
u32 s_blocks_count;
u32 s_r_blocks_count;
__u32 s_free_blocks_count;
u32 s_free_inodes_count;
u32 s_first_data_block;
__u32 s_log_block_size;
u32 s_dummy3[7];
unsigned char s_magic[2];
__u16 s_state;
...

}
下面我們通過debugfs來獲取一下ext2的相關信息。

root@libin:/mnt/bean# dumpe2fs /dev/loop0
dumpe2fs 1.41.11 (14-Mar-2010)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: 3bff7535-6f39-4720-9b64-1dc8cf9fe61d
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super
Filesystem flags: signed_directory_hash
Default mount options: (none)
Filesystem state: not clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 128016
Block count: 512000
Reserved block count: 25600
Free blocks: 493526
Free inodes: 128005
First block: 1
Block size: 1024
Fragment size: 1024
Reserved GDT blocks: 256
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 2032
Inode blocks per group: 254
Filesystem created: Fri Jul 6 22:31:09 2012
Last mount time: Fri Jul 6 22:33:28 2012
Last write time: Fri Jul 6 22:33:28 2012
Mount count: 1
Maximum mount count: 24
Last checked: Fri Jul 6 22:31:09 2012
Check interval: 15552000 (6 months)
Next check after: Wed Jan 2 22:31:09 2013
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 128
Default directory hash: half_md4
Directory Hash Seed: 0140915d-91ae-43df-9d84-9536cedc0d2b

Group 0: (Blocks 1-8192)
主 superblock at 1, Group descriptors at 2-3
保留的GDT塊位於 4-259
Block bitmap at 260 ( 259), Inode bitmap at 261 ( 260)
Inode表位於 262-515 ( 261)
7663 free blocks, 2021 free inodes, 2 directories
可用塊數: 530-8192
可用inode數: 12-2032
...
Group 62: (Blocks 507905-511999)
Block bitmap at 507905 (+0), Inode bitmap at 507906 (+1)
Inode表位於 507907-508160 (+2)
3839 free blocks, 2032 free inodes, 0 directories
可用塊數: 508161-511999
可用inode數: 125985-128016
OK ,我們拿到了這些信息,但是,我怎麼證明debugfs拿到的信息是對的呢。只有一個辦法,我們鑽到超級塊裏面,根據超級塊數據結構,獲得超級塊每個字段的值,聽起來很刺激吧,OK,Just DO IT。

root@libin:/mnt/bean# dd if=/dev/loop0 bs=1k count=261 |od -tx1 -Ax > /tmp/dump_hex
記錄了261 0 的讀入
記錄了261 0 的寫出
267264字節(267 kB)已複製,0.0393023 秒,6.8 MB/秒
root@libin:/mnt/bean# vi /tmp/dump_hex
我將整個loop設備前面的261K字節讀入了/tmp/dump_hex中。其中第0塊是啓動塊,按下不提。第一塊就是說super block。很激動,我們終於可以和傳說中的超級塊赤裸相見了。

000400 10 f4 01 00 00 d0 07 00 00 64 00 00 d6 87 07 00
000410 05 f4 01 00 01 00 00 00 00 00 00 00 00 00 00 00
000420 00 20 00 00 00 20 00 00 f0 07 00 00 5f cb f7 4f
000430 5f cb f7 4f 01 00 1a 00 53 ef 00 00 01 00 00 00
000440 25 cb f7 4f 00 4e ed 00 00 00 00 00 01 00 00 00
000450 00 00 00 00 0b 00 00 00 80 00 00 00 38 00 00 00
000460 02 00 00 00 01 00 00 00 5a 65 4b 92 fe 63 43 eb
000470 b6 86 3e f3 6e 44 19 af 00 00 00 00 00 00 00 00
000480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0004c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01
0004d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0004e0 00 00 00 00 00 00 00 00 00 00 00 00 f9 6f 16 79
0004f0 b7 dc 4f 8a a1 a1 18 82 72 a7 d8 25 01 00 00 00
000500 00 00 00 00 00 00 00 00 25 cb f7 4f 00 00 00 00
000510 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

000560 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000570 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000800 04 01 00 00 05 01 00 00 06 01 00 00 ef 1d e5 07

    最左邊一列是地址,16進制。000400=1K,換句話說,就是文件第1K個字節。000800 =2K,這就是我們朝思暮想的超級塊啊。我很激動,所以把整個超級塊都貼上了,幸好我不是靠字數來騙稿費的人,否則咱得被鄙視死。
    再把ext2超級塊的數據結構貼上,咱挨個字段比較比較,看看debugfs說的對不?

struct ext2_super_block {
u32 s_inodes_count;
u32 s_blocks_count;
u32 s_r_blocks_count;
__u32 s_free_blocks_count;
u32 s_free_inodes_count;
__u32 s_first_data_block;
__u32 s_log_block_size;

...

}
第一個字段叫s_inodes_count, 佔四個字節。OK,我們看,從1K開始前四個字節是10 f4 01 00。我們知道有little-endian和big-endian。ext2設計者爲了支持文件系統的可移動,規定磁盤上一律是little-endian,數據讀入內存中時,kernel來負責把格式轉成cpu的本機格式。

OK,是little-endian咱就明白了,不就是0x0001f410嘛 。 0x0001f410=128016,看看debugfs給我們的數據,Inode count: 128016,一模一樣。

再舉個例子,比如,我們關心free_blocks_count,查看數據結構,free_blocks_count字段起始位置是超級塊的第12字節。即00040c地址。看下的 d6 87 07 00。計算以下可以得到0x000787d6 = 493526,和debugfs 的Free blocks給出的一樣。OK。看管關心什麼字段,可以自己查看。通過和超級塊赤裸想見,我們知道了ext2 super block的結構。

最後總結一句,不是所有的塊組都有超級塊,超級塊只佔1個block塊,沒錯,當blocksize爲4K的時候,這個塊大多數空間是浪費的。不過還好,畢竟超級塊個數有限,浪費不了多少。

 下面講述 塊組描述符:

組描述符一共32個字節,大多數的教材都會給我們一組誤解,就是每個塊組,都要有組描述符。事實上並不是這樣。我們知道,一個組描述符只佔32字節,而大多數的教材都會告訴我們,一個塊組裏面的組描述符佔k個塊,一個組描述符是用不了這麼多空間的。

真相只有一個,就是所有的組描述符以數組的形式存放在k個塊中。也就是說,某個塊組可能沒有組描述符,而有組描述符的塊組,k個block中存放了所有組塊的組描述符。下面我來證實:

struct ext2_group_desc
{
u32 bg_block_bitmap; / Blocks bitmap block /
u32 bg_inode_bitmap; / Inodes bitmap block /
u32 bg_inode_table; / Inodes table block /
u16 bg_free_blocks_count; / Free blocks count /
u16 bg_free_inodes_count; / Free inodes count /
__u16 bg_used_dirs_count; / Directories count /
u16 bg_flags;
__u32 bg_exclude_bitmap_lo;/ Exclude bitmap for snapshots /
u16 bg_block_bitmap_csum_lo;/ crc32c(s_uuid+grp_num+bitmap)LSB /
u16 bg_inode_bitmap_csum_lo;/ crc32c(s_uuid+grp_num+bitmap)LSB /
u16 bg_itable_unused; / Unused inodes count /
u16 bg_checksum; / crc16(s_uuid+grouo_num+group_desc)/
};

Group 0: (Blocks 1-8192)
主 superblock at 1, Group descriptors at 2-3
保留的GDT塊位於 4-259
Block bitmap at 260 (+259), Inode bitmap at 261 (+260)
Inode表位於 262-515 (+261)
7663 free blocks, 2021 free inodes, 2 directories
可用塊數: 530-8192
可用inode數: 12-2032
Group 1: (Blocks 8193-16384)
備份 superblock at 8193, Group descriptors at 8194-8195
保留的GDT塊位於 8196-8451
Block bitmap at 8452 (+259), Inode bitmap at 8453 (+260)
Inode表位於 8454-8707 (+261)
7677 free blocks, 2032 free inodes, 0 directories
可用塊數: 8708-16384
可用inode數: 2033-4064
Group 2: (Blocks 16385-24576)
Block bitmap at 16385 (+0), Inode bitmap at 16386 (+1)
Inode表位於 16387-16640 (+2)
7936 free blocks, 2032 free inodes, 0 directories
可用塊數: 16641-24576
可用inode數: 4065-6096
看上圖,debugfs出來的信息,Group 2,並沒有所謂的組描述符。而Group1,用8194和8195兩個塊來存儲。OK,我們看下,裏面存儲的是什麼東西。

Group 0裏面第2和第3塊存儲的是組描述符,也就說從0x000800~0x001000是組描述符塊的內容。

000800 04 01 00 00 05 01 00 00 06 01 00 00 ef 1d e5 07
000810 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 塊組0的組描述符

000820 04 21 00 00 05 21 00 00 06 21 00 00 fd 1d f0 07
000830 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 塊組1的組描述符

000840 01 40 00 00 02 40 00 00 03 40 00 00 00 1f f0 07
000850 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 塊組2的組描述符

000860 04 61 00 00 05 61 00 00 06 61 00 00 fd 1d f0 07
000870 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000880 01 80 00 00 02 80 00 00 03 80 00 00 00 1f f0 07
000890 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
0008a0 04 a1 00 00 05 a1 00 00 06 a1 00 00 fd 1d f0 07
0008b0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
0008c0 01 c0 00 00 02 c0 00 00 03 c0 00 00 00 1f f0 07
0008d0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
0008e0 04 e1 00 00 05 e1 00 00 06 e1 00 00 fd 1d f0 07
0008f0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000900 01 00 01 00 02 00 01 00 03 00 01 00 00 1f f0 07
000910 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000fb0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000fc0 01 c0 07 00 02 c0 07 00 03 c0 07 00 ff 0e f0 07
000fd0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 塊組62的組描述符

000fe0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

  • 沒有塊組63

    001000 04 20 00 00 04 60 00 00 04 a0 00 00 04 e0 00 00
    04 01 00 00 轉換成可讀的十進制是0x104=259,表示數據位圖位於第259塊block。inode位圖位於260,和debugfs出來的信息是一樣的(不算啓動塊)。0x1def=7663個空閒數據塊....

    各位看官可以自己解析任何一個塊組的相關信息,可以證明和debugfs出來的塊組的信息是一致的。現在我們確定了,組描述符以數組的形式存儲在K個快上,對於我們只有63個組塊,每個組塊需要32個字節,只需要2個1KB的block就足夠了。這就是說,其實組描述符和超級塊一樣,其實是冗餘的。也就是說,其他存儲組描述符的兩個block,信息和塊組0中的組描述符的兩個block是一樣的。下面我來證明。

    塊組25也有組描述符塊,204802和204803兩個塊,記錄了63個塊組的組描述符信息。內容應該和前面的塊組0的兩個塊一致。我已經取出了這兩個block的內容,大家自己比較吧,結果是內容是一樣的。

Group 25: (Blocks 204801-212992)
備份 superblock at 204801, Group descriptors at 204802-204803
保留的GDT塊位於 204804-205059
Block bitmap at 205060 (+259), Inode bitmap at 205061 (+260)
Inode表位於 205062-205315 (+261)
7677 free blocks, 2032 free inodes, 0 directories
可用塊數: 205316-212992
可用inode數: 50801-52832

點擊(此處)摺疊或打開
root@libin:/mnt/bean# dd if=/dev/loop0 bs=1k skip=204802 count=2|od -tx1 -Ax > /tmp/dumphex
記錄了2+0 的讀入
記錄了2+0 的寫出
2048字節(2.0 kB)已複製,0.000160205 秒,12.8 MB/秒
root@libin:/mnt/bean# vi /tmp/dumphex
000000 04 01 00 00 05 01 00 00 06 01 00 00 ef 1d e5 07
000010 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000020 04 21 00 00 05 21 00 00 06 21 00 00 fd 1d f0 07
000030 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000040 01 40 00 00 02 40 00 00 03 40 00 00 00 1f f0 07
000050 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000060 04 61 00 00 05 61 00 00 06 61 00 00 fd 1d f0 07
000070 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000080 01 80 00 00 02 80 00 00 03 80 00 00 00 1f f0 07
000090 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
0000a0 04 a1 00 00 05 a1 00 00 06 a1 00 00 fd 1d f0 07
0000b0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
....
0007c0 01 c0 07 00 02 c0 07 00 03 c0 07 00 ff 0e f0 07
0007d0 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
0007e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000800

最後,最後的最後,解釋以下,爲什麼每個塊組中的塊數blocks per group 是8192,因爲,我們用1個塊作爲位圖保存本塊組 block的使用情況(bit爲1表示對應的block被使用,bit爲0表示對應的block空閒),1個block是1024字節,共有1024*8=8192個bit,所以,每個塊組最多隻能是81292個塊。

同樣道理如果用戶使用的是4094大小的塊,那麼,4096*8=32768個bit,所以每個塊組會有32K個塊。證據在下面。

root@libin:/mnt/bean# cd /home
root@libin:/home# umount /dev/loop0
root@libin:/home# cd /mnt/bean
root@libin:/mnt/bean# ll
總用量 8
drwxr-xr-x 2 root root 4096 2012-07-06 22:32 ./
drwxr-xr-x 4 root root 4096 2012-07-06 22:32 ../
root@libin:/mnt/bean# mke2fs -b 4096 /dev/loop0
mke2fs 1.41.11 (14-Mar-2010)
文件系統標籤=
操作系統inux
塊大小=4096 (log=2)
分塊大小=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
128000 inodes, 128000 blocks
6400 blocks (5.00%) reserved for the super user
第一個數據塊=0
Maximum filesystem blocks=134217728
4 block groups
32768 blocks per group, 32768 fragments per group
32000 inodes per group
Superblock backups stored on blocks:
32768, 98304

正在寫入inode表: 完成
Writing superblocks and filesystem accounting information: 完成

This filesystem will be automatically checked every 39 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章