0%

文件系统概念和Ext2文件系统

文件系统概念

我在本科第一次学习“文件系统”时,觉得这一章的很多概念仿佛如空中楼阁,因为“文件系统”太抽象了,后来才慢慢加深理解,其实文件系统就是磁盘管理,也可以说文件系统是物理设备的高级抽象。硬盘(低速设备)读写单位是扇区,通常为512 Bytes,而文件系统的读写单位是块(若干个扇区组成,为了避免频繁访问磁盘,一次读入多个块)。为了管理这些块,需要各种数据结构。

inode和文件控制块FCB

一个文件被拆分成多个块来存储,因此需要一种文件组织方式。以Windows的FAT32为例,FAT是文件分配表(File Allocation Table),所有的块被用链式结构来组织,在每一块的最后记录下一块的地址,这样文件不需要连续存储,节省了磁盘空间。FAT文件系统为每个文件都分配了一个FAT表,用单独的链式结构来存储、跟踪文件的所有块。FAT文件系统的缺点是文件查找效率地下,为了定位到最后一个数据块,需要遍历前面的所有数据块,且每访问一个块就涉及一次磁盘寻道,使得本就低速的磁盘访问更加频繁,也许微软受不了FAT32文件系统,后来推出了NTFS文件系统(下图来自《操作系统概念》)。

Unix的文件系统比较先进,采用索引结构来组织文件块,文件块依然可以零散地分布在磁盘中,但Unix文件系统为每个文件的所有块都建立了一个索引表,索引表就是块地址数组,数组元素就是块的地址,访问任意一个块只需要从索引表获得块地址就可以了,速度大大提升。包含此索引表的索引结构就是index node,简称inode(i节点)。任何一个文件都有一个inode,inode记录了文件的所有信息,包括一个索引表,记录文件所有块的块地址。索引结构的缺点是索引结构本身要占一定的存储空间,文件越大,块越多,索引表越大,因此Unix采取了一个折中的方法,采用多级索引结构:inode的索引表一共15个索引项,如果文件块小于12块,那么将这12块的块地址直接存储在前12个索引项中,这前面12个索引可以直接获得块地址,如果文件大于12块,就用第13个索引(一级索引指针)指向一个新的块,新的一块存储256个块地址(一个块1024字节,一个块地址4字节,所以可以存储256个块地址),因此一级索引结构可以存储12+256=268个块。如果文件大于268个块,就建立二级索引指针,因此inode索引表的第14个索引是二级索引指针,,第15个是三级索引指针,这样可以管理非常大的文件(文件最多$12+256+256\times{256}+256\times{256}\times{256}块\approx{16.06G}$,如果一个块1024个字节)。

只要用于控制和管理文件相关信息的数据结构都叫文件控制块FCB(File Control Block),因此inode也是FCB的一种。并且inode的数量等于文件数量,为了方便管理,分区中所有文件的inode都用一个大表格(inode_table)来维护,用单独的几个磁盘块来存储这个表格,再结合inode位图(单独的一块)。表格就是一个数组,数组中的元素就是inode地址,数组的下标就是inode的编号。可以根据inode编号来查询inode地址。Linux中的inode的定义如下(少了3个字段,但是不重要,没贴出来,来自《深入理解Linx内核》):

目录项与目录

在Unix的inode中,缺少了一个对用户来说至关重要的属性:文件名。用户给出文件名来访问文件,而文件系统通过inode来定位数据块(文件名对文件系统来说不重要,有了inode即可)。因此目录的作用就是将文件名和inode绑定。用户给出文件名,文件在目录项中找到文件名对应的inode,然后再找到数据块。
在Linux中,目录和文件都用inode来表示,因此目录也是一种文件。inode同时指向目录和普通文件,磁盘文件系统中没有一种叫做目录的数据结构,磁盘上有的只是inode,inode指向文件实体的数据块,而不关心数据块具体的内容,因此目录和普通文件本质的区别就是“数据块上记录的内容”不同。例如,一个.txt文本文件的数据块记录的是文本文件的ASCII码,而一个目录文件的数据块,记录的内容是目录下包含的所有目录项,如下所示:

其中’.’表示当前目录,’..’表示上一级目录,每一个目录项的字段如下所示:

