文件系统的组成部分

转载于骏马金龙

将磁盘进行分区,分区是将磁盘按照柱面进行物理上的划分。划分好分区后还要进行格式化,然后再挂载才能使用。格式化分区的过程其实就是创建文件系统的过程。

文件系统的类型有很多中,如CentOS 5和Centos 6上默认使用的ext2/3/4,CentOS 7上默认使用的xfs,windows上的NTFS,光盘类的文件系统ISO9660,MAC上的混合文件系统HFS,网络文件系统NFS,Oracle研发的btrfs,还有老式的FAT/FAT32等。

block的出现

硬盘最底层的读写IO一次是一个扇区512字节,如果要读写大量的文件,以扇区为单位肯定很消耗性能,所以硬盘使用了一个称为逻辑块的概念。逻辑块是逻辑的,由磁盘驱动器负责维护和操作,并非是像扇区一样物理划分的。一个逻辑块的大小可能包含一个或者多个扇区,每个逻辑块都有唯一的地址,成为LBA。有了逻辑块之后,磁盘控制器对数据的操作就以逻辑块为单位,一次读写一个逻辑块,磁盘控制器知道如何将逻辑块翻译成对应的扇区并读写数据。

以上是磁盘层次,到了linux操作系统层次,通过文件系统提供了一个也称为块的读写单元,文件系统数据块的大小一般为1024bytes或者2048bytes或者4096bytes。文件系统数据块也是逻辑块的概念,是文件系统层次维护的,而磁盘上的逻辑数据库是由磁盘控制器维护的,文件系统的IO管理器知道如何将它的数据块翻译成磁盘维护的数据块地址LBA。对于使用文件系统的IO操作来说,比如读写文件,这些IO的基本单元是文件系统上的数据块,一次读写一个文件系统数据块。比如需要读写一个或者多个数据块时,文件系统的IO管理器首先计算出这些文件系统块对应在那些磁盘数据块中,也就是LBA,然后通知磁盘控制器要读取哪些块的数据,硬盘控制器将这些块翻译成扇区地址,然后从扇区中读取数据,再通过硬盘控制器将这些扇区数据重组写入到内存中。

本文既然是讨论文件系统的,那么重点自然是在文件系统上而不是在磁盘上,所以后文出现的block均表示的是文件系统的数据块而不是磁盘维护的逻辑块。

文件系统block的出现使得文件系统层面上读写性能大大提高,也大量减少了碎片。但是它的副作用时可能造成空间的浪费。由于文件系统以block为单元,即使存储的文件只有1K大小也将占用一个block,剩余空间完全是浪费的。在某些业务需求下。可能存储大量的小文件,这样就会造成大量的空间浪费。

尽管有缺点,但是其优点足够明显,在当下硬盘容量廉价且追求性能的时代,使用block是一定的。

inode的出现

如果存储的1个文件占用了大量的block,读取时会如何?假设block大小为1KB,仅仅存储一个10M的文件就需要10240个block,而且这些blocks很可能在位置上是不连续的,读取该文件时,难道要从前王后扫描整个文件系统的块吗?显然是不可能这样做。再考虑一下,读取一个只占用1个block的文件,难道只读取一个block就结束了吗?并不是,仍然是扫描整个文件系统的所有block,因为它不知道什么时候扫描到,扫描到了它也不知道这个文件是不是已经完整而不需要再扫描其他的block。

另外,每个文件都有属性(如权限、大小、时间戳等),这些属性类的元数据存储在哪里呢?难道也和文件的数据部分存储在块中吗?如果一个文件占用多个block那是不是每个属于该文件的block都要存储一份文件元数据?但是如果不在每个block中存储元数据文件系统又怎么知道某一个block是不是属于该文件呢?但是显然,每个数据block中都存储一份元数据太浪费空间。

文件系统设计者当然知道这样的存储方式很不理想,所以需要优化存储方式。如何优化?对于这种类似的问题的解决方法是使用索引,通过扫描索引找到对应的数据,而且索引可以存储部分数据。

在文件系统上索引技术具体化为索引节点(index node),在索引结点上存储的部分数据即为文件的属性元数据及其他少量信息。一般说索引占用的空间相比其索引的文件数据而言占用的空间就少的多,扫描它比扫描整个数据要快得多,否则索引就每意义了。这样一来就解决了前面所有的问题。

在文件系统上的术语中,索引节点称为inode。在inode中存储了inode号(注,inode中并未存储inode
num,但为了方便理解,这里暂时认为它存储了inode号)、文件类型、权限、文件所有者、大小、时间戳等元数据信息,最重要的是还存储了指向属于该文件block的指针,这样读取inode就可以找到属于该文件的block,进而读取这些block并获得该文件的数据。由于后面还会介绍一种指针,为了方便称呼和区分,暂且将这个inode记录中指向文件data block的指针称之为block指针。以下是ext2文件系统中inode包含的信息示例:

