一、Ext概述

EXT是延伸文件系统(Extended file system,缩写为 ext或 ext1),也译为扩展文件系统,第 1 个扩展文件系统(ext1)由 Remy Card 设计,并于 1992 年 4 月引入到 Linux 中。采用Unix文件系统(UFS)的元数据结构,以克服MINIX文件系统性能不佳的问题。它是在linux上,第一个利用虚拟文件系统(VFS)实现出的文件系统,在linux核心0.96c版中首次加入支持,最大可支持2GB的文件系统。

第 2 个扩展文件系统(ext2)也是由 Remy Card 实现的,并于 1993 年 1 月引入到 Linux 中。它借鉴了当时文件系统(比如 Berkeley Fast File System [FFS])的先进想法。ext2 支持的最大文件系统为 2TB,但是 2.6 内核将该文件系统支持的最大容量提升到 32TB。

第 3 个扩展文件系统(ext3)是 Linux 文件系统的重大改进,尽管它在性能方面逊色于某些竞争对手。ext3 文件系统引入了日志概念,以在系统突然停止时提高文件系统的可靠性。

目前,已经拥有第 4 个扩展文件系统(ext4)。ext4 在性能、伸缩性和可靠性方面进行了大量改进。最值得一提的是,ext4 支持 1 EB 的文件系统。ext4 是由 Theodore Tso(ext3 的维护者)领导的开发团队实现的,并引入到 2.6.19 内核中。目前,它在 2.6.28 内核中已经很稳定(到 2008 年 12 月为止)。

上面就是Ext文件系统的发展历史,我们可以看到Ext文件系统已经发展到第四代Ext4。虽然主流基本是Ext4, 但是Ext2/3目前仍然再用。所以我们此篇文章从Ext2入手,对Ext2/Ext3/Ext4加以详细阐述。

二、Ext文件系统结构

【Ext2文件系统】

不管是Windows或者Linux操作系统,其档案数据除了档案实际内容外,通常含有非常多的属性,例如Linux操作系统的档案权限(rwx)与文件属性(拥有者、群组、时间参数等)。

文件系统通常会将这两部份的数据分别存放在不同的区块,权限与属性放置到inode中,至于实际数据则放置到data block区块中。

另外,还有一个超级区块(superblock)会记录整个文件系统的整体信息,包括inode与block的总量、使用量、剩余量等,每个inode与block都有编号。

至于这三个数据的意义可以简略说明如下:
(1) superblock:记录此filesystem的整体信息,包括inode/block的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
(2) inode:记录档案的属性,一个档案占用一个inode,同时记录此档案的数据所在的block号码;
(3) block:实际记录档案的内容,若档案太大时,会占用多个block。


我们将inode与block区块用图解来说明一下,如下图所示,文件系统先格式化出inode与block的区块,假设某一个档案的属性与权限数据是放置到inode4号(下图较小方格内),而这个inode记录了档案数据的实际放置点为2,7,13,15这四个block号码,此时我们的操作系统就能够据此来排列磁盘的阅读顺序,可以一口气将四个block内容读出来!那么数据的读取就如同下图中的箭头所指定的模样了。这种数据存取的方法我们称为索引式文件系统(indexedallocation)

文件系统一开始就将inode与block规划好了,除非重新格式化,否则inode与block固定后就不再变动。但是如果仔细考虑一下,如果我的文件系统高达数百GB时,那么将所有的inode与block通通放置在一起将是很不智的决定,因为inode与block的数量太庞大,不容易管理。

为此之故,Ext2文件系统在格式化的时候基本上是区分为多个区块群组(blockgroup)的,每个区块群组都有独立的inode/block/superblock系统。就好似在公司,有很多的部门,每个部门都有自己的联络系统,但最终都得向大boss汇报!如此这样分成一群群的目的就是比较好管理咯!

Ext2格式化后基本长下面这个样子:

接下来,我们逐个介绍一个上面提到的重要概念:

(1) Data block(资料区块)

Data block是用来放置档案内容数据地方,在Ext2文件系统中所支持的block大小有1K,2K及4K三种而已。在格式化时block的大小就固定了,且每个block都有编号,以方便inode记录。由于block大小的差异,会导致该文件系统能够支持的最大磁盘容量与最大单一档案容量并不相同(如下表)。