目录项长度通常为4的倍数,不足4的倍数会在文件名末尾补上’\0’。可以看到,目录项包含文件对应的inode索引,因此获得这个索引之后,就可以根据该索引值计算出inode地址,然后就可以得到文件的inode了。这一个inode指向的文件也可能是目录文件,因此数据块中包含目录项,这样就可以根据多级目录来查询了。另外,不管是普通文件还是目录文件(这里将目录称之为“目录文件”),总会位于一个目录下,所有普通文件和目录都位于根目录’/‘下,根目录是所有目录的父目录。
要想找到数据块,必须找到文件的inode,而要找到文件的inode,又必须要知道目录项(目录项包含inode索引),而要找到目录项又必须先找到数据块(目录项本身又是目录文件数据块里面的内容)……寻找过程似乎是个死循环。这种看似死循环的过程,原因无外乎就是上层目录是无休止的,因此有个固定的目录就可以了,答案就是根目录’/‘!每个分区都有自己的根目录,根目录是所有目录的父目录,在创建文件分区的时候就已经在一个固定死的位置写好了根目录的inode,查找文件时时都首先根据根目录inode获得根目录数据块,然后再查找这个数据块里面的目录项,最后递归查找,找到任意子目录的文件。因此查找/home/hello.txt文件的过程是:

  • 1、文件系统找到根目录’/‘的inode
  • 2、由根目录的inode找到根目录的数据块
  • 3、根目录数据块中有home目录项,并由home目录项找到home这个目录文件的inode
  • 5、由home目录文件的inode找到home目录的数据块
  • 6、home目录数据块有hello.txt目录项,并由该目录项找到hollo.txt这个文件的inode
  • 7、由hello.txt文件的inode找到对应的数据块,数据块存储着字符串的ASCII码值。

文件系统布局

Ext2文件系统在磁盘上的布局如下图所示:

Ext2文件系统将若干个块分成一个组,一个分区可能有多个块组。

  • 在整个磁盘中,最开始一个块是引导块,包含引导操作系统的相关信息,MBR(Master Boot Record和Loader程序就在这个块中)。如果该磁盘不包含操作系统,则该块内容为空。NTFS文件系统称该之为分区引导扇区。

  • 每个块组的第一块是超级块,是在为分区创建文件系统时创建的,超级块包含总的inode数量、块的数量、每块的大小(字节)、每组的块数等等这些重要信息,详细字段如下所示:

  • 组描述符紧跟着超级块,记录着数据块位图从几号块开始、inode位图从几号块开始、inode节点从几号块开始等等信息,具体字段如下:

  • 之后是数据块位图和inode位图,记录那些块被(数据块或者inode)使用过了,1比特表示对应的块被使用过了。

  • 索引节点块存储所有inode。

  • 后面的所有块均为存储数据的数据块(存储目录文件、普通文件)

Ext2文件系统实例

现有一个磁盘had.raw,根目录的内容如下,一共4个文件(包括目录):

1
2
3
4
.
..
lost+found
hello.txt

利用hexdump命令查看磁盘内容:

1
hexdump -C rootfs/hda.raw | less

磁盘内容如下,可以根据各个块的字段来查看意义:

超级块

1
2
3
4
5
6
7
8
9
10
11
12
13
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
***
//以上为引导块(0号块),接下来为超级块(1号块)
00000400 00 01 00 00 00 08 00 00 66 00 00 00 cd 07 00 00 |........f.......|
^_________^ ^_________^
s_inodes_count s_blocks_count
00000410 f4 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|
^_________^
s_log_block_size
00000420 00 20 00 00 00 20 00 00 00 01 00 00 43 54 9e 5e |. ... ......CT.^|
^_________^ ^_________^
s_blocks_per_group s_inodes_per_group
00000430 c8 56 9e 5e 00 00 15 00 53 ef 01 00 01 00 00 00 |.V.^....S.......|
字段 意义
s_inodes_count 0x0100 总共包含0x0100个inode
s_blocks_count 0x0800 总共包含0x0800块
s_log_block_size 0x0000 块大小为$1024\times{2^0}$,即1024B
s_blocks_per_group 0x2000 每组包含0x2000块
s_inodes_per_group 0x0100 每组包含0x0100(256)个inode,占$\frac{256}{8}=32$个块

其中每个inode占128字节,因此一个块包含$\frac{1024}{128}=8$个inode。

组描述符

组描述符紧跟着超级块

1
2
3
4
5
6
7
//以下为组描述符(2号块)
00000800 03 00 00 00 04 00 00 00 05 00 00 00 cd 07 f4 00 |................|
^_________^ ^_________^ ^_________^
bg_clock_bitmap bg_inode_bitmap bg_inode_table
00000810 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***
字段 意义
bg_clock_bitmap 0x03 数据块位图区从3号块开始
bg_inode_bitmap 0x04 inode位图区从4号块开始
bg_inode_table 0x05 inode区从5号块开始

数据区位图和inode位图(bitmap)

1
2
3
4
5
6
7
8
9
//以下为数据块位图区(3号块)
00000c00 ff ff ff ff ff ff 01 00 00 00 00 00 00 00 00 00 |................|
00000c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***
//以下为i节点位图区(4号块)
00001000 ff 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001020 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
***

inode区

inode区从5号块开始,因此起始地址应该是0x400$\times{5}=$0x1400,查看此处数据。另外根目录的inode是定死的,位于inode区的第2个,地址就是0x1400+128$\times$2=0x1480(inode区的第一个inode没有意义):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//以下为inode区(5号块)
00001400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***
//以下为2号i节点(i节点从1开始编号),即根目录的i节点
00001480 ed 41 00 00 00 04 00 00 49 4d 9e 5e 46 4a 9e 5e |.A......IM.^FJ.^|
^_________^
i_size(bytes)
00001490 46 4a 9e 5e 00 00 00 00 00 00 03 00 02 00 00 00 |FJ.^............|
^_________^
i_blocks
000014a0 00 00 00 00 01 00 00 00 25 00 00 00 00 00 00 00 |........%.......|
^_________^
i_block
000014b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***
字段 意义
i_size 0x0400 文件大小0x400B=1KB
i_blocks 0x0002 文件数据块数为2
i_block 0x0025 第一个数据块的块号为37,偏移是0x400$\times$37=0x9400