Inode: 12   Type: regular    Mode:  0644   Flags: 0x0
Generation: 1454951771    Version: 0x00000000:00000001
User:     0   Group:     0   Size: 5
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x5b628db2:15e0aff4 -- Thu Aug  2 12:50:58 2018
 atime: 0x5b628db2:15e0aff4 -- Thu Aug  2 12:50:58 2018
 mtime: 0x5b628db2:15e0aff4 -- Thu Aug  2 12:50:58 2018
crtime: 0x5b628db2:15e0aff4 -- Thu Aug  2 12:50:58 2018
Size of extra inode fields: 28
BLOCKS:
(0):1024
TOTAL: 1

一般inode大小为128字节或256字节,相比那些MB或GB计算的文件数据而言小得多的多,但也要知道可能一个文件大小小于inode大小,例如只占用1个字节的文件。

bmap的出现

在向硬盘存储数据时,文件系统需要知道哪些块是空闲的,哪些块是已经占用了的。最苯的方法当然是前后扫描,遇到空闲块就存储一部分,继续扫描直到存储完所有数据。

优化的方法当然也可以考虑索引,但是仅仅1G的文件就有1KB的block共1024*1024=1048576个,这仅仅是1GB,那如果是100G,500G甚至更大呢,仅仅使用索引的数量和空间也将极大,这时就出现了更高一级的优化方法:位块图(bitmap简称bmap)。

位图只使用0和1标识对应block是空闲还是被占用,0和1在位图中的位置和block的位置一一对应,第一位标识第一个块,第二个位标识第二个块,依次下去直到标记完所有的block。

考虑下为什么块位图更优化。在位图中1个字节8个位,可以标识8个block。对于一个block大小为1KB、容量为1G的文件系统而言,block数量有10241024个,所以在位图中使用10241024个位共1024*1024/8=131072字节=128K,即1G的文件只需要128个block做位图就能完成一一对应。通过扫描这100多个block就能知道哪些block是空闲的,速度提高了非常多。

但是需要注意,bmap的优化针对的是写优化,因为只有写才需要找到空闲block并分配空闲block。对于读而言,只要通过inode找到了block位置,cpu就能迅速计算出block在物理磁盘上的地址,cpu的计算速度是极快的,计算block地址的时间几乎可以忽略,那么读速度基本认为受硬盘本身性能的影响而与文件系统无关。大多数可能稍大一点的文件可能会存储在不连续的block上,而且使用了一段时间的文件系统可能会有不少碎片,这时硬盘的随机读取性能直接决定数据的速度,这也是机械硬盘性能相比固态硬盘慢很多的原因之一,而且固态硬盘的随机读和连续读取速度几乎是一致的,对它来说,文件系统碎片的多少不会影响读取速度。

虽然bmap已经极大的优化了扫描,但是仍有其瓶颈:如果文件系统是100G呢?100G的文件系统要使用128*100=12800个1KB大小的block,这就占用了12.5M的空间了。试想完全扫描12800个很可能不连续的block这也是需要占用一些时间的,虽然快但是扛不住每次存储文件都要扫描带来的巨大开销。

所以需要再次优化,如何优化?简而言之就是将文件系统划分开形成块组,至于块组的介绍放在后文。

inode表的出现

回顾以下inode信息:inode存储了inode号(注,同前文,inode中并未存储inode num)、文件属性元数据、指向文件占用的block的指针;每一个inode占用128字节或256字节。

现在又出现问题了,一个文件系统中可以说有无数多个文件,每一个文件都对应一个inode,难道每一个仅128字节的inode都要单独占用一个block进行存储吗?这太浪费空间了。

所以更优的方法是将多个inode合并存储在block中,对于128字节的inode,一个block存储8个inode,对于256字节的inode,一个block存储4个inode。这就使得每个存储inode的块都不浪费。

在ext文件系统上,将这些物理上存储inode的block组合起来,在逻辑上形成一张inode表(inode table)来记录所有的inode。

再细细一思考,就能发现一个大的文件系统仍将占用大量的块来存储inode,想要找到其中的一个inode记录也需要不小的开销,尽管它们已经形成了一张逻辑上的表,但扛不住表太大记录太多。那么如何快速找到inode,这同样是需要优化的,优化的方法是将文件系统的block进行分组划分,每个组中都存有本组inode
table范围、bmap等。

imap的出现

前面说的bmap是块位图,用于标识文件系统中哪些block是空闲哪些block是占用的。

