• 扇区
  • 磁道

image.png

这一节我们重点目前 Linux 下最主流的文件系统格式——ext 系列的文件系统的格式。

inode 与块的存储

  • 块(Block)。一块的大小是扇区大小的整数倍,默认是 4K。在格式化的时候,这个值是可以设定的。
  • inode, 存储文件元数据
  1. struct ext4_inode {
  2. __le16 i_mode; /* File mode */
  3. __le16 i_uid; /* Low 16 bits of Owner Uid */
  4. __le32 i_size_lo; /* Size in bytes */
  5. __le32 i_atime; /* Access time */
  6. __le32 i_ctime; /* Inode Change time */
  7. __le32 i_mtime; /* Modification time */
  8. __le32 i_dtime; /* Deletion Time */
  9. __le16 i_gid; /* Low 16 bits of Group Id */
  10. __le16 i_links_count; /* Links count */
  11. __le32 i_blocks_lo; /* Blocks count */
  12. __le32 i_flags; /* File flags */
  13. ......
  14. __le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
  15. __le32 i_generation; /* File version (for NFS) */
  16. __le32 i_file_acl_lo; /* File ACL */
  17. __le32 i_size_high;
  18. ......
  19. };
  • 权限
  • 用户
  • 用户组
  • 大小
  • 占用 block 数
  • 时间
    • i_atime 是 access time,是最近一次访问文件的时间
    • i_ctime 是 change time,是最近一次更改 inode 的时间
    • i_mtime 是 modify time,是最近一次更改文件的时间

首先,访问了,不代表修改了,也可能只是打开看看,就会改变 access time。其次,修改 inode,有可能修改的是用户和权限,没有修改数据部分,就会改变 change time。只有数据也修改了,才改变 modify time。

EXT4_N_BLOCKS 保存每个文件块的索引:

  • 一共有15项
  1. #define EXT4_NDIR_BLOCKS 12
  2. #define EXT4_IND_BLOCK EXT4_NDIR_BLOCKS
  3. #define EXT4_DIND_BLOCK (EXT4_IND_BLOCK + 1)
  4. #define EXT4_TIND_BLOCK (EXT4_DIND_BLOCK + 1)
  5. #define EXT4_N_BLOCKS (EXT4_TIND_BLOCK + 1)
  • 在 ext2 和 ext3 中,其中前 12 项直接保存了块的位置,也就是说,我们可以通过 i_block[0-11],直接得到保存文件内容的块。

image.png

当我们用到 i_block[12]的时候,这个块里面不放数据块,而是放数据块的位置,这个块我们称为间接块。如果文件再大一些,i_block[13]会指向一个块,我们可以用二次间接块。二次间接块里面存放了间接块的位置,间接块里面存放了数据块的位置,数据块里面存放的是真正的数据。

这里面有一个非常显著的问题,对于大文件来讲,我们要多次读取硬盘才能找到相应的块,这样访问速度就会比较慢。为了解决这个问题,ext4 做了一定的改变。它引入了一个新的概念,叫做 Extents

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

image.png

ext4_extent_header 可以用来描述某个节点:

  1. struct ext4_extent_header {
  2. __le16 eh_magic; /* probably will support different formats */
  3. __le16 eh_entries; /* number of valid entries */
  4. __le16 eh_max; /* capacity of store in entries */
  5. __le16 eh_depth; /* has tree real underlying blocks? */
  6. __le32 eh_generation; /* generation of the tree */
  7. };

eh_entries 表示这个节点里面有多少项. 这里的项分两种 (理解上参考 B+ 树):

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

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

inode 位图和块位图

如果我要保存一个数据块,或者要保存一个 inode,我应该放在硬盘上的哪个位置呢?难道需要将所有的 inode 列表和块列表扫描一遍,找个空的地方随便放吗?

这样效率太低了。所以在文件系统里面,我们专门弄了一个块来保存 inode 的位图。在这 4k 里面,每一位对应一个 inode。如果是 1,表示这个 inode 已经被用了;如果是 0,则表示没被用。同样,我们也弄了一个块保存 block 的位图。

我们来看接下来的调用链:do_sys_open-> do_filp_open->path_openat->do_last->lookup_open。这个调用链的逻辑是,要打开一个文件,先要根据路径找到文件夹。如果发现文件夹下面没有这个文件,同时又设置了 O_CREAT,就说明我们要在这个文件夹下面创建一个文件,那我们就需要一个新的 inode。

这里面一个重要的逻辑就是,从文件系统里面读取 inode 位图,然后找到下一个为 0 的 inode,就是空闲的 inode。

对于 block 位图,在写入文件的时候,也会有这个过程.

文件系统的格式

数据块的位图是放在一个块里面的,共 4k。每位表示一个数据块,共可以表示 4∗1024∗8=215 个数据块。如果每个数据块也是按默认的 4K,最大可以表示空间为 215∗4∗1024=227 个 byte,也就是 128M。

如果采用“一个块的位图 + 一系列的块”,外加“一个块的 inode 的位图 + 一系列的 inode 的结构”,最多能够表示 128M。是不是太小了?现在很多文件都比这个大。我们先把这个结构称为一个块组。有 N 多的块组,就能够表示 N 大的文件。

对于块组,我们也需要一个数据结构来表示为 ext4_group_desc。这里面对于一个块组里的 inode 位图 bg_inode_bitmap_lo、块位图 bg_block_bitmap_lo、inode 列表 bg_inode_table_lo,都有相应的成员变量。

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

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

对于整个文件系统,别忘了咱们讲系统启动的时候说的。如果是一个启动盘,我们需要预留一块区域作为引导区,所以第一个块组的前面要留 1K,用于启动引导区。

image.png

image.png

目录的存储格式

目录文件的块里面保存的是目录里面一项一项的文件信息。这些信息我们称为 ext4_dir_entry。从代码来看,有两个版本,在成员来讲几乎没有差别,只不过第二个版本 ext4_dir_entry_2 是将一个 16 位的 name_len,变成了一个 8 位的 name_len 和 8 位的 file_type。

image.png

软链接和硬链接的存储格式

所谓的链接(Link),我们可以认为是文件的别名,而链接又可分为两种,硬链接与软链接

ln -s 创建的是软链接,不带 -s 创建的是硬链接。它们有什么区别呢?在文件系统里面是怎么保存的呢?

image.png

硬链接与原始文件共用一个 inode 的,但是 inode 是不跨文件系统的,每个文件系统都有自己的 inode 列表,因而硬链接是没有办法跨文件系统的。

而软链接不同,软链接相当于重新创建了一个文件。这个文件也有独立的 inode,只不过打开这个文件看里面内容的时候,内容指向另外的一个文件。这就很灵活了。我们可以跨文件系统,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。

总结时刻

image.png