Block大小

1KB

2KB

4KB

最大单一档案限制

16GB

256GB

2TB

最大文件系统总容量

2TB

8TB

16TB

另外,需要注意的是:每个block内最多只能够放置一个档案的数据;

a,如果档案大于block的大小,则一个档案会占用多个block数量;

b,若档案小于block,则该block的剩余容量就不能够再被使用了(磁盘空间会浪费)。

(2) Inode table(inode表)

我们前面有提到inode的作用是记录档案的属性,同时记录此档案的数据所在的block号码,Ext2详细的Inode Data Structure请见文章结尾附录A。

Ext2中inode主要包括以下一些信息:

inode的数量与大小也是在格式化时就已经固定了,有三点需要注意:

a, 每个inode大小均固定为128bytes

b, 每个档案都仅会占用一个inode而已;

c, inode记录一个block号码要花掉 4bytes

假设我一个档案有4MB且每个block为4KB时,那么,主要也至少需要1K笔block号码的记录,也就是需要inode至少有1K*4Bytes=4KB这么大,这不是开玩笑吗!每个inode只有128 bytes!!!

为此Ext2文件系统很聪明的将inode记录block号码的区域定义为:

12个直接 + 1个间接 + 个双间接 + 1个三间接记录区。

这样子inode能够指定多少个block呢?(以1KB大小的Block为例说明一下)

12个直接指向:共可记录12笔记录,也即12 blocks;

1个间接指向:1KB/4=256笔记录,也即256 blocks;

1个双间接指向:(1KB/4)*(1KB/4)=256*256=256^2笔记录,也即256^2 blocks;

1个三间接指向:(1KB/4)*(1KB/4)*(1KB/4)=256*256*256=256^3笔记录,也即256^3 blocks;

如何查找inode?

Inode记录位于inode table, 包含在block group,所以查找一个inode的步骤:

a, 首先看它属于哪个block group,

b, 再来查找该inode位于该block group的index, 其中INODES_PER_GROUP在superblock中有定义。

#下面公式计算哪一个block group包含这个inode;

  block group = (inode – 1) / INODES_PER_GROUP

#下面公式计算inode位于这个block group具体index

   index = (inode – 1) % INODES_PER_GROUP

#下面公式计算该inode位于哪个block中

  containing block = (index * INODE_SIZE) / BLOCK_SIZE

这里提一点:根目录的内容一定是放在inode2对应的block。

(3) Super block(超级区块)

Superblock是记录整个filesystem相关信息的地方,没有Superblock,就没有这个filesystem了。记录的主要信息有:

  1. block与inode的总量;

  2. 未使用与已使用的inode/block数量;

  3. block与inode的大小(block为1,2,4K,inode为128bytes);

  4. filesystem的挂载时间、最近一次写入数据的时间、最近一次检验磁盘(fsck)的时间等文件系统的相关信息;

  5. 一个validbit数值,若此文件系统已被挂载,则valid bit为0,若未被挂载,则validbit为1。

Superblock是非常重要的,因为我们这个文件系统的基本信息都写在这里,因此,如果superblock挂了,你的文件系统可能就需要花费很多时间去修复。一般来说,superblock的大小为1024bytes。

此外,每个block group都可能含有superblock, 但事实上除了第一个block group内会含有superblock之外,后续的block group不一定含有superblock,而若含有superblock则该superblock主要是做为第一个block group内superblock的备份,这样可以进行superblock的救援!

Super Block 的备份在 ext2 中,是在所有的 group block 中的 Super block 中备份,而后的版本只在0,1,3,5,7, 以及3 5 7的指数的 group block 号中的 Super Block 做拷贝,如 9,25,49,...

我们来查看一个Ext2的super block分布:

(4) Group Descriptor(组描述区)

这个区段可以描述每个block group的开始与结束的block号码,以及说明每个区段(superblock,bitmap,inodemap,data block)分别介于哪一个block号码之间

注意,从Ext2格式化后的分布来看,Group Descriptor是紧着Super block的,从上图我们可以看到Block size=4K=0x1000,

(5) Blockbitmap(区块对照表)

