引言
和标题一样,ext文件系统是一个老牌的Linux的文件系统,是Linux的第一个文件系统。虽然业界将Ext文件系统看成是一个过度用的文件系统,但是现在的Ext4依然有很强的生命力。从ext2-ext3-ext4,文件系统的磁盘布局没有发生太大的变化,从ext2到ext3,主要是增强了可用性、数据完整性、速度、易于迁移等功能;ext3到ext4就不仅仅是功能增强,它修改了某一些重要的数据结构,并且在对大文件的支持上做了更多的优化改进
Ext文件系统磁盘布局
Ext2文件系统磁盘布局
ext文件系统从ext2-ext4磁盘布局并没有太大的改动,如下图
Block Group
ext文件系统将文件系统分块组管理,在写文件时,尽可能保证文件集中在块组内,减少读取时磁盘旋转的开销。块组的结构如上图,这里要注意,块组0的结构和其他组有点区别,块组0开始位置多了一个固定1024byte用于安装x86引导扇区以及其他信息。
-
super block
-
定义:块组0的super block是从1024byte偏移量开始的,如果每个group都备份super block,可能造成一些空间浪费,因此提供了sparse_super的设置,如果设置了该sparse_super,那么只有group 0,3、5、7的幂组才会备份super block
-
可能理论优点抽象,一个简单ext4的例子,是我Linux系统引导分区的例子,首先我们看到并不是所有的组上都有super block的备份,并且备份分别在 0,1,3,5,7,9,25,49,正如上边描述的,开启了sparse_super后,只有0号和3,5,7的幂的组才会备份
下边是7和9组,其他的不列举,如果有兴趣可以用dumpe2fs工具看一下自己的电脑,尽量挑小的分区看,分区太大,数据太多
-
-
GDT(块组描述符表)
-
定义:记录块组的信息,super block和GDT从组1-组n都是组0的备份,因为super block和GDT是非常重要的信息,为了安全,需要备份
-
GDT的局限,假设一个组描述符占用64byte,块大小为4KB,那么一个组全部存放GDT能存放
C G D T = 2 27 ÷ 2 6 = 2 21 C_{GDT} = 2^{27} \div 2^{6} = 2^{21} CGDT=227÷26=221
因此如果块为4KB,那么一个组最多存放GDT为2^{21}个,那么可以表示的最大文件系统大小为组容量乘组数
C D i s k = 2 27 × 2 21 = 2 48 = 256 P B C_{Disk} = 2^{27} × 2^{21} = 2^{48} = 256PB CDisk=227×221=248=256PB
通过上面的简单计算,即使第一个组全部用于存放GDT,文件系统大小无法突破256PB的极限。并且,文件系统为了安全,所有组的GDT会随着super block备份,也会消耗很大的文件系统空间,即使设置了sparse_super设置也会
-
-
block bitmap:记录块组内块的使用情况,同时也限制了块组的块数量,所以块组的最大大小为
B G _ B _ S I Z E = B _ S I Z E ∗ 8 BG\_B\_SIZE = B\_SIZE * 8 BG_B_SIZE=B_SIZE∗8
所以一个4KB块的文件系统,一个组最多可以包含 2 12 ∗ 2 3 = 2 15 = 32768 2^{12}*2^{3} = 2^{15} = 32768 212∗23=215=32768个块,因此块的大小
B G _ S I Z E = B _ S I Z E ∗ B G _ B _ S I Z E BG\_SIZE = B\_SIZE * BG\_B\_SIZE BG_SIZE=B_SIZE∗BG_B_SIZE
依然假设文件系统为4KB的块,那么一个块组的大小为 2 15 ∗ 2 12 = 2 27 2^{15} * 2^{12} = 2^{27} 215∗212=227byte,为128MB
-
inode bitmap:记录哪些项在inode table中使用
-
特殊inode
inode 用途 0 不存在0号inode 1 损坏数据块链表 2 根目录 3 ACL索引 4 ACL数据 5 引导装载程序 6 未删除的目录 7 预留的块组描述符inode 8 日志inode 9 排除在外的inode,用于快照 10 备份inode,用于一些非上游特性 11 第一个非预留的inode,通常是lost + found目录
改进
Meta Block Group(元块组)
-
定义:改进GDT,上边描述了ext2中GDT的局限,在ext3中引入了Meta Block Group,其固定用一个块来存放GDT,用于记录连续的组组成的一个元块组,GDT不在跟随super block一起备份,而是在自己元块组内备份,分别存放在元块组的1,2号组和最后一个组上。元块组的大小为一个块能存放的组描述数数目
C M B G = B _ S I Z E ÷ G D T _ S I Z E C_{MBG} = B\_SIZE \div GDT\_SIZE CMBG=B_SIZE÷GDT_SIZE
假设块组为4KB,组描述符为64byte,那么元块组的大小为 2 12 ÷ 2 6 = 2 6 2^{12} \div 2^6 = 2^6 212÷26=26,为64个
-
画了一个简单的示意图,凑合着看
没有使用meta block group的文件系统组分布示意图
使用meta block group的文件系统组分布示意图,
Flexible Block Groups
-
定义:我们知道,机械硬盘每次读取块前要先寻道,相对读取数据,寻道开销是非常高的,而block bitmap、i-node table、i-node table这些数据需要频繁加载,它们分散在各个组上存放就会导致频繁的寻道。为了减少加载block bitmap、i-node table、i-node table时的寻道开销和大文件的连续块分配,ext4引入了Flexible Block Groups,它将一定数量的物理块组连起来组成一个逻辑块组,block bitmap、i-node table、i-node table按顺序存储在逻辑块组的第一个块组上
-
简单的示意图,注意该图只是一个简单示意图,一个方块并不代表一个块,block bitmip、i-node bitmap依然是一个组一个块,i-node table依顺序占用多个块
-
一个简单的例子,还是以我操作系统为例,先看一下,flexible block group size
sudo dumpe2fs /dev/sda10 |grep 'Flex'
看到组大小16
然后来看一下组的block bitmap 、i-node bitmap、i-node table分布,可以看到从0-7block bitmap 、i-node bitmap、i-node table都是依次连续的,当然一直到15都是连续的,从第16组开始会变化
再来看一下第16组,是从524288块开始的,它们属于另一个Flexible Block Groups,和上图中flex block group size大小吻合
Extent 树
-
定义:ext2/3时,i-node中指向文件数据块采用直接/间接块地址,如下图,0-11是直接地址,之后的块分为3种,第一种是一级间接地址,第二种是二级,第三种三级,随着间接级数变大,指向的数据块越多,但是这种表示方法有一个缺点,那就是如果数据块连续,依然会每个数据块都存储一个地址,很浪费空间
ext4采用Extent树优化了这种情况,它的核心原理是连续块只存储起始块和连续的块数,每个节点都是以ext4_extent_header(12byte)结构开始,然后如果是内节点,则后边是ext4_extent_idx的项其大小为12byte;如果是叶子节点,则后边是ext4_extent,其大小也是12byte。
-
ext4_extent_header结构
-
ext4_extent_idx结构
-
ext4_extent结构,ee_block该连续块段的第一个块号,ee_len连续块数量,ee_start_hi是该范围段起始指针的高16位,ee_start_lo是该范围段的起始指针低32位
-
目录项
-
线性(传统)目录,示意图如下,ext4默认是ext4_dir_entry_2,ext4_dir_entry_2和ext4_dir_entry的区别主要是因为文件名不超过255,所以不需要16位存储文件名,因此抽出8位用来存储文件类型,inode 2是系统保留的inode号代表根目录,该图是简单的根目录下的文件目录项示意图,可以看到在块内查找一个文件几乎要遍历目录项数组对比文件名
-
Hash树目录,由于传统目录查找文件几乎是遍历查找,出于性能的考虑,ext3引入了文件名hash值的B树来提升查找文件的速度。其结构如下图,树的root通常保存在文件的第一个数据块中,这里有一个地方要注意dx_entry结构中的block号是文件内部的块号,代表是文件的第几个数据块,并非文件系统的块号。
如果dx_root.info.indirect_levels为非零,则htree有两层,根节点并非指向存放ext4_dir_entry_2的叶子节点,而是指向内部节点,其示意图如下,此时根节点中的hash值为其指向内部节点中hash值的最小值
jbd2日志
-
jdb2流程简单示意图,写文件时,先写到磁盘缓冲区,然后写到journal(是一个文件系统保留区域,inode为8,大小默认128MB),因为是顺序写,速度很快。当写入完成时,写入一个提交记录,然后刷新磁盘缓冲区。之后文件系统会将journal的提交记录在擦除前写入最终的位置,这里的写就可能会比较慢,因为数据可能需要写到不同块,需要多个寻道开销
-
jdb2日志模式
- ordered(默认级别):只将文件系统元数据写入journal,当系统崩溃时,不能保证任何一致性状态
- journal:所有数据和元数据都会写入到journal,该模式比较慢,但是很安全
- writeback:在元数据通过journal写入到磁盘最终位置前,脏数据不能刷新到磁盘
-
崩溃恢复:当系统崩溃重启时,文件系统利用journal的最近的提交记录重放写过程来恢复数据,journal详细的数据结构参考jdb2