对于inode也一样,在存储文件(linux中一切皆文件)时需要为其分配一个inode号。但是在格式化创建文件系统后所有的inode号都已被事先计算好了(创建文件系统时会为每个块组计算好该块组拥有哪些inode号),因此产生了问题:要为文件分配哪个inode号呢?又如何知道某一个inode号时否已经被分配了呢?

既然是“是否被占用”问题,使用位图是最佳方案,像bmap记录block的情况一样。标识inode号是否被分配的位图称为inodemap简称为imap。这时要为一个文件分配inode号只需要扫描imap即可知道哪个inode是空闲的。

imap存在着和bmap和inode table一样需要解决的问题:如果文件系统比较大,imap本身就会很大,每次文件存储都需要进行扫描,这样效率不够高。同样,优化的方式是将文件系统占用的block划分成块组,每个块组有自己的imap范围。

块组的出现

前面一直提到的优化方法是将文件系统占用的block划分成块组(block group),解决bmap、inode table和imap太大的问题。

在物理层面上的划分是将磁盘按柱面划分为多个分区,即多个文件系统;在逻辑层面上的划分是将文件系统划分成块组。每个文件系统包含多个块组,每个块组包含多个元数据区和数据区:元数据区就是存储bmap、inode table、imap等的数据;数据区就是存储文件数据的区域。注意块组是逻辑层面的概念,所以并不会真的在磁盘上按柱面、按扇区、按磁道等概念进行划分。

块组的划分

块组在文件系统创建完成后就已经划分完成了,也就是说元数据区bmap、inode table和imap等信息占用的block以及数据区占用的block都已经划分划分好了。那么文件系统如何知道一个块组元数据区包含多少个block,数据区又包含多少block呢?

它只需确定一个数据——每个block的大小,再根据bmap至多只能占用一个完整的block的标准就能计算出块组如何划分。如果文件系统非常小,所有的bmap总共都不能占用完一个block,那么也只能空闲bmap的block了。

每个block的大小在创建文件系统时可以人为指定,不指定也有默认值。

假设现在的block的大小是1KB,一个bmap完整占用一个block能标识1024*8=8192个block(当然这8192个block是数据区和元数据区共8192个,因为元数据区分配的block也需要通过bmap来标识)。每个block是1K,每个块组是8192K即8M,创建1G的文件系统需要划分1024/8=128个块组,如果是1.1G的文件系统呢?128+12.8=128+13=141个块组。

每个组的block数目是划分好了,但是每个组设定多少个inode号呢?inode table占用多少block呢?这需要由系统决定了,因为描述"每多少个数据区的block就为其分配一个inode号"的指标默认是我们不知道的,当然创建文件系统时也可以人为指定这个指标或者百分比例。

使用dumpe2fs可以将ext类的文件系统信息全部显示出来,当然bmap是每个块组固定一个block的不用显示,imap比bmap更小所以也只占用1个block不用显示。

下图是一个文件系统的部分信息,在这些信息的后面还有每个块组的信息,其实这里面的很多信息都可以通过几个比较基本的元数据推导出来。

Inode count:              66060288
Block count:              134189307
Reserved block count:     0
Free blocks:              124790761
Free inodes:              65729020
First block:              0
Block size:               4096
Fragment size:            4096
Group descriptor size:    64
Reserved GDT blocks:      192
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         16128
Inode blocks per group:   1008
Flex block group size:    16
Filesystem created:       Tue Jan 26 00:11:41 2021
Last mount time:          Tue May 11 14:19:40 2021
Last write time:          Tue Jan 26 00:12:05 2021
Mount count:              12
Maximum mount count:      -1
Last checked:             Tue Jan 26 00:11:41 2021
Check interval:           0 (<none>)
Lifetime writes:          1880 GB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               256
Required extra isize:     32
Desired extra isize:      32
Journal inode:            8

从这张表中能计算出文件系统的大小,该文件系统共134189307个blocks,每个block大小为4K,所以文件系统大小为134189307*4/1024/1024=511.9GB。

也能算出分了多少个块组,因为每个块组的block数量是32768,所以块组的数量为134189307/32768=4096个。由于块组从0开始编号,所以块组的最后编后位4095。

Group 4095: (Blocks 134184960-134189306) csum 0x4d45 [INODE_UNINIT, ITABLE_ZEROED]
  Block bitmap at 133693455 (bg #4080 + 15), csum 0x668edd27
  Inode bitmap at 133693471 (bg #4080 + 31), csum 0xeb65bbc4
  Inode table at 133708592-133709599 (bg #4080 + 15152)
  4347 free blocks, 16128 free inodes, 0 directories, 16128 unused inodes
  Free blocks: 134184960-134189306
  Free inodes: 66044161-66060288

标题:文件系统的组成部分
作者:reyren
地址:https://www.reyren.cn/articles/2021/06/21/1624260996158.html

    评论
    0 评论
avatar

取消