文件系统 ext 系列

ext 系列的文件系统

img

文件系统 数据结构
块组描述符表 ext4_group_desc
块位图 bg_block_bitmap_lo
inode 位图 bg_inode_bitmap_lo
inode 列表 bg_inode_table_lo

image-20220503123944075

image-20220503123732596

ext3 文件系统

我们刚才说的 “某个文件分成几块、每一块在哪里”,这些在 inode 里面,应该保存在 i_block 里面。

在 ext2 和 ext3 中,其中前 12 项直接保存了块的位置,也就是说,我们可以通过 i_block [0-11],直接得到保存文件内容的块。

73349c0fab1a92d4e1ae0c684cfe06e2

但是,如果一个文件比较大,12 块放不下。当我们用到 i_block [12] 的时候,就不能直接放数据块的位置了,要不然 i_block 很快就会用完了。这该怎么办呢?我们需要想个办法。我们可以让 i_block [12] 指向一个块,这个块里面不放数据块,而是放数据块的`位置,这个块我们称为间接块。也就是说,我们在 i_block [12] 里面放间接块的位置,通过 i_block [12] 找到间接块后,间接块里面放数据块的位置,通过间接块可以找到数据块。

对于大文件来讲,我们要多次读取硬盘才能找到相应的块,这样访问速度就会比较慢。

ext4 文件系统

1、Extents

为了解决这个问题,ext4 做了一定的改变。它引入了一个新的概念,叫做 Extents

img

我们来解释一下 Extents。比方说,一个文件大小为 128M,如果使用 4k 大小的块进行存储,需要 32k 个块。如果按照 ext2 或者 ext3 那样散着放,数量太大了。但是 Extents 可以用于存放连续的块,也就是说,我们可以把 128M 放在一个 Extents 里面。这样的话,对大文件的读写性能提高了,文件碎片也减少了。

树有一个个的节点,有叶子节点,也有分支节点。每个节点都有一个头,ext4_extent_header 可以用来描述某个节点。

struct ext4_extent_header {
  __le16  eh_magic;  /* probably will support different formats */
  __le16  eh_entries;  /* number of valid entries */                       eh_entries 表示这个节点里面有多少项
  __le16  eh_max;    /* capacity of store in entries */
  __le16  eh_depth;  /* has tree real underlying blocks? */                最底层 eh_depth=0 的是叶子节点
  __le32  eh_generation;  /* generation of the tree */
};

这里的项分两种,如果是叶子节点,这一项会直接指向硬盘上的连续块的地址,我们称为数据节点 ext4_extent;如果是分支节点,这一项会指向下一层的分支节点或者叶子节点,我们称为索引节点 ext4_extent_idx。这两种类型的项的大小都是 12 个 byte

/*
 * This is the extent on-disk structure.
 * It's used at the bottom of the tree.
 */
struct ext4_extent {
  __le32  ee_block;  /* first logical block extent covers */
  __le16  ee_len;    /* number of blocks covered by extent */
  __le16  ee_start_hi;  /* high 16 bits of physical block */
  __le32  ee_start_lo;  /* low 32 bits of physical block */
};
/*
 * This is index on-disk structure.
 * It's used at all the levels except the bottom.
 */
struct ext4_extent_idx {
  __le32  ei_block;  /* index covers logical blocks from 'block' */
  __le32  ei_leaf_lo;  /* pointer to the physical block of the next *
         * level. leaf or next index could be there */
  __le16  ei_leaf_hi;  /* high 16 bits of physical block */
  __u16  ei_unused;
};

如果文件不大,inode 里面的 i_block 中,可以放得下一个 ext4_extent_header 和 4 项 ext4_extent。所以这个时候,eh_depth 为 0,也即 inode 里面的就是叶子节点,树高度为 0。如果文件比较大,4 个 extent 放不下,就要分裂成为一棵树,eh_depth>0 的节点就是索引节点,其中根节点深度最大,在 inode 中。最底层 eh_depth=0 的是叶子节点。

除了根节点,其他的节点都保存在一个块 4k 里面,4k 扣除 ext4_extent_header 的 12 个 byte,剩下的能够放 340 项 【(4*1024-12)/12=340】,每个 extent 最大能表示 128MB 的数据,340 个 extent 会使你表示的文件达到 42.5GB【340*128000000=43,520,000,000】。这已经非常大了,如果再大,我们可以增加树的深度。

2、位图

到这里,我们知道了,硬盘上肯定有一系列的 inode 和一系列的块排列起来。

在文件系统里面,我们专门弄了一个块来保存 inode 的位图。inode 位图其实就是一个管理器,管理存储 inode 的集合中,哪些被占用,哪些空闲。在这 4k 里面,每一位对应一个 inode。如果是 1,表示这个 inode 已经被用了;如果是 0,则表示没被用。inode 位图是文件系统 mount 时从磁盘读取的,和超级块一起读入到内存。

同样,我们也弄了一个块保存 block 的位图。创建文件,其实就是根据 inode 位图和 block 位图,确定哪些 inode 和 block 是空闲的,然后分别向这些 block 中写入数据,

3、格式

到这里,我们来算一下空间。

采用 “一个块的位图 + 一系列的块”,外加 “一个块的 inode 的位图 + 一系列的 inode 的结构”,

一个块的位图:4K

这样的话,每位可以表示一个数据块,共可以表示 8*4*1024=32,768=2^15 个数据块。

一系列的块:如果每个数据块也是按默认的 4K

一个文件最大可以表示空间为 32,768*4*1024=134,217,728=2^27 个 byte,也就是 128M。

最多能够表示 128M。是不是太小了?现在很多文件都比这个大。

1)块组

我们先把这个结构称为一个块组。有 N 多的块组,就能够表示 N 大的文件。

2)块组描述符表

这样一个个块组,就基本构成了我们整个文件系统的结构。因为块组有多个,块组描述符也同样组成一个列表,我们把这些称为块组描述符表

3)超级快

我们还需要有一个数据结构,对整个文件系统的情况进行描述,这个就是超级块 ext4_super_block。

这里面有整个文件系统一共有多少 inode,s_inodes_count;一共有多少块,s_blocks_count_lo,每个块组有多少 inode,s_inodes_per_group,每个块组有多少块,s_blocks_per_group 等。这些都是这类的全局信息。

4)保留引导区

如果是一个启动盘,我们需要预留一块区域作为引导区,所以第一个块组的前面要留 1K,用于启动引导区。

e3718f0af6a2523a43606a0c4003631b

img

4、备份

超级块和块组描述符表都是全局信息,而且这些数据很重要。如果这些数据丢失了,整个文件系统都打不开了,这比一个文件的一个块损坏更严重。

所以,这两部分我们都需要备份,但是采取不同的策略。

默认情况下,超级块和块组描述符表都有副本保存在每一个块组里面。

1)超级快

如果开启了 sparse_super 特性,超级块和块组描述符表的副本只会保存在块组索引为 0、3、5、7 的整数幂里。除了块组 0 中存在一个超级块外,在块组 1(30=1)的第一个块中存在一个副本;在块组 3(31=3)、块组 5(51=5)、块组 7(71=7)、块组 9(32=9)、块组 25(52=25)、块组 27(33=27)的第一个 block 处也存在一个副本。

对于超级块来讲,由于超级块不是很大,所以就算我们备份多了也没有太多问题。但是,对于块组描述符表来讲,如果每个块组里面都保存一份完整的块组描述符表,一方面很浪费空间;另一个方面,由于一个块组最大 128M,而块组描述符表里面有多少项,这就限制了有多少个块组,128M * 块组的总数目是整个文件系统的大小,就被限制住了。

2)块组描述符

我们的改进的思路就是引入 Meta Block Groups 特性

首先,块组描述符表不会保存所有块组的描述符了,而是将块组分成多个组,我们称为元块组(Meta Block Group)。每个元块组里面的块组描述符表仅仅包括自己的,一个元块组包含 64 个块组,这样一个元块组中的块组描述符表最多 64 项。我们假设一共有 256 个块组,原来是一个整的块组描述符表,里面有 256 项,要备份就全备份,现在分成 4 个元块组,每个元块组里面的块组描述符表就只有 64 项了,这就小多了,而且四个元块组自己备份自己的。

b0bf4690882253a70705acc7368983b9

根据图中,每一个元块组包含 64 个块组,块组描述符表也是 64 项,备份三份,在元块组的第一个,第二个和最后一个块组的开始处。

struct ext4_super_block {
......
  __le32  s_blocks_count_lo;  /* Blocks count */
  __le32  s_r_blocks_count_lo;  /* Reserved blocks count */
  __le32  s_free_blocks_count_lo;  /* Free blocks count */
......
  __le32  s_blocks_count_hi;  /* Blocks count */
  __le32  s_r_blocks_count_hi;  /* Reserved blocks count */
  __le32  s_free_blocks_count_hi;  /* Free blocks count */
......
}

这样化整为零,我们就可以发挥出 ext4 的 48 位块寻址的优势了,在超级块 ext4_super_block 的定义中,我们可以看到块寻址分为高位和低位,均为 32 位,其中有用的是 48 位,2^48 个块是 1EB,足够用了。

posted @ 2022-05-04 14:07  季文康  阅读(347)  评论(0编辑  收藏  举报