磁盘布局
为了更好的理解在线调整大小工作机制,我们首先需要理解 ext3 和 ext4 文件系统的磁盘布局,对于该功能的实现来说,这两个文件系统在磁盘上的结构是一致的,同时为了简化和突出重点,对于与在线调整大小功能不相关的内容我们将不会介绍。
Ext3 文件系统将其所管理的磁盘或者分区(引导块除外)中的块划分到不同的块组中。每个块组大小相同,当然最后一个块组所管理的块可能会少一些,其大小在文件系统创建时决定,主要取决于文件系统的块大小,对于大小为4k的文件系统块来说,块组大小为 168M。每个块组都包含一些重要的元数据信息,见图1:
图1 Ext3和Ext4文件系统磁盘布局每个块组包含一个块位图块,一个 inode 位图块,一个或多个块用于描述 inode 表和用于存储文件数据的数据块,除此之外,还有可能包含超级块和所有块组描述符表(取决于块组号和文件系统创建时使用的参数)。下面将对这些元数据作一些简要介绍。
块位图用于描述该块组所管理的块的分配状态。如果某个块对应的位未置位,那么代表该块未分配,可以用于存储数据;否则,代表该块已经用于存储数据或者该块不能够使用(譬如该块物理上不存在)。由于块位图仅占一个块,因此这也就决定了块组的大小。
Inode位图用于描述该块组所管理的inode的分配状态。我们知道inode是用于描述文件的元数据,每个inode对应文件系统中唯一的一个号,如果inode位图中相应位置位,那么代表该inode已经分配出去;否则可以使用。由于其仅占用一个块,因此这也限制了一个块组中所能够使用的最大inode数量。
Inode表用于存储inode信息。它占用一个或多个块(为了有效的利用空间,多个inode存储在一个块中),其大小取决于文件系统创建时的参数,由于inode位图的限制,决定了其最大所占用的空间。
超级块用于存储文件系统全局的配置参数(譬如:块大小,总的块数和inode数)和动态信息(譬如:当前空闲块数和inode数),其处于文件系统开始位置的1k处,所占大小为1k。为了系统的健壮性,最初每个块组都有超级块和组描述符表(以下将用GDT)的一个拷贝,但是当文件系统很大时,这样浪费了很多块(尤其是GDT占用的块多),后来采用了一种稀疏的方式来存储这些拷贝,只有块组号是3, 5 ,7的幂的块组(譬如说1,3,5,7,9,25,49…)才备份这个拷贝。通常情况下,只有主拷贝(第0块块组)的超级块信息被文件系统使用,其它拷贝只有在主拷贝被破坏的情况下才使用。
GDT用于存储块组描述符,其占用一个或者多个数据块,具体取决于文件系统的大小。它主要包含块位图,inode位图和inode表位置,当前空闲块数,inode数以及使用的目录数(用于平衡各个块组目录数),具体定义可以参见ext3_fs.h文件中struct ext3_group_desc。每个块组都对应这样一个描述符,目前该结构占用32个字节,因此对于块大小为4k的文件系统来说,每个块可以存储128个块组描述符。由于GDT对于定位文件系统的元数据非常重要,因此和超级块一样,也对其进行了备份。GDT在每个块组(如果有备份)中内容都是一样的,其所占块数也是相同的。从上面的介绍可以看出块组中的元数据譬如块位图,inode位图,inode表其位置不是固定的,当然默认情况下,文件系统在创建时其位置在每个块组中都是一样的,如图2所示(假设按照稀疏方式存储,且n不是3,5,7的幂):
图2 以稀疏方式存储超级块和块组描述符表从图我们可以看出,每个块组大小相同,除了最后一个块组可能包含的块少一些(用虚线和阴影表达)。
online resizing工作机制
由上,文件系统所管理的块最终都是分派到块组中进行管理的。如果我们希望扩大文件系统的大小或者管理更多的物理块,那么需要将这些待添加的物理块分配到适当的块组中进行管理。很容易就能够想到,按照如下三个步骤一步步添加直到达到希望的文件系统大小为止:
- 将物理块添加到最后一个块组中直到其满位置,如图2所示,我们可以将第n块块组中虚线部分填充满,然后增加文件系统可用的块数。
- 如果GDT最后一个块还可以添加新的块组描述符,那么我们就添加一个新的块组。
- 如果当前GDT中已经不能够添加新的块组了,那么我们需要新的块能够容纳块组描述符,对于该步骤,当前内核预留了一些块,用于存储块组描述符;当然这些预留块是有限的(始终小于一个块组的大小),如果文件系统很大,这些预留块最终会被耗尽,本文将采用一种新的方式meta block group来解决该问题。下面我们将先看看当前内核是如何处理的。见图3
图3 当前Ext3和Ext4文件系统通过预留块来解决online resizing的问题
当文件系统在创建时考虑到将来在线调整文件系统大小的需要,预留了一些块,并且使用一个inode(其对应的inode号为7)来管理这些预留块,以免这些块被分配给其它文件使用了。因此对于步骤3,当我们需要新块来存储块组描述符时,我们将从预留的组描述符块中分配一个块给GDT,由于GDT和预留的组描述符块在物理上是相邻的,因此很容易和原先的GDT块合并起来。
当我们在创建新的块组时,需要考虑该块组是否需要存储超级块和GDT的备份,如果需要备份,那么拷贝备份,同时将预留块添加到预留的组描述符inode中,然后增加超级块的块组数,空闲块数以及空闲inode数,为了保证文件系统的一致性,需要这些操作要么都完成,要么都不完成,它由JBD的事务机制来保证。
meta block group工作机制
由于预留的组描述符有限,当我们需要文件系统增加到很大时,预留块可能已经耗尽,此时文件系统不能够增加,除非卸载文件系统,采用ext2prepare或者类似工具来预留块,但是文件系统在这段时间内将不能够使用,对于生产系统来说是不可接受的。因此我提交了使用meta block group来进行在线增长文件系统的补丁,使得文件系统的增长不再受限于预留块的大小和块组大小。
元块组的概念其实很早就出现在内核中了,但是直到linux 2.6.21内核Ext3和Ext4文件系统online resizing功能并未支持这种新的布局,在Ext4的实现计划中将进行支持。元块组实际上是可以用一个组描述符块来进行描述的块组集,简单的说,它由一系列块组组成,同时这些块组对应的组描述符存储在一个块中。它的出现使得Ext3和Ext4的磁盘布局有了一定的变化,以往超级块后紧跟的是变长的GDT块,现在超级块(决定于是否是3,5,7的幂)和一个组描述符块存储在元块组的第一个,第二个和最后一个块组的开始处(见图4)。
图4 采用meta block group后文件系统的磁盘布局在两种情况下我们可能会用到这种新布局:
- 文件系统创建时。用户可以指定使用这种布局。
- 当文件系统增长而且预留的组描述符块耗尽时。目前超级块中有一个域s_first_meta_bg用于描述第一个使用元块组的块组。
该方法非常高效同时保留了足够的冗余以防止异常情况。这种布局中GDT仅占用一个块,由于组描述符占用32个字节,对于块大小为4k的文件系统来说,每个元块组包含128个块组,可以管理16GB的空间,而且每个元块组中对于组描述符表都有3个备份。
对比图4与图3我们就能够发现,当增加新块组时,我们不需要给组描述符表预留空间,而是在当前文件系统后面直接添加新的元块组就可以了。
结论
由于我们在创建文件系统时无法很好的预测将来可能的容量,因此文件系统的在线增长功能是非常有必要的。特别是LVM的出现更大的刺激了该需求,请注意本文所谈到的文件系统的在线增长并未涉及到如何扩大分区或者磁盘的大小,读者可以自己先采用lvm或者类似的工具扩大分区或者卷的大小,然后再采用ext2resize或者类似工具来完成ext3或者ext4文件系统的在线增长,其在内核中的工作机制本文已经阐述,希望对读者能够有所帮助。