根目录数据块

在地址0x9400处查看根目录数据块中的内容,其中记录着目录项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//以下为根目录的第一个数据块
00009400 02 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................|
^_________^ ^___^ ^^ ^^ ^_________^ ^_________^
inode rec_len name inode
^name_len
^file_type ^第2个目录项
00009410 0c 00 02 02 2e 2e 00 00 0b 00 00 00 14 00 0a 02 |................|
^___^ ^^ ^^ ^_________^ ^_________^ ^___^ ^^ ^^
^第3个目录项
00009420 6c 6f 73 74 2b 66 6f 75 6e 64 00 00 0c 00 00 00 |lost+found......|
^__________________________________^ ^_________^
^文件名lost+found,末尾用\0补充 ^第4个目录项
00009430 d4 03 09 01 68 65 6c 6c 6f 2e 74 78 74 00 00 00 |....hello.txt...|
^___^ ^^ ^^ ^__________________________________^
^名字hello.txt
00009440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
inode号 目录项长度 文件名长度 文件类型 文件名
0x02 0x0c: 12 Bytes 0x1 目录 .
0x02 0x0c: 12 Bytes 0x1 目录 ..
0x0b 0x14: 20 Bytes 0x0a: 10 目录 lost+found
0x0c 0x3d4 Bytes 0x09 普通文件 hello.txt

这是根目录下的目录项,注意到第1个和第2个目录项的inode号都是0x02号inode,指向根目录自己(打开当前目录和打开上一级目录都是指向自己)。此外hello.txt的inode号为0x0c,因此hello.txt的inode的地址为0x1400+0x80$\times$(0x0c-1)=0x1980,在此处查看hello.txt的inode信息。

hello.txt的inode

1
2
3
4
5
6
7
8
//以下为hello.txt的i节点(12号)
00001980 a4 81 00 00 0e 00 00 00 56 4a 9e 5e 46 4a 9e 5e |........VJ.^FJ.^|
00001990 46 4a 9e 5e 00 00 00 00 00 00 01 00 02 00 00 00 |FJ.^............|
000019a0 00 00 00 00 01 00 00 00 01 02 00 00 00 00 00 00 |................|
^_________^
i_block:第一个数据块号
000019b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***

hello.txt的inode信息说明第一个数据块的块号为0x0201,对应偏移是0x400$\times$0x201=0x80400,查看该地址处的数据块的内容。

hello.txt的数据块

1
2
3
4
//以下为hello.txt的第一个数据块
00080400 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 0a 00 00 |Hello, world!...|
00080410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***

数据块中存储着hello.txt的ASCII码。

lost+found的inode

根目录中lost+found目录的inode编号为0x0b,因此lost+found目录文件的inode地址为0x1400+0x80$\times$(0x0b-1)=0x1900,在此处查看lost+found的inode信息。

1
2
3
4
5
6
7
8
9
10
11
12
//以下为lost+found的inode
00001900 ed 41 00 00 00 30 00 00 d7 49 9e 5e d7 49 9e 5e |.A...0...I.^.I.^|
00001910 d7 49 9e 5e 00 00 00 00 00 00 02 00 18 00 00 00 |.I.^............|
00001920 00 00 00 00 00 00 00 00 26 00 00 00 27 00 00 00 |........&...'...|
^_________^ ^_________^
i_block i_block
第一个数据块标号 第二个数据块标号
00001930 28 00 00 00 29 00 00 00 2a 00 00 00 2b 00 00 00 |(...)...*...+...|
00001940 2c 00 00 00 2d 00 00 00 2e 00 00 00 2f 00 00 00 |,...-......./...|
00001950 30 00 00 00 31 00 00 00 00 00 00 00 00 00 00 00 |0...1...........|
00001960 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***

lost+found第一个数据块的块号为0x0026,对应偏移是0x400$\times$0x26=0x9800,查看该地址处的数据块的内容。

lost+found数据块内容

1
2
3
4
5
6
7
8
9
10
11
12
13
//以下为lost+found的目录项数据块
00009800 0b 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................|
^_________^ ^___^ ^^ ^^ ^_________^ ^_________^
inode rec_len name inode
^name_len
^file_type ^第2个目录项
00009810 f4 03 02 02 2e 2e 00 00 00 00 00 00 00 00 00 00 |................|
^___^ ^^ ^^ ^_________^
00009820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***
00009c00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 |................|
00009c10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
***
inode号 目录项长度 文件名长度 文件类型 文件名
0x0b 0x0c: 12 Bytes 0x1 目录 .
0x02 0x03f4Bytes 0x2 目录 ..
当前目录inode指向自己,而上一级目录指向根目录。