如果你想要新增档案时总会用到block吧!那你要使用那个block来记录呢?当然是选择【空的block】来记录新档案的数据啰。那你怎么知道那个block是空的?这就得要透过block bitmap的辅助了。

从block bitmap当中可以知道哪些block是空的,因此我们的系统就能够很快速的找到可使用的空间来处置档案啰。

同样的,如果你删除某些档案时,那么那些档案原本占用的block号码就得要释放出来,此时在blockbitmpap当中相对应到该block号码的标志就得要修改成为【未使用中】!这就是bitmap的功能。

(6) inodebitmap(inode对照表)

这个其实与blockbitmap是类似的功能,只是block bitmap记录的是使用与未使用的block号码,至于inode bitmap则是记录使用与未使用的inode号码!

【Ext3文件系统】

Ext3 是对Ext2 系统的扩展,它兼容Ext2。但是与Ext2最大的不同就是Ext3引入了日志式文件系统。

由于文件系统都有快取层参与运作,如不使用时必须将文件系统卸下,以便将快取层的资料写回磁盘中。因此每当系统要关机时,必须将其所有的文件系统全部shutdown 后才能进行关机。

如果在文件系统尚未shutdown 前就关机时,下次重开机后会造成文件系统的资料不一致,故这时必须做文件系统的重整工作,将不一致与错误的地方修复。然而,此一重整的工作是相当耗时的,特别是容量大的文件系统,而且也不能百分之百保证所有的资料都不会流失。为了克服此问题,使用所谓“日志式文件系统 (Journal File System)” 。

其中绿色的箭头表示正常的磁盘读写,

紫色的箭头表示由JBD将元数据块额外写一份到磁盘日志中,红色箭头表示恢复时,由JBD将日志中的数据写回磁盘的原始位置。

此类文件系统最大的特色是,它会将整个磁盘的写入动作完整记录在磁盘的某个区域上,以便有需要时可以回溯追踪。

此外,和Ext2 不同,Ext3 会在删除文件时把文件的节点中的块指标清除。这样做可以在unclean 载入文件系统后,重放日志时,可以减少对文件系统的访问。但也同样了文件在反删除上面的困难。用户唯一的补救是在硬盘中捞取数据,并且要知道文件的起始到结束的块指标。尽管提供了比Ext2 在删除文件上稍微高一些的安全性,却也无可避免的带来了不便之处。

【Ext4文件系统】

Ext4 是一种针对Ext3 系统的扩展日志式文件系统。EXxt 是 Ext3 的改进版,修改了 Ext3 中部分重要的数据结构,而不仅仅像 Ext3 对 Ext2 那样,只是增加了一个日志功能而已。Ext4 可以提供更佳的性能和可靠性,还有更为丰富的功能。

Ext4 一些重要的特点如下:

(1) Ext4与Ext2、Ext3等传统Unix文件系统最大的区别在于使用了extents而不是间接块(indirect block)来标记文件内容。extent相似于NTFS文件系统中的运行(Data run),本质上他们指示了组成extent的一系列文件块的起始地址、数量,每个 extent 为一组连续的数据块。

(2) 更大的文件系统和更大的文件。较之 Ext3 目前所支持的最大 16TB 文件系统和最大 2TB 文件,Ext4 分别支持 1EB(1,048,576TB, 1EB=1024PB,

1PB=1024TB)的文件系统,以及 16TB 的文件

(3) 无限数量的子目录。Ext3 目前只支持 32,000 个子目录,而 Ext4 支持无限数量的子目录

(4) Ext4中inode是256个字节,而Ext2、Ext3文件系统中inode只有128个字节。Extent结构是12个字节,前12个字节是extent头结构区(40到51字节), 所以一个inode中实际上可以包含4个extent。

(5) “无日志”模式。日志总归有一些开销,EXT4 允许关闭日志,以便某些有特殊需求的用户可以借此提升性能。

重点介绍一下extend数据结构:

所谓的extent指的是一段连续的物理磁盘块,这样,只需要一个extent数据结构,我们就能描述一段很长的物理磁盘空间,性价比很高。

其次,我们得明白,ext4文件系统中的extent是干什么的,又存储在哪里?ext4文件系统中的extent数据结构的主要作用是索引,即根据逻辑块号查询文件的extent能够准确定位逻辑块对应的物理块号。有两种情况:

  1. ext4的extent在文件较小的时候存储在inode的i_data[]中,

  2. 在文件较大的时候,所有的extent会被组织成一棵B+树,

