引言
和标题一样,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能存放
因此如果块为4KB,那么一个组最多存放GDT为2^{21}个,那么可以表示的最大文件系统大小为组容量乘组数
通过上面的简单计算,即使第一个组全部用于存放GDT,文件系统大小无法突破256PB的极限。并且,文件系统为了安全,所有组的GDT会随着super block备份,也会消耗很大的文件系统空间,即使设置了sparse_super设置也会
block bitmap:记录块组内块的使用情况,同时也限制了块组的块数量,所以块组的最大大小为
所以一个4KB块的文件系统,一个组最多可以包含$2^{12}*2^{3} = 2^{15} = 32768$个块,因此块的大小
依然假设文件系统为4KB的块,那么一个块组的大小为$2^{15} * 2^{12} = 2^{27}$byte,为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号组和最后一个组上。元块组的大小为一个块能存放的组描述数数目
假设块组为4KB,组描述符为64byte,那么元块组的大小为$2^{12} \div 2^6 = 2^6$,为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
1
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