3.2.1 磁盘空间布局(Layout)
文件系统的核心功能是实现对磁盘空间的管理。对磁盘空间的管理是指要知道哪些空间被使用了,哪些空间没有被使用。这样,在用户层需要使用磁盘空间时,文件系统就可以从未使用的区域分配磁盘空间。
为了对磁盘空间进行管理,文件系统往往将磁盘划分为不同的功能区域。简单来说,磁盘空间通常被划分为元数据区与数据区两个区域,如图3-11所示。其中,数据区就是存储数据的地方,用户在文件中的数据都会存储在该区域;而元数据区则是对数据区进行管理的地方。前文提到,文件系统需要知道磁盘的哪些区域已经被分配出去了。所以,必须要有一个地方进行记录,这个地方就是元数据区。
图3-11 磁盘空间管理的基本原理
当然,实际文件系统的区域划分要复杂很多,这里主要是让大家容易理解一些。接下来结合实例来介绍一下关于文件系统磁盘空间布局与空间管理的相关内容。
3.2.1.1 基于固定功能区的磁盘空间布局
基于固定功能区的磁盘空间布局是指将磁盘的空间按照功能划分为不同的子空间。每种子空间有具体的功能,以Linux中的ExtX文件系统为例,其空间被划分为数据区和元数据区,而元数据区又被划分为数据块位图、inode位图和inode表等区域,如图3-12所示。
这里ExtX是Ext、Ext2、Ext3和Ext4文件系统的总称,该系列文件系统是Linux原生的文件系统。
但在实际实现时,ExtX并不是将整个磁盘划分为如图3-12所示的功能区,而是先将磁盘划分为等份(最后一份的空间可能会小一些)的若干个区域,这个区域被称为块组(Block Group)。磁盘空间的管理是以块组为单位进行管理的,在所有块组中第1个块组(块组0)是最复杂的。
图3-12 基于固定功能区的磁盘空间布局
图3-13所示为Ext2文件系统的磁盘空间布局及块组0的细节(以4KB逻辑块大小为例,如果是其他逻辑块大小则略有差异)。块组0最前面空间是引导块,这个是预留给操作系统使用的,接下来分别是超级块、块组描述符、预留GDT块、数据块位图、inode位图、inode表和数据块。除块组0及一些备份超级块的块组外,其他块组并没有这么复杂,大多数块组只有数据块位图、inode位图、inode表和数据块等关键的信息。
图3-13 Ext2文件系统的磁盘空间布局及块组0的细节
超级块(SuperBlock):也就是不一般的块,这是相对文件系统的其他块来说的。超级块存储了文件系统级别的信息,如文件系统的逻辑块大小、挂载点等,它是文件系统的起点。
inode(index node,索引节点),所谓索引节点也就是索引数据的节点。在Linux文件系统中一个inode对应着一个文件,它是文件数据的起点。在ExtX文件系统中,inode是放在一个固定的区域的,通常在每个块组中都有若干个inode,称为inode表,类似数组。由于inode数量固定,且存储形式固定,因此可以根据inode偏移给予编号,称为inode ID(或者inode number)。反过来,也可以根据该inode ID快速定位inode的具体位置,进而读到inode的内容。
位图(Bitmap):在ExtX文件系统中包含数据块位图和inode位图,用来描述对应资源的使用情况。位图通过1个位(bit)的数据来描述对应资源的使用情况,0表示没有被使用,1表示已经被使用。
为了能够更加直观地理解ExtX文件系统的布局情况,我们可以格式化一个文件系统,然后通过dumpe2fs命令来获取其描述信息。对于实验验证,我们不必在一个磁盘上来进行文件系统的格式化。其实在一个空白文件即可进行文件系统的格式化。比如,这里格式化一个30MB的Ext2文件系统,并且指定文件系统块大小是1KB,当使用dumpe2fs命令查看时可以看到一共有4个块组,如图3-14所示。
图3-14 Ext2文件系统元数据信息(1KB逻辑块)
可以看到该文件系统中的第1个块组包含超级块及GDT保留信息,第2个块组包含一个备份(Backup)超级块和GDT保留信息,而第3个块组则不包含超级块的信息。正如前文所述,如果块设备的存储空间充足,其实大部分块组是不包含超级块信息的。
对比图3-13中的磁盘空间布局和图3-14格式化的实例,如超级块的位置、位图的位置等,我们可以更加直观地了解磁盘空间布局的细节。通过这种方式可以加深我们对文件系统相关原理的理解。
如果在格式化时选择文件系统块的大小是4KB,则此时我们可以看到文件系统中只有一个块组,如图3-15所示。为什么文件系统块大小不同,块组的数量会有变化呢?
图3-15 Ext2文件系统元数据信息(4KB逻辑块)
原因很简单,因为ExtX文件系统通过一个逻辑块来存储数据块位图,如果将文件系统格式化为1KB的块大小,那么对应的数据块位图可以管理8192(1024×8)个数据块,也就是8MB(1024×1024×8)空间。因此30MB的存储空间被划分为4个块组。
而对于4KB大小逻辑块的文件系统,一个块可以管理32768(4×1024×8)个数据块,也就是128MB(32768×4KB)。因此30MB的存储空间只需要划分为一个块组。
不仅块组的大小受限于此,在ExtX文件系统中inode的数量也受限于此。表3-2所示为官网给出的不同块大小情况下相关数据。由于上述限制,在使用时也就随之会有限制。比如,对于1KB大小逻辑块的文件系统,由于一个块组中最大只有8192个inode,因此也就最多只能创建8192个文件,超过该规格则无法继续创建新的文件。
表3-2 官网给出的不同块大小情况下相关数据
3.2.1.2 基于非固定功能区的磁盘空间布局
基于功能分区的磁盘空间布局空间职能清晰,便于手动进行丢失数据的恢复。但是由于元数据功能区大小固定,因此容易出现资源不足的情况。比如,在海量小文件的应用场景下,有可能会出现磁盘剩余空间充足,但inode不够用的情况。
在磁盘空间管理中有一种非固定功能区的磁盘空间管理方法。这种方法也分为元数据和数据,但是元数据和数据的区域并非固定的,而是随着文件系统对资源的需求而动态分配的,比较典型的有XFS和NTFS等。本节将以XFS为例介绍一下XFS的磁盘空间布局情况及管理磁盘空间的方法。
XFS文件系统先将磁盘划分为等份的区域,称为分配组(Allocate Group,简称AG)。XFS对每个分配组进行独立管理,这样可以避免在分配空间时产生碰撞,影响性能。不同于ExtX文件系统,XFS文件系统的AG容量可以很大,最大可以达到1TB。
如图3-16所示,每个AG包含的信息有超级块(xfs_sb_t)、剩余空间管理信息(xfs_agf_t)和inode管理信息(xfs_agi_t)。在XFS文件系统中,AG中的磁盘空间管理不同于ExtX文件系统中的磁盘空间管理,它不是通过固定的位图区域来管理磁盘空间的,而是通过B+树管理磁盘空间的。xfs_agf_t和xfs_agi_t则是用来磁盘空间B+树和inode B+树的树根和统计信息等内容的数据结构。
图3-16 XFS文件系统磁盘空间布局
剩余空间的管理通过两个B+树来实现。其中,一个B+树通过块的编号来管理剩余空间;另一个B+树通过剩余块的大小来管理。通过两个不同的B+树可以实现对剩余空间的快速查找。
同样,在XFS文件系统中inode也是通过一个B+树来管理的,这一点与ExtX文件系统不同。在XFS文件系统中,将64个inode(默认大小是256字节)打包为一个块(chunk),而该块作为B+树的一个叶子节点。
XFS文件系统中的inode通过B+管理,位置并不确定。因此XFS文件系统无法像ExtX文件系统那个根据inode的偏移来确定编号。XFS文件系统通过另外一种方式确定inode编号,从而方便根据inode编号查找inode节点中的数据。
inode编号分为相对inode编号和绝对inode编号两种。相对inode编号是指针对AG的编号,也就是AG中的编号;绝对inode编号则是在整个文件系统中的编号。
相对inode编号格式分为两部分,高位部分是该inode所在的逻辑块在AG中的偏移,而低位部分则是该inode在该块中的偏移。这样文件系统根据inode编号就可以在AG中定位具体的inode。
绝对inode编号格式就比较好理解了,它是在高位增加了AG的编号。这样在文件系统级别根据AG编号就可以定位AG,然后根据AG内块偏移定位具体的块,进而可以知道具体的inode信息。图3-17是两种模式inode编号格式示意图。
图3-17 XFS文件系统的inode编号格式示意图
为了容易理解,列举一个具体的实例。我们知道XFS文件系统默认inode大小是256KB,假设文件系统逻辑块大小是1KB,那么一个块可以包含4个inode。假设存储inode的块在AG偏移为100的地方,而inode在该逻辑块的第3个位置,相对inode编号示意图如图3-18所示。
图3-18 相对inode编号示意图
根据上述信息可以得到,该inode的ID为100<<3+2,也就是802。绝对inode编号相对于相对inode编号只是在高位增加了一个AG编号。
3.2.1.3 基于数据追加的磁盘空间布局
前文介绍的磁盘空间布局方式对于数据的变化都是原地修改的,也就是对于已经分配的逻辑块,当对应的文件数据改动时都是在该逻辑块进行修改的。在文件随机I/O比较多的情况下,不太适合使用SSD设备,这主要由SSD设备的修改和擦写特性所决定。
有一种基于数据追加的磁盘空间布局方式,也被称为基于日志(Log-structured)的磁盘空间布局方式。这种磁盘空间布局方式对数据的变更并非在原地修改,而是以追加写的方式写到后面的剩余空间。这样,所有的随机写都转化为顺序写,非常适合用于SSD设备。
Linux也有基于日志的文件系统实现,这就是NILFS2。为了便于磁盘空间的管理和回收,NILFS2文件系统将磁盘划分为若干个Segment,Segment默认大小是8MB。这里第1个Segment的大小略有差异,由于前面引导扇区和超级块占用了4KB的空间,因此第1个Segment的大小是4KB~8MB。
如图3-19所示,每一个Segment包含若干个日志(log)。每一个日志由摘要块(Summary Blocks)、有效载荷块(Payload Blocks)和可选的超级根块(SR)组成。这里有效载荷块就是存储实际数据的单元。
图3-19 NILFS2文件系统磁盘空间布局
如图3-19所示,有效载荷块以文件为单位进行组织,每个文件包含数据块和B树块。其中,B树块是元数据,实现对数据块的管理。但是实际情况可能要比图示的格式复杂一些,因为随着文件的修改,数据块和B树块会发生很大的变化。
在NILFS2文件系统中,文件分为若干类,分别是常规文件、目录文件、链接文件和元数据文件。元数据文件是用于维护文件系统元数据的文件。目前,Linux内核版本中的NILFS2文件系统的元数据文件如下。
(1)inode文件(ifile):用于存储inode。
(2)检查点文件(Checkpoint file,简称cpfile):存储检查点。
(3)段使用文件(Segment usage file,简称sufile):用于存储段(segment)的使用状态。
(4)数据地址转换文件(DAT):进行虚拟块号与常规块号的映射。
图3-20所示为NILFS2文件系统中各种类型的文件在磁盘的布局情况,这里的文件包括内部文件和常规文件。
图3-20 NILFS2文件系统中各种类型的文件在磁盘的布局情况
通过图3-20可以看出,NILFS2文件系统中的元数据都是在段的尾部,而数据则是在段的开始位置。这个与实际使用是相关的,因为段数据的分配是从头到尾追加的。这种布局模式便于数据和元数据的管理。
上面介绍的都是单磁盘文件系统。除了单磁盘文件系统,目前还有很多文件系统可以管理多个磁盘。也就是一个文件系统可以构建在多个磁盘之上,并且实现数据的冗余保护,如ZFS和Btrfs等。