Ext4创建文件/目录的时候,会初始化一棵extent tree,B+树上的每个节点,其主体包含两个部分:extent_header和extent_body。每个节点包含一个extent_header和多个extent_body。大致结构如下:

extent_body分为两种,索引节点(index node)叶子节点(leaf node)

  1. 索引节点用于存储到叶子节点的中间路径,

  2. 叶子节点记录文件一段连续的逻辑块所对应的物理磁盘块范围。

举个Extent的例子:

  1.  EXT4文件系统上创建一个新的文件,

    "echo Here is a new file by Gumao! >testfile.txt"

  2. ls -li testfile.txt查看inode编号为803365

    除了用上面的方式,还是可以用stat命令,如下:

  3. 用fsstat命令读取新建文件testfile.txt所在分区的磁盘信息:

    上半段是磁盘的一些基本信息,我们想要的是Block group的信息。从下面读出来的结果我们看到Group 98包含8192个inode并且inode range是802817~811008,而前面我们新建的testfile.txt对应的inode值为803365,所以,我们判断,inode 803365在Group 98.

    也可以用命令“istat /dev/sda1 803365”得到Group的信息:

    但是哪个块里包含了刚才创建文件的inode?

  4. Group 98中,第一个inode是802817,那么,前面Group 0~97包含的inode数目=802816,我们要找inode位置=803365-802816= 549,也就是Group 98中的第549个inode。

  5. 我们的系统采用的Block size是4KB,Ext4的inode大小为256B,也就是说一个Block里面有16 inodes。我们要找block位置=549/16=34余5,即Group 98中的第34个inode table block中的第5个256字节。

  6. 又因为Group 98中的第一个inode table block地址为3146784,那么,3146784+34=3146818,所以我们要找的inode在Block 3146818的第5个256字节中

  7. 用命令“blkcat /dev/sda1 3146818” dump Block 3146818的内容,并用16进制工具打开,(第5个256字节从0x400开始),内容如下,

  8. 那么结果读出来,我们解析一下上面读出来的内容,inode的结构如下:

    位置

    描述

    0x0

    __le16

    文件模式

    0x2

    __le16

    所有者UID.

    0x4

    __le32

    文件大小.

    0x8

    __le32

    读取时间.

    0xC

    __le32

    Inode修改时间

    0x10

    __le32

    文件修改时间.

    0x14

    __le32

    删除时间

    0x18

    __le16

    GID.

    0x1A

    __le16

    硬链接计数.

    0x1C

    __le32

    块计数(512字节)

    0x20

    __le32

    文件标识(ext4使用extent需要标记0x80000)

    ...

    0x28

    __le32

    块映射(ext2/3)或区段树(ext4)

    ...

  9. 从上面的结构中,我们可以看到0x4~0x7四个字节代表的文件大小=0x1D=29. 这个跟我们要的文件对应的大小吻合,也可以侧面判断,我们应该找对了文件,同时,我们看到byte0x28开始就是Ext4的extents区域了,我们就继续解析,

  10. 我们结合Extends结构(具体的结构解析见文章最后附录B)解析上面读出来的结果,先看一下extent_header(12 bytes)的内容,

    偏移

    大小

    描述

    0x0

    0xf30a

    幻数magic number, 0xF30A.

    0x2

    0x0001

    区段数.

    0x4

    0x0004

    最大的区段数.

    0x6

    0x0000

    段节点在段树中的深度。0则表示为叶子节点,指向数据块;否则指向其它段节点。

    0x8

    0x0000 0000

    暂不讨论

    从上表解析的结果来看,因为我们的文件较小,这里作为叶子节点直接指向了文件数据块。

    接着,我们再解析一个extend_index 叶子节点的内容,

    偏移

    大小

    描述

    0x0

    0x0000 0000

    此区段的第一个块号,起始块号

    0x4

    0x0001

    区段内包含的块数.

    0x6

    0x0000

    此区段所指向的块号(高16位)

    0x8

    0x0030 97B6

    此区段所指向的块号(低32位

    从上面解析的内容来看,我们要找的文件对应的Block编号为0x3097B6=3184566。

  11. 我们使用命令“blkcat /dev/sda1 3184566” dump Block 3184566的内容,并用16进制工具打开,内容如下,

    从上面的结果来看,这正是我们之前新建文件testfile.txt的内容!

至此,通过这个案例,相信聪明的你一定理解了extents的含义。感兴趣的话就自己实验一下吧~


附录A:Ext2详细的Inode Data Structure如下表:

Start

Byte

End

Byte

SizeField Description
012Type and Permissions
232User ID
474Lower 32 bits of size in bytes
8114Last Access Time (in POSIX time)
12154Creation Time (in POSIX time)
16194Last Modification time (in POSIX time)
20234Deletion time (in POSIX time)
24252Group ID
26272Count of hard links (directory entries) to this inode. When this reaches 0, the data blocks are marked as unallocated.
28314Count of disk sectors (not Ext2 blocks) in use by this inode, not counting the actual inode structure nor directory entries linking to the inode.
32354Flags
36394Operating System Specific value #1
40434Direct Block Pointer 0
44474Direct Block Pointer 1
48514Direct Block Pointer 2
52554Direct Block Pointer 3
56594Direct Block Pointer 4
60634Direct Block Pointer 5
64674Direct Block Pointer 6
68714Direct Block Pointer 7
72754Direct Block Pointer 8
76794Direct Block Pointer 9
80834Direct Block Pointer 10
84874Direct Block Pointer 11
88914Singly Indirect Block Pointer (Points to a block that is a list of block pointers to data)
92954Doubly Indirect Block Pointer (Points to a block that is a list of block pointers to Singly Indirect Blocks)
96994Triply Indirect Block Pointer (Points to a block that is a list of block pointers to Doubly Indirect Blocks)
1001034Generation number (Primarily used for NFS)
1041074In Ext2 version 0, this field is reserved. In version >= 1, Extended attribute block (File ACL).
1081114In Ext2 version 0, this field is reserved. In version >= 1, Upper 32 bits of file size (if feature bit set) if it's a file, Directory ACL if it's a directory
1121154Block address of fragment
11612712

附录B:extend结构解析

The extent tree header is recorded in struct ext4_extent_header, which is 12 bytes long:

OffsetSizeNameDescription
0x0__le16eh_magicMagic number, 0xF30A.
0x2__le16eh_entriesNumber of valid entries following the header.
0x4__le16eh_maxMaximum number of entries that could follow the header.
0x6__le16eh_depthDepth of this extent node in the extent tree. 0 = this extent node points to data blocks; otherwise, this extent node points to other extent nodes. The extent tree can be at most 5 levels deep: a logical block number can be at most 2^32, and the smallest n that satisfies 4*(((blocksize - 12)/12)^n) >= 2^32 is 5.
0x8__le32eh_generationGeneration of the tree. (Used by Lustre, but not standard ext4).

Internal nodes of the extent tree, also known as index nodes, are recorded as struct ext4_extent_idx, and are 12 bytes long:

OffsetSizeNameDescription
0x0__le32ei_blockThis index node covers file blocks from 'block' onward.
0x4__le32ei_leaf_loLower 32-bits of the block number of the extent node that is the next level lower in the tree. The tree node pointed to can be either another internal node or a leaf node, described below.
0x8__le16ei_leaf_hiUpper 16-bits of the previous field.
0xA__u16ei_unused

Leaf nodes of the extent tree are recorded as struct ext4_extent, and are also 12 bytes long:

OffsetSizeNameDescription
0x0__le32ee_blockFirst file block number that this extent covers.
0x4__le16ee_lenNumber of blocks covered by extent. If the value of this field is <= 32768, the extent is initialized. If the value of the field is > 32768, the extent is uninitialized and the actual extent length is ee_len - 32768. Therefore, the maximum length of a initialized extent is 32768 blocks, and the maximum length of an uninitialized extent is 32767.
0x6__le16ee_start_hiUpper 16-bits of the block number to which this extent points.
0x8__le32ee_start_loLower 32-bits of the block number to which this extent points.

参考文献:

  1. 鸟哥Linux私房菜

  2. 维基百科

Logo

更多推荐