一个磁盘可以划分成多个分区,每个分区必须先用格式化工具(例如mkfs命令)格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局的信息。下以ext2文件系统为例说明文件在磁盘上如何存储。
一个分区ext2文件系统的总体存储布局
注:上图为一个ext2分区,一个ext2分区有好多快组组成
启动块大小确定,1KB,启动块由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划分成若干个同样大小的块组(Block Group),每个块组都是有以下部分组成。
超级块(Super Block)
描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。
超级块在每个块组的开头都有一份拷贝。
块组描述符表(GDT,Group Descriptor Table)
由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。
块位图(Block Bitmap)
一个块组中的块是这样利用的:数据块存储所有文件的数据,比如某个分区的块大小是1024字节,某个文件是2049字节,那么就需要三个数据块来存,即使第三个块只存了一个字节也需要占用一个整块;超级块、块组描述符表、块位图、inode位图、inode表这几部分存储该块组的描述信息。那么如何知道哪些块已经用来存储文件数据或其它描述信息,哪些块仍然空闲可用呢?块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。
为什么用df命令统计整个磁盘的已用空间非常快呢?因为只需要查看每个块组的块位图即可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因
为不可避免地要搜遍整个目录的所有文件。
与此相联系的另一个问题是:在格式化一个分区时究竟会划出多少个块组呢?主要的限制在于块位图本身必须只占一个块。用mke2fs格式化时默认块大小是1024字节,可以用-b参数指定块大小,现在设块大小指定为b字节,那么一个块可以有8b个bit,这样大小的一个块位图就可以表示8b个块的占用情况,因此一个块组最多可以有8b个块,如果整个分区有s个块,那么就可以有s/(8b)个块组。格式化时可以用-g参数指定一个块组有多少个块,但是通常不需要手动指定,mke2fs工具会计算出最优的数值。
inode位图(inode Bitmap)
和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。
inode表(inode Table)
我们知道,一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。
inode表占多少个块在格式化时就要决定并写入块组描述符中,mke2fs格式化工具的默认策略是一个块组有多少个8KB就分配多少个inode。由于数据块占了整个块组的绝大部分,也可以近似认为数据块有多少个8KB就分配多少个inode,换句话说,如果平均每个文件的大小是8KB,当分区存满的时候inode表会得到比较充分的利用,数据块也不浪费。如果这个分区存的都是很大的文件(比如电影),则数据块用完的时候inode会有一些浪费,如果这个分区存的都是很小的文件(比如源代码),则有可能数据块还没用完inode就已经用完了,数据块可能有很大的浪费。如果用户在格式化时能够对这个分区以后要存储的文件大小做一个预测,也可以用mke2fs的-i参数手动指定每多少个字节分配一个inode。
数据块(Data Block)
根据不同的文件类型有以下几种情况
》对于常规文件,文件的数据存储在数据块中。
》对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。
》对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
》设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。
现在做几个小实验来理解这些概念。例如在home目录下ls -l:
$ ls -l
total 32
drwxr-xr-x 114 akaedu akaedu 12288 2008-10-25 11:33 akaedu
drwxr-xr-x 114 ftp ftp 4096 2008-10-25 10:30 ftp
drwx------ 2 root root 16384 2008-07-04 05:58 lost+found
为什么各目录的大小都是4096的整数倍?因为这个分区的块大小是4096,目录的大小总是数据块的整数倍。为什么有的目录大有的目录小?因为目录的数据块保存着它下边所有文件和目录的名字,如果一个目录中的文件很多,一个块装不下这么多文件名,就可能分配更多的数据块
给这个目录。再比如:
$ ls -l /dev
......
prw-r----- 1 syslog adm 0 2008-10-25 11:39 xconsole
crw-rw-rw- 1 root root 1, 5 2008-10-24 16:44 zero
xconsole文件的类型是p(表示pipe),是一个FIFO文件,后面会讲到它其实是一块内核缓冲区的标识,不在磁盘上保存数据,因此没有数据块,文件大小是0。zero文件的类型是c,表示字符设备文件,它代表内核中的一个设备驱动程序,也没有数据块,原本应该写文件大小的地方写了1, 5这两个数字,表示主设备号和次设备号,访问该文件时,内核根据设备号找到相应的驱动程序。再比如:
$ touch hello
$ ln -s ./hello halo
$ ls -l
total 0
lrwxrwxrwx 1 akaedu akaedu 7 2008-10-25 15:04 halo -> ./hello
-rw-r--r-- 1 akaedu akaedu 0 2008-10-25 15:04 hello
文件hello是刚创建的,字节数为0,符号链接文件halo指向hello,字节数却是7,为什么呢?
其实7就是“./hello”这7个字符,符号链接文件就保存着这样一个路径名。再试试硬链接:
$ ln ./hello hello2
$ ls -l
total 0
lrwxrwxrwx 1 akaedu akaedu 7 2008-10-25 15:08 halo -> ./hello
-rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello
-rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello2
hello2和hello除了文件名不一样之外,别的属性都一模一样,并且hello的属性发生了变化,第二栏的数字原本是1,现在变成2了。从根本上说,hello和hello2是同一个文件在文件系统中的两个名字,ls -l第二栏的数字是硬链接数,表示一个文件在文件系统中有几个名字(这些名字可以保存在不同目录的数据块中,或者说可以位于不同的路径下),硬链接数也保存在inode中。既然是同一个文件,inode当然只有一个,所以用ls -l看它们的属性是一模一样的,因为都是从这个inode里读出来的。再研究一下目录的硬链接数:
$ mkdir a
$ mkdir a/b
$ ls -ld a
drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 a
$ ls -la a
total 20
drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 .
drwxr-xr-x 115 akaedu akaedu 12288 2008-10-25 16:14 ..
drwxr-xr-x 2 akaedu akaedu 4096 2008-10-25 16:15 b
$ ls -la a/b
total 8
drwxr-xr-x 2 akaedu akaedu 4096 2008-10-25 16:15 .
drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 ..
首先创建目录a,然后在它下面创建子目录a/b。目录a的硬链接数是3,这3个名字分别是当前目录下的a,a目录下的.和b目录下的..。目录b的硬链接数是2,这两个名字分别是a目录下的b和b目录下的.。注意,目录的硬链接只能这种方式创建,用ln命令可以创建目录的符号链接,但不能创建目录的硬链接。
实例剖析
用普通文件模拟分区描述整个过程。
#dd if=/dev/zero of=fs count=256 bs=4k ; 创建1M大小的文件
#mke2fs fs
mke2fs 1.42 (29-Nov-2011)
fs is not a block special device.
Proceed anyway? (y,n) y
Discarding device blocks: done
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
128 inodes, 1024 blocks
51 blocks (4.98%) reserved for the super user
First data block=1
Maximum filesystem blocks=1048576
1 block group
8192 blocks per group, 8192 fragments per group
128 inodes per group
Allocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
#dumpe2fs fs
dumpe2fs 1.42 (29-Nov-2011)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: e43499a5-c37a-4f03-84b9-ce044964c7d2
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: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 128
Block count: 1024
Reserved block count: 51
Free blocks: 986
Free inodes: 117
First block: 1
Block size: 1024
Fragment size: 1024
Reserved GDT blocks: 3
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 128
Inode blocks per group: 16
Filesystem created: Sat Mar 18 23:28:28 2017
Last mount time: n/a
Last write time: Sat Mar 18 23:28:28 2017
Mount count: 0
Maximum mount count: -1
Last checked: Sat Mar 18 23:28:28 2017
Check interval: 0 (<none>)
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: 0fdcbdb2-889a-4fc8-9382-886681cec655
Group 0: (Blocks 1-1023)
Primary superblock at 1, Group descriptors at 2-2
Reserved GDT blocks at 3-5
Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
Inode table at 8-23 (+7)
986 free blocks, 117 free inodes, 2 directories
Free blocks: 38-1023
Free inodes: 12-128
根据上面讲过的知识简单计算一下,块大小是1024字节, 1MB的分区共有1024个块,第0个块 是启动块,启动块之后才算ext2文件系统的开始,因此Group 0占据第1个到第1023个块, 共1023个块。块位图占一个块,共有1024×8=8192个bit,足够表示这1023个块了,因此只要一 个块组就够了。默认是每8KB分配一个inode,因此1MB的分区对应128个inode,这些数据都 和dumpe2fs的输出吻合。
现在我们用二进制查看工具查看这个文件系统的所有字节,并且同dumpe2fs工具的输出信息相 比较,就可以很好地理解文件系统的存储布局了。
#od -tx1 -Ax fs
000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000400 80 00 00 00 00 04 00 00 33 00 00 00 da 03 00 00
000410 75 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
000420 00 20 00 00 00 20 00 00 80 00 00 00 00 00 00 00
000430 1c 52 cd 58 00 00 ff ff 53 ef 01 00 01 00 00 00
000440 1c 52 cd 58 00 00 00 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 e4 34 99 a5 c3 7a 4f 03
000470 84 b9 ce 04 49 64 c7 d2 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 03 00
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 0f dc bd b2
0004f0 88 9a 4f c8 93 82 88 66 81 ce c6 55 01 00 00 00
000500 0c 00 00 00 00 00 00 00 1c 52 cd 58 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 06 00 00 00 07 00 00 00 08 00 00 00 da 03 75 00
000810 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00
000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
001800 ff ff ff ff 1f 00 00 00 00 00 00 00 00 00 00 00
001810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
001870 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80
001880 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
*
001c00 ff 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00
001c10 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
*
002000 00 00 00 00 00 00 00 00 1c 52 cd 58 1c 52 cd 58
002010 1c 52 cd 58 00 00 00 00 00 00 00 00 00 00 00 00
002020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
002080 ed 41 e8 03 00 04 00 00 1c 52 cd 58 1c 52 cd 58
002090 1c 52 cd 58 00 00 00 00 e8 03 03 00 02 00 00 00
0020a0 00 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00
0020b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
002300 80 81 00 00 00 30 04 04 1c 52 cd 58 1c 52 cd 58
002310 1c 52 cd 58 00 00 00 00 00 00 01 00 08 00 00 00
002320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
002350 00 00 00 00 00 00 00 00 00 00 00 00 25 00 00 00
002360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
002500 c0 41 00 00 00 30 00 00 1c 52 cd 58 1c 52 cd 58
002510 1c 52 cd 58 00 00 00 00 00 00 02 00 18 00 00 00
002520 00 00 00 00 00 00 00 00 19 00 00 00 1a 00 00 00
002530 1b 00 00 00 1c 00 00 00 1d 00 00 00 1e 00 00 00
002540 1f 00 00 00 20 00 00 00 21 00 00 00 22 00 00 00
002550 23 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00
002560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
006000 02 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00
006010 0c 00 02 02 2e 2e 00 00 0b 00 00 00 e8 03 0a 02
006020 6c 6f 73 74 2b 66 6f 75 6e 64 00 00 00 00 00 00
006030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
006400 0b 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00
006410 f4 03 02 02 2e 2e 00 00 00 00 00 00 00 00 00 00
006420 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
006800 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
006810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
006c00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
006c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
007000 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
007010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
007400 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
007410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
007800 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
007810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
007c00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
007c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
008000 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
008010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
008400 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
008410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
008800 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
008810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
008c00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
008c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
009000 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00
009010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
009400 00 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
009410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
100000
其中以*开头的行表示这一段数据全是零因此省略了。
从000000开始的1KB是启动块,由于这不是一个真正的磁盘分区,启动块的内容全部为零。
从000400到0007ff的1KB是超级块,对照着dumpe2fs的输出信息。
从000800开始是块组描述符表,这个文件系统较小,只有一个块组描述符。
整个文件系统是1MB,每个块是1KB,应该有1024个块,除去启动块还有1023个块,分别编号 为1-1023,它们全都属于Group 0。其中, Block 1是超级块,接下来的块组描述符指出,块位 图是Block 6,因此中间的Block 2-5是块组描述符表,其中Block 3-5保留未用。块组描述符还指 出, inode位图是Block 7, inode表是从Block 8开始的,那么inode表到哪个块结束呢?由于超 级块中指出每个块组有128个inode,每个inode的大小是128字节,因此共占16个块, inode表 的范围是Block 8-23。
从Block 24开始就是数据块了。块组描述符中指出,空闲的数据块有986个,由于文件系统是新 创建的,空闲块是连续的Block 38-1023,用掉了前面的Block 24-37。从块位图中可以看出, 前37位(前4个字节加最后一个字节的低5位)都是1,就表示Block 1-37已用。
001c00这一行的128位就表示了所有inode,因此下面的行不管是0还是1都没有意义。已用 的11个inode中,前10个inode是被ext2文件系统保留的,其中第2个inode是根目录, 第11个inode是lost+found目录,块组描述符也指出该组有两个目录,就是根目录 和lost+found。
探索文件系统还有一个很有用的工具debugfs,它提供一个命令行界面,可以对文件系统做各种操作,例如查看信息、恢复数据、修正文件系统中的错误。
#debugfs fs
debugfs 1.42 (29-Nov-2011)
debugfs: stat /
Inode: 2 Type: directory Mode: 0755 Flags: 0x0
Generation: 0 Version: 0x00000000
User: 1000 Group: 1000 Size: 1024
File ACL: 0 Directory ACL: 0
Links: 3 Blockcount: 2
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x58cd521c -- Sat Mar 18 23:28:28 2017
atime: 0x58cd521c -- Sat Mar 18 23:28:28 2017
mtime: 0x58cd521c -- Sat Mar 18 23:28:28 2017
BLOCKS:
(0):24
TOTAL: 1
转自:akaedu教材