第 4 章 Linux ext 文件系统

作者: Brinnatt 分类: ARM64 Linux 基础精修 发布时间: 2022-01-20 15:11

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

文件系统的类型有很多种,CentOS 5-6 默认使用 ext2/ext3/ext4,CentOS 7上默认使用的xfs,windows上的NTFS,光盘类的文件系统ISO9660,MAC上的混合文件系统HFS等。

ext 文件系统根据历史改进分 ext2、ext3、ext4 几个版本。ext3是有日志的ext2改进版,ext4对相比ext3做了非常多的改进。

4.1、文件系统组件

4.1.1、block 概念

磁盘逻辑块

硬盘最底层的读写 IO 一次是一个扇区512字节,为了提升性能硬盘使用了一个称作逻辑块的概念。

逻辑块是逻辑的,由磁盘驱动器负责维护和操作,它并非是像扇区一样物理划分的。

一个逻辑块的大小可能包含一个或多个扇区,每个逻辑块都有唯一的地址,称为 LBA

磁盘控制器对数据的操作以逻辑块为单位,将逻辑块翻译成对应的扇区读写数据。

文件系统逻辑块

文件系统提供了一个也称为块的读写单元,文件系统数据块的大小一般为 1024 bytes(1K) 或 2048 bytes(2K) 或 4096 bytes(4K)。

文件系统数据块也是逻辑概念,是文件系统层次维护的,而磁盘上的逻辑数据块是由磁盘控制器维护的,文件系统的 IO 管理器知道如何将它的数据块翻译成磁盘维护的数据块地址 LBA。

对于使用文件系统的 IO 操作来说,比如读写文件,这些 IO 的基本单元是文件系统上的数据块,一次读写一个文件系统数据块。

举例:开始读 --> IO 管理器计算 LAB --> 磁盘控制器计算磁盘逻辑块对应哪些扇区 --> 数据从磁盘到内存

注意:本文重点介绍文件系统逻辑块,不是磁盘逻辑块,所以后文出现的 block 均表示的是文件系统的数据块而不是磁盘维护的逻辑块。

4.1.2、inode 概念

block 的大小默认为 4KB,如果存储 1GB 大小的文件,如何组织 block 的存储,全盘扫描肯定不现实。为了更好地组织 block 进行数据读写,设计了索引技术,在文件系统上索引技术具体化为索引节点(index node)。

一般来说索引占用的空间相比其索引的文件数据而言占用的空间就小得多,扫描它比扫描整个数据要快得多。

在文件系统上的术语中,索引节点称为 inode。在 inode 中存储了 inode 号、文件类型、权限、文件所有者、大小、时间戳等元数据信息,最重要的是还存储了指向属于该文件 block 的指针,这样读取 inode 就可以找到属于该文件的 block,进而读取这些 block 并获得该文件的数据。

  • 文件都存储在目录当中,inode num 在目录 data block 当中,然后对应到 inode,inode 中并未存储 inode num,既然它们有一一对应关系,姑且理解为 inode num 就在 inode 当中。

后面还会介绍一种指针,为了方便称呼和区分,暂且将这个 inode 记录中指向文件 data block 的指针称之为 block 指针。

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

4.1.3、bmap 概念

在向硬盘存储数据时,文件系统需要知道哪些块是空闲的,哪些块是已经占用了的。

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

位图的优势

在位图中 1 个字节 8 个位,可以标识 8 个 block。

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

4.1.4、inode table 概念

每一个文件都对应一个 inode,一个 block 默认设为 1KB(2KB 或 4KB),一个 inode 默认 128B(或 256B) 占用一个 block 吗,这显然太浪费了。

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

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

inode table

4.1.5、imap 概念

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

imap 是 inode 位图,用于标识文件系统中哪些 inode 是空闲哪些 inode 是占用的。

4.1.6、block group 概念

无论是 block、 inode、bmap 还是 imap,全盘组织没有问题,每个文件都可以找到,但是如果文件太大,全盘查找 inode、bmap、imap 一样性能比较差。

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

在物理层面上的划分是将磁盘按柱面划分为多个分区,即多个文件系统;在逻辑层面上的划分是将文件系统划分成块组。

每个文件系统包含多个块组,每个块组包含多个元数据区和数据区

  • 元数据区就是存储 bmap、inode table、imap 等的数据;
  • 数据区就是存储文件数据的区域。

注意:块组是逻辑层面的概念,所以并不会真的在磁盘上按柱面、按扇区、按磁道等概念进行划分。

4.1.7、划分 block group

块组在文件系统创建完成后就已经划分完成了,也就是说元数据区 bmap、inode table 和 imap 等信息占用的 block 以及数据区占用的 block 都已经划分好了。

那么文件系统如何知道一个块组元数据区包含多少个 block,数据区又包含多少 block 呢?

  1. 确定每个 block 的大小,每个 block 的大小在创建文件系统时可以人为指定,不指定也有默认值。
  2. 根据 bmap 最少要占用一个完整的 block 的标准就能计算出块组如何划分。
    • 如果文件系统非常小,所有的 bmap 总共都不能占用完一个 block,那么也只能空闲 bmap 的 block 了。
  3. 假如现在 block 的大小是 1KB,一个 bmap 完整占用一个 block 能标识 1024*8= 8192 个 block。
    • 这 8192 个 block 是数据区和元数据区共 8192 个,因为元数据区分配的 block 也需要通过 bmap 来标识。
  4. 每个 block 是 1KB,每个块组是 8192KB 即 8MB,创建 1GB 的文件系统需要划分 1024/8=128 个块组。
  5. 如果是 1.1G 的文件系统呢?
    • 128 + 12.8 = 128 + 13 = 141个块组。

4.1.8、划分 inode

inode 大小为 128 字节的倍数,最小为 128 字节。它有默认值大小,它的默认值由 /etc/mke2fs.conf 文件中指定。不同的文件系统默认值可能不同。

root@arm64v8:~# cat /etc/mke2fs.conf 
[defaults]
    base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
    default_mntopts = acl,user_xattr
    enable_periodic_fsck = 0
    blocksize = 4096
    inode_size = 256
    inode_ratio = 16384

[fs_types]
    ext3 = {
        features = has_journal
    }
    ext4 = {
        features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
        auto_64-bit_support = 1
        inode_size = 256
    }
    ......
  • blocksize 的默认值 4096 字节即 4KB
  • inode_size 的默认值 256 字节
  • blocksize 的默认值和 inode 分配比率 inode_ratio 默认值 16384 字节,表示每 16384 个字节即 16KB 就分配一个 inode 号,由于默认 blocksize=4KB,所以每 4 个 block 就分配一个 inode 号。
    • 分配的这些 inode 号只是预分配,并不真的代表会全部使用,毕竟每个文件才会分配一个 inode 号。
    • 如果存储的都是电影这样的大文件,一个 inode 号对应成千上万个 block 完成一个大文件存储,inode 号就浪费很多,inode 占用的空间也浪费很多。
    • 当然 inode size、inode 分配比例、block size 都可以在创建文件系统的时候人为指定。

分配的 inode 自身会占用 block,而且其自身大小 256 字节还不算小,所以 inode 号的浪费代表着空间的浪费。

既然知道了 inode 分配比率,就能计算出每个块组分配多少个 inode 号,也就能计算出 inode table 占用多少个 block。

4.1.9、查看文件系统组件

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

root@arm64v8:~# dumpe2fs /dev/vdb2 | grep -A 26 "^Inode count"
dumpe2fs 1.42.13 (17-May-2015)
Inode count:              1310720                   # 文件系统 inode 号数量
Block count:              5242880                   # 文件系统 block 总数
Reserved block count:     262144                    # 保留的 block 数量
Free blocks:              5116558                   # 空闲的 block 数量
Free inodes:              1310709                   # 空闲的 inode 数量
First block:              0                         # 第一个 block 号
Block size:               4096                      # block 大小 4KB
Fragment size:            4096
Reserved GDT blocks:      1022                      # 保留 GDT 的块总数
Blocks per group:         32768                     # 每个块组的 block 数量
Fragments per group:      32768
Inodes per group:         8192                      # 每个块组的 inode 号数量
Inode blocks per group:   512                       # 每个块组inode占用的块数量(inode表数量)
Filesystem created:       Wed Jun  2 16:09:05 2021
Last mount time:          n/a
Last write time:          Wed Jun  2 16:09:05 2021
Mount count:              0
Maximum mount count:      -1
Last checked:             Wed Jun  2 16:09:05 2021
Check interval:           0 (<none>)
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11                        # 文件系统的第一个 inode 号
Inode size:               256                       # 每个 inode 的大小为 256 字节
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
root@arm64v8:~#
  • 根据查看文件系统的数据,可以计算出文件系统的大小,共 5242880 个 blocks,每个 block 大小为 4KB,所以文件系统的大小为 5242880 * 4 / 1024 / 1024 = 20GB

  • 根据查看文件系统的数据,可以计算出分了多少个块组,每一个块组的 block 数量为 32768,所以块组的数量为 5242880 / 32768 = 160 即 160 个块组。由于块组从 0 开始编号,所以最后一个块组编号为 Group 159。

    Group 159: (Blocks 5210112-5242879)
    Block bitmap at 5210112 (+0), Inode bitmap at 5210113 (+1)
    Inode表位于 5210114-5210625 (+2)
    32254 free blocks, 8192 free inodes, 0 directories
    可用块数: 5210626-5242879
    可用inode数: 1302529-1310720

4.2、文件系统完整结构

将上文描述的 bmap、inode table、imap、数据区的 blocks 和块组的概念组合起来就形成了一个文件系统,当然这还不是完整的文件系统。完整的文件系统如下图

ext filesystem construction

首先,该图中多了 Boot Block、Super Block、GDT、Reserver GDT 这几个概念。下面会分别介绍它们。

然后,图中指明了块组中每个部分占用的 block 数量,除了 superblock、bmap、imap 能确定占用 1 个 block,其他的部分都不能确定占用几个 block。

最后,图中指明了 Superblock、GDT 和 Reserved GDT 是同时出现且不一定存在于每一个块组中的,也指明了 bmap、imap、inode table 和 data blocks 是每个块组都有的。

4.2.1、Boot Block

上图中的 Boot Block 部分,也称为 boot sector。

它位于分区上的第一个块,占用 1024 字节,并非所有分区都有这个 boot sector,只有装了操作系统的主分区和装了操作系统的逻辑分区才有。

里面存放的也是 boot loader,这段 boot loader 称为 VBR(主分区装操作系统时) 或 EBR(扩展分区装操作系统时),这里的 Boot loader 和 mbr 上的 boot loader 是存在交错关系的。

开机启动的时候,首先加载 mbr 中的 bootloader,然后定位到操作系统所在分区的 boot serctor 上加载此处的 boot loader。

如果是多系统,加载 mbr 中的 bootloader 后会列出操作系统菜单,菜单上的各操作系统指向它们所在分区的 boot sector 上。

Boot sector

这种操作系统的管理方式是早期传统 BIOS 下的管理方式,现在的服务器都进化到了 UEFI 的启动方式,使用 grub 的方案管理操作系统启动,与传统的 BIOS 管理方式在逻辑上是一致的,UEFI 新加了很多特性,俨然成了一个小型的操作系统,所以在系统启动之前可以做很多事情,比如硬件管理、网络管理等。

4.2.2、Super Block

超级块 super block 记录了整个文件系统的块组信息,包括 block 数量、inode 信息、文件系统时间戳等。在 4.1.9 小节使用 dumpe2fs 查看的就包括 super block 中记录的信息,可想而知 super block 多么重要。

  • dumpe2fs 不加选项显示的是 super block 和 GDT 信息,使用 dumpe2fs -h 只显示 super block 信息。

  • 存储这些信息占用 1024 字节,所以也要一个 block,这个 block 称为超级块(superblock),它的 block 号可能为 0 也可能为 1。

    • 如果 block 大小为 1K,则引导块正好占用一个 block,这个 block 号为 0,所以 superblock 的号为 1;
    • 如果 block 大小大于 1K,则引导块和超级块同置在一个 block 中,这个 block 号为 0。总之 superblock 的起止位置是第二个 1024(1024-2047) 字节。
  • 除了 dumpe2fs 命令外,df 命令读取的也是 super block,速度非常快;du 命令需要遍历整个目录的所有文件,速度非常慢。

    [root@arm64v8 ~]# df -hT
    Filesystem     Type      Size  Used Avail Use% Mounted on
    devtmpfs       devtmpfs   16G     0   16G   0% /dev
    tmpfs          tmpfs      16G     0   16G   0% /dev/shm
    tmpfs          tmpfs      16G  147M   16G   1% /run
    tmpfs          tmpfs      16G     0   16G   0% /sys/fs/cgroup
    /dev/vda4      xfs       483G  1.2G  482G   1% /
    /dev/vda2      xfs      1014M  108M  907M  11% /boot
    /dev/vda1      vfat      200M  7.6M  193M   4% /boot/efi
    tmpfs          tmpfs     3.2G     0  3.2G   0% /run/user/0
    [root@arm64v8 ~]#

superblock 对于文件系统而言是至关重要的,超级块丢失或损坏必将导致文件系统的损坏。

旧式的文件系统将超级块备份到每一个块组中,但是这又有所空间浪费,所以 ext2 文件系统只在块组 0、1 和 3、5、7 幂次方的块组中保存超级块的信息,如 Group9、Group25 等。

尽管保存了这么多的 superblock,但是文件系统只使用第一个块组即 Group0 中超级块信息来获取文件系统属性,只有当 Group0 上的 superblock 损坏或丢失才会找下一个备份超级块复制到 Group0 中来恢复文件系统。

4.2.3、GDT

ext 文件系统每一个块组信息使用 32 字节描述,这 32 个字节称为块组描述符,所有块组的块组描述符组成块组描述符表 GDT(group descriptor table)。

虽然每个块组都需要块组描述符来记录块组的信息和属性元数据,但是不是每个块组中都存放了块组描述符。

ext 文件系统的存储方式是将它们组成一个 GDT,并将该 GDT 存放于某些块组中,存放 GDT 的块组和存放 superblock 和备份 superblock 的块相同,也就是说它们是同时出现在某一个块组中的。读取时也总是读取 Group0 中的块组描述符表信息。

假如 block 大小为 4KB 的文件系统划分了 160 个块组,每个块组描述符 32 字节,那么 GDT 就需要 160 * 32 = 5120 字节即两个 block 来存放。这两个 GDT block 中记录了所有块组的块组信息,且存放 GDT 的块组中的 GDT 都是完全相同的。

# 通过 dumpe2fs 获取 GDT 信息
root@arm64v8:~# dumpe2fs /dev/vdb2 | grep -A 100 "Group 159"
dumpe2fs 1.42.13 (17-May-2015)
Group 159: (Blocks 5210112-5242879)
  Block bitmap at 5210112 (+0), Inode bitmap at 5210113 (+1)
  Inode表位于 5210114-5210625 (+2)
  32254 free blocks, 8192 free inodes, 0 directories
  可用块数: 5210626-5242879
  可用inode数: 1302529-1310720
root@arm64v8:~#

4.2.4、Reserved GDT

保留 GDT 用于以后扩容文件系统使用,防止扩容后块组太多,使得块组描述符超出当前存储 GDT 的 blocks。

保留 GDT 和 GDT 总是同时出现,当然也就和 superblock 同时出现了。

例如前面 160 个块组使用了 2 个 block 来存放 GDT,但是此时第二个 block 还空余很多空间,当扩容到一定程度时 2 个 block 已经无法再记录块组描述符了,这时就需要分配一个或多个 Reserved GDT 的 block 来存放超出的块组描述符。

由于新增加了 GDT block,所以应该让每一个保存 GDT 的块组都同时增加这一个 GDT block,所以将保留 GDT 和 GDT 存放在同一个块组中可以直接将保留 GDT 变换为 GDT 而无需使用低效的复制手段备份到每个存放 GDT 的块组。

同理,新增加了 GDT 需要修改每个块组中 superblock 中的文件系统属性,所以将 superblock 和 Reserved GDT/GDT 放在一起又能提升效率。

4.3、Data Block

ext filesystem construction

如上图,除了 Data Blocks 其他的部分都解释过了。data block 是直接存储数据的 block,但事实上并非如此简单。

数据所占用的 block 由文件对应 inode 记录中的 block 指针找到,不同的文件类型,数据 block 中存储的内容是不一样的。以下是 Linux 中不同类型文件的存储方式。

  1. 对于目录,该目录下的所有文件和一级子目录的目录名存储在数据块中。
    • 文件名和 inode 号不是存储在其自身的 inode 中,而是存储在其所在目录的 data block 中
  2. 对于常规文件,文件的数据正常存储在数据块中。
  3. 对于符号链接,如果目标路径名较短则直接保存在 inode 中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
  4. 设备文件、FIFO 和 socket 等特殊文件没有数据块,设备文件的主设备号和次设备号保存在 inode 中。

4.3.1、目录文件 data block

directory data block

由图可知,在目录文件的数据块中存储了其下的文件名、目录名、目录本身的相对名称 "." 和上级目录的相对名称 "..",还存储了这些文件名对应的 inode 号、目录项长度 rec_len、文件名长度 name_len 和文件类型 file_type。

注意到除了文件本身的 inode 记录了文件类型,其所在的目录的数据块也记录了文件类型。

由于 rec_len 只能是 4 的倍数,所以需要使用 "\0" 来填充 name_len 不够凑满 4 倍数的部分。至于 rec_len 具体是什么,只需知道它是一种偏移即可。

需要注意的是,inode table 中的 inode 自身并没有存储每个 inode 的 inode 号,它是存储在目录的 data block 中的,通过 inode 号可以计算并索引到 inode table 中该 inode 号对应的 inode 记录,可以认为这个 inode 号是一个 inode 指针。

  • 并非真的是指针,但有助于理解通过 inode 号索引找到对应 inode 的这个过程,后文将在需要的时候使用 inode 指针这个词来表示 inode 号。
  • 至此,已经知道了两种指针。
    • 一种是 inode table 中每个 inode 记录指向其对应 data block 的 block 指针
    • 一种是 inode 指针。

除了 inode 号,目录的 data block 中还使用数字格式记录了文件类型,数字格式和文件类型的对应关系如下

编码 文件类型
0 Unknown
1 Regular file
2 Directory
3 Character device
4 Block device
5 Named pipe
6 Socket
7 Symbolic link

4.3.2、通过 inode 号找到 inode

前面提到过,inode 结构自身并没有保存 inode 号,也没有保存文件名,那么 inode 号保存在哪里呢?

  • 答:目录的 data block 中保存了该目录中每个文件的 inode 号和文件名。

既然 inode 中没有 inode 号,那么如何根据目录 data block 中的 inode 号找到 inode table 中对应的 inode 呢?

  • 答:实际上,只要有了 inode 号,就可以计算出 inode 表中对应该 inode 号的 inode 结构。
    • 在创建文件系统的时候,每个块组中的起始 inode 号以及 inode table 的起始地址都已经确定了。
    • 所以只要知道 inode 号,就能知道这个 inode 号和该块组起始 inode 号的偏移数量。
    • 再根据每个 inode 结构的大小(256字节或其它大小),就能计算出来对应的 inode 结构。
    • 所以,目录的 data block 中的 inode number 和 inode table 中的 inode 是通过计算的方式一一映射起来的。
    • 从另一个角度上看,目录 data block 中的 inode number 是找到 inode table 中对应 inode 记录的唯一方式。

4.3.3、符号链接存储方式

符号链接即为软链接,类似于 Windows 操作系统中的快捷方式,它的作用是指向原文件或目录。

软链接一般情况下不占用 data block,仅仅通过它对应的 inode 记录就能将其信息描述完成。

root@arm64v8:~# ll /etc | grep "^l"
lrwxrwxrwx   1 root  root       19 9月  21  2020 mtab -> ../proc/self/mounts
lrwxrwxrwx   1 root  root       21 9月  21  2020 os-release -> ../usr/lib/os-release
lrwxrwxrwx   1 root  root       35 5月  21 07:25 resolv.conf -> /var/run/NetworkManager/resolv.conf
lrwxrwxrwx   1 root  root       23 9月  21  2020 vtrgb -> /etc/alternatives/vtrgb
root@arm64v8:~#
  • 符号链接的大小是其指向目标路径占用的字符个数。

    • 只有当符号链接指向的目标的路径名较长(60个字节)时,文件系统才会划分一个 data block 给它。
  • 符号链接权限为 777,它的权限如何也不重要,最终决定是否能读写执行的权限由原文件决定。

  • 软链接的 block 指针存储的是目标文件名,也就是说链接文件的一切都依赖于其目标文件名。

4.3.4、设备文件、FIFO、套接字文件

关于这 3 种文件类型的文件只需要通过 inode 就能完全保存它们的信息,它们不占用任何数据块,所以它们是特殊文件。

设备文件的主设备号和次设备号也保存在 inode 中。

root@arm64v8:~# ll /dev/ | grep "^c" | tail
crw-rw----   1 root tty       7, 132 5月  21 07:24 vcsa4
crw-rw----   1 root tty       7, 133 5月  21 07:24 vcsa5
crw-rw----   1 root tty       7, 134 5月  21 07:24 vcsa6
crw-rw----   1 root tty       7, 135 5月  21 07:24 vcsa7
crw-------   1 root root     10,  63 5月  21 07:24 vga_arbiter
crw-------   1 root root     10, 137 5月  21 07:24 vhci
crw-------   1 root root     10, 238 5月  21 07:24 vhost-net
crw-------   1 root root     10,  57 5月  21 07:24 vndbinder
crw-------   1 root root    248,   1 5月  21 07:24 vport2p1
crw-rw-rw-   1 root root      1,   5 5月  21 07:24 zero
root@arm64v8:~# 
  • 第 5 列和第 6 列分别是主设备号和次设备号。
    • 主设备号标识每一种设备的类型;
    • 次设备号标识同种设备类型的不同编号;
  • 这些信息中没有大小的信息,因为设备文件不占用数据块所以没有大小的概念。

4.4、inode 机制

每个文件都有一个 inode,在将 inode 关联到文件后系统将通过 inode 号来识别文件,而不是文件名。

并且访问文件时将先找到 inode,通过 inode 中记录的 block 位置找到该文件。

4.4.1、硬链接

inode 相同的文件在 Linux 中被称为 "硬链接"。

  • inode 号、元数据、block 位置都相同。
  • 使用的都是同一条 inode 记录,所以代表的都是同一个文件。
  • 这些文件所在目录的 data block 中的 inode 号都是一样的,只不过各 inode 号对应的文件名互不相同

每个文件都有一个 "硬链接数" 的属性,使用 ls -l 的第二列就是被硬链接数,它表示的就是该文件有几个硬链接。

# 查看硬链接
root@arm64v8:~# ls -l /etc/app* -d
drwxr-xr-x 3 root root 4096 9月   1  2020 /etc/apparmor/
drwxr-xr-x 9 root root 4096 9月   1  2020 /etc/apparmor.d/
drwxr-xr-x 4 root root 4096 9月   1  2020 /etc/apport/
root@arm64v8:~#

# 目录不能创建硬链接
[root@aarch64 tmp]# mkdir dir_a
[root@aarch64 tmp]# ln -v dir_a/ dir_b
ln: ‘dir_a/’: hard link not allowed for directory   
[root@aarch64 tmp]# ls
dir_a
[root@aarch64 tmp]#

# 文件创建硬链接示例
[root@aarch64 tmp]# touch file_a
[root@aarch64 tmp]# ll file_a 
-rw-r--r-- 1 root root 0 Oct 31 22:35 file_a
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ln -v file_a file_b
‘file_b’ => ‘file_a’
[root@aarch64 tmp]# ll
total 4
drwxr-xr-x 2 root root 4096 Oct 31 22:33 dir_a
-rw-r--r-- 2 root root    0 Oct 31 22:35 file_a
-rw-r--r-- 2 root root    0 Oct 31 22:35 file_b
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ln -v file_a file_c
‘file_c’ => ‘file_a’
[root@aarch64 tmp]# ln -v file_a file_d
‘file_d’ => ‘file_a’
[root@aarch64 tmp]# ln -v file_a file_e
‘file_e’ => ‘file_a’
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ll
total 4
drwxr-xr-x 2 root root 4096 Oct 31 22:33 dir_a
-rw-r--r-- 5 root root    0 Oct 31 22:35 file_a
-rw-r--r-- 5 root root    0 Oct 31 22:35 file_b
-rw-r--r-- 5 root root    0 Oct 31 22:35 file_c
-rw-r--r-- 5 root root    0 Oct 31 22:35 file_d
-rw-r--r-- 5 root root    0 Oct 31 22:35 file_e
[root@aarch64 tmp]#

每创建一个文件的硬链接,实质上是多一个指向该 inode 记录的 inode 指针,并且硬链接数加 1。

删除文件的实质是删除该文件所在目录 data block 中的对应的 inode 行,所以也是减少硬链接次数。

  • block 指针是存储在 inode 中的,所以不是真的删除数据。
  • 如果仍有其他 inode 号链接到该 inode,那么该文件的 block 指针仍然是可用的。
  • 当硬链接次数为 1 时再删除文件就是真的删除文件了,此时 inode 记录中 block 指针也将被删除。

不能跨分区创建硬链接,因为不同文件系统的inode号可能会相同,冲突。

硬链接只能对文件创建,无法对目录创建硬链接。

  • 因为文件系统已经把每个目录的硬链接创建好了,它们就是相对路径中的 "." 和 "..",分别标识当前目录的硬链接和上级目录的硬链接。
  • 每一个目录中都会包含这两个硬链接,它包含了两个信息。
    1. 一个没有子目录的目录文件的硬链接数是 2,其一是目录本身,即该目录 datablock 中的 ".",其二是其父目录 datablock 中该目录的记录,这两者都指向同一个 inode 号;
    2. 一个包含子目录的目录文件,其硬链接数是 2+ 子目录数,因为每个子目录都关联一个父目录的硬链接 ".."。
      • 很多人在计算目录的硬链接数时认为由于包含了 "." 和 "..",所以空目录的硬链接数是 2,这是错误的,因为 ".." 不是本目录的硬链接。
      • 另外,还有一个特殊的目录应该纳入考虑,即 "/" 目录,它自身是一个文件系统的入口,是自引用的,所以 "/" 目录下的 "." 和 ".." 的 inode 号相同,它自身不占用硬链接。

4.4.2、软链接

软链接就是字符链接,链接文件默认指的就是字符链接文件(注意不是字符设备),使用 "l" 表示其类型。

硬链接不能跨文件系统创建,否则 inode 号可能会冲突。于是实现了软链接以便跨文件系统建立链接。既然是跨文件系统,那么软链接必须得有自己的 inode 号。

软链接指向原文件,原文件损坏或消失,软链接文件就损坏。可以认为软链接 inode 记录中的指针内容是目标路径的字符串。

ln 命令

ln [options] source [dest]
ln [options] source...directory

选项说明:
-s, --symbolic  # 建立符号连接以替代硬连接,没有指定此选项就是硬链接
-f, --force     # 删除已存在的目的文件。
-n              # 把符号链接视为一般目录
-v, --verbose   # 在建立连接前显示所操作的文件名

root@arm64v8:~# ln -sv -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 
'/etc/localtime' -> '/usr/share/zoneinfo/Asia/Shanghai'
root@arm64v8:~#

# 好好感受一下这个实验,生产环境遇到的
[root@aarch64 tmp]# mkdir dir_a dir_b dir_c dir_d   # 创建四个目录
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ls                              # 查看已有这四个目录
dir_a  dir_b  dir_c  dir_d
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ln -svf dir_b dir_a             # 我想让dir_a作为软链接指向dir_b
‘dir_a/dir_b’ -> ‘dir_b’                         # 很显然不是我想要的,因为dir_a是个已存在目录,只能在dir_a中建立一个与dir_b同名的软链接
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ln -svnf dir_b dir_c            # 加 -n 选项也不行,因为dir_c是个已存在目录,不是软链接
‘dir_c/dir_b’ -> ‘dir_b’
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ln -sv dir_b dir_new            # dir_new不存在,正好创建dir_new软链接
‘dir_new’ -> ‘dir_b’
[root@aarch64 tmp]# ll
total 16
drwxr-xr-x 2 root root 4096 Oct 31 22:41 dir_a
drwxr-xr-x 2 root root 4096 Oct 31 22:40 dir_b
drwxr-xr-x 2 root root 4096 Oct 31 22:42 dir_c
drwxr-xr-x 2 root root 4096 Oct 31 22:40 dir_d
lrwxrwxrwx 1 root root    5 Oct 31 22:43 dir_new -> dir_b
[root@aarch64 tmp]# 
[root@aarch64 tmp]# ln -snvf dir_d dir_new          # dir_new已存在,但它是一个软链接,不是真目录,所以可以使用-n选项替换掉目标目录
‘dir_new’ -> ‘dir_d’
[root@aarch64 tmp]#

4.4.3、ext 文件系统预留的 inode 号

Ext 预留了一些 inode 做特殊特性使用,如下:某些可能并非总是准确,具体的inode号对应什么文件可以使用 "find / -inum NUM" 查看。

Inode 号 用途
0 不存在 0 号 inode,可用于标识目录 data block 中已删除的文件
1 虚拟文件系统,如 /proc 和 /sys
2 根目录
3 ACL 索引
4 ACL 数据
5 Boot loader
6 未删除的目录
7 预留的块组描述符 inode
8 日志 inode
11 第一个非预留的 inode,通常是 lost+found 目录

在 ext4 文件系统的 dumpe2fs 信息中,能观察到 fisrt inode 号可能为 11 也可能为 12。

并且注意到 "/" 的 inode 号为 2,这个特性在文件访问时会用上。

需要注意的是,每个文件系统都会分配自己的 inode 号,不同文件系统之间是可能会出现使用相同 inode 号文件的。例如:

root@arm64v8:~# find / -ignore_readdir_race -inum 2 -ls
        2      4 drwxr-xr-x  22 root     root         4096 9月 21  2020 /
        2      4 drwxr-xr-x   6 root     root         4096 9月 21  2020 /boot
        2      0 -rw-r--r--   1 root     root            0 5月 21 07:24 /proc/sys/fs/binfmt_misc/status
        2      0 c---------   1 root     root       5,   2 1月  1  1970 /dev/pts/ptmx
        2      0 drwxr-xr-x   8 root     root            0 6月  9 11:10 /sys/fs
        2      0 -rw-r--r--   1 root     root            0 5月 21 07:24 /sys/fs/cgroup/memory/cgroup.procs
        2      0 -rw-r--r--   1 root     root            0 6月  9 11:10 /sys/fs/cgroup/debug/cgroup.procs
  • 除了根的 Inode 号为 2,还有几个文件的 inode 号也是 2,它们都属于独立的文件系统,有些是虚拟文件系统,如 /proc 和 /sys。

4.4.4、inode 寻址

前文说过,inode 中保存了 blocks 指针,但是一条 inode 记录中能保存的指针数量是有限的,否则就会超出 inode 大小(128字节或256字节)。

在 ext2 和 ext3 文件系统中,一个 inode 中最多只能有 15 个指针,每个指针使用 i_block[n] 表示。

block pointer adressing

  • 前 12 个指针 i_block[0] 到 i_block[11] 是直接寻址指针,每个指针指向一个数据区的 block。
  • 第 13 个指针 i_block[12] 是一级间接寻址指针,它指向一个仍然存储了指针的 block 即 i_block[12] --> Pointerblock --> datablock。
  • 第 14 个指针 i_block[13] 是二级间接寻址指针,它指向一个仍然存储了指针的 block,但是这个 block 中的指针还继续指向其他存储指针的 block,即 i_block[13] --> Pointerblock1 --> PointerBlock2 --> datablock。
  • 第 15 个指针 i_block[14] 是三级间接寻址指针,它指向一个任然存储了指针的 block,这个指针 block 下还有两次指针指向。即 i_block[13] --> Pointerblock1 --> PointerBlock2 --> PointerBlock3 --> datablock。

其中由于每个指针大小为 4 字节,所以每个指针 block 能存放的指针数量为 BlockSize/4byte。例如 blocksize 为 4KB,那么一个 Block 可以存放 4096/4=1024 个指针。

为什么要分间接和直接指针呢?

  • 如果一个 inode 中 15 个指针全是直接指针,假如每个 block 的大小为 1KB,那么 15 个指针只能指向 15 个 block 即 15KB 的大小,由于每个文件对应一个 inode 号,所以就限制了每个文件最大为 15 * 1 = 15KB,这显然是不合理的。
  • 如果存储大于 15KB 的文件而又不太大的时候,就占用一级间接指针 i_block[12],这时可以存放指针数量为 1024/4+12=268,所以能存放 268KB 的文件。
  • 如果存储大于 268K 的文件而又不太大的时候,就继续占用二级指针 i_block[13],这时可以存放指针数量为 [1024/4]^2+1024/4+12=65804,所以能存放 65804KB=64M 左右的文件。
  • 如果存放的文件大于 64M,那么就继续使用三级间接指针 i_block[14],存放的指针数量为 [1024/4]^3+[1024/4]^2+[1024/4]+12=16843020 个指针,所以能存放 16843020KB=16GB 左右的文件。
  • 如果 blocksize=4KB 呢?那么最大能存放的文件大小为 ([4096/4]^3+[4096/4]^2+[4096/4]+12)*4/1024/1024/1024=4T 左右。

ext2 和 ext3 对超大文件的存取效率是低下的,ext4 针对这一点就进行了优化,ext4 使用 extent 的管理方式取代 ext2 和 ext3 的块映射,大大提高了效率也降低了碎片。

4.5、单文件系统文件操作机制

在 Linux 上执行删除、复制、重命名、移动等操作时,它们是怎么进行的呢?还有访问文件时是如何找到它的呢?

其实只要理解了前文中介绍的几个术语以及它们的作用就很容易知道文件操作的原理了。

4.5.1、读文件

当执行 "cat /var/log/messages" 命令在系统内部进行了什么样的步骤呢?

  1. 找到根文件系统的块组描述符表所在的 blocks,读取 GDT(已在内存中) 找到 inode table 的 block号。
    • 因为 GDT 总是和 superblock 在同一个块组,而 superblock 总是在分区的第 1024-2047 个字节,所以很容易就知道第一个 GDT 所在的块组以及 GDT 在这个块组中占用了哪些 block。
    • 其实 GDT 早已经在内存中了,在系统开机的时候会挂载根文件系统,挂载的时候就已经将所有的 GDT 放进内存中。
  2. 在 inode table 的 block 中定位到根 "/" 的 inode,找出 "/" 指向的 data block。
    • 前文说过,ext 文件系统预留了一些 inode 号,其中 "/" 的 inode 号为 2,所以可以根据 inode 号直接定位根目录文件的 data block。
  3. 在 "/" 的 datablock 中记录了 var 目录名和 var 的 inode 号,找到该 inode 记录,inode 记录中存储了指向 var 的 block 指针,所以也就找到了 var 目录文件的 data block。
    • 通过 var 目录的 inode 号,可以寻找到 var 目录的 inode 记录,但是在寻找的过程中,还需要知道该 inode 记录所在的块组以及所在的 inode table,所以需要读取 GDT,同样,GDT 已经缓存到了内存中。
  4. 在 var 的 data block 中记录了 log 目录名和其 inode 号,通过该 inode 号定位到该 inode 所在的块组及所在的 inode table,并根据该 inode 记录找到 log 的 data block。
  5. 在 log 目录文件的 data block 中记录了 messages 文件名和对应的 inode 号,通过该 inode 号定位到该 inode 所在的块组及所在的 inode table,并根据该 inode 记录找到 messages 的 data block。
  6. 最后读取 messages 对应的 datablock。

注意:该命令能被成功执行涉及了 cat 命令的寻找、权限判断以及 messages 文件的寻找和权限判断等等复杂的过程。上面只是介绍怎么找到 message 文件这一步。

4.5.2、删除、重命名和移动文件

删除文件分为普通文件和目录文件,知道了这两种类型的文件的删除原理,就知道了其他类型特殊文件的删除方法。

  • 删除普通文件
    1. 找到文件的 inode 和 data block(根据前一个小节中的方法寻找);
    2. 将 inode table 中该 inode 记录中的 data block 指针删除;
    3. 在 imap 中将该文件的 inode 号标记为未使用;
    4. 在其所在目录的 data block 中将该文件名所在的记录行删除,删除了记录就丢失了指向 inode 的指针;
      • 实际上不是真的删除,直接删除的话会在目录 data block 的数据结构中产生空洞,所以实际的操作是将待删除文件的 inode 号设置为特殊的值 0,这样下次新建文件时就可以重用该行记录。
    5. 将 bmap 中 data block 对应的 block 号标记为未使用。
  • 删除目录文件
    1. 找到目录和目录下所有文件、子目录、子文件的 inode 和 data block;
    2. 在 imap 中将这些 inode 号标记为未使用;
    3. 将 bmap 中将这些文件占用的 block号标记为未使用;
    4. 在该目录的父目录的 data block 中将该目录名所在的记录行删除。
      • 需要注意的是,删除父目录 data block 中的记录是最后一步,如果该步骤提前,将报目录非空的错误,因为在该目录中还有文件占用。

重命名文件分为同目录内重命名和非同目录内重命名。非同目录内重命名实际上是移动文件的过程。

  • 同目录内重命名文件的动作仅仅只是修改所在目录 data block 中该文件记录的文件名部分,不是删除再重建的过程。
  • 如果重命名时有文件名冲突(该目录内已经存在该文件名),则提示是否覆盖。覆盖的过程是覆盖目录 data block 中冲突文件的记录。

移动文件

  • 同文件系统下移动文件实际上是修改目标文件所在目录的 data block,向其中添加一行指向 inode table 中待移动文件的 inode 指针;
  • 如果目标路径下有同名文件,则会提示是否覆盖,实际上是覆盖目录 data block 中冲突文件的记录,由于同名文件的 inode 记录指针被覆盖,所以无法再找到该文件的 data block,也就是说该文件被标记为删除。
  • 所以在同文件系统内移动文件相当快,仅仅在所在目录 data block 中添加或覆盖了一条记录而已。也因此,移动文件时,文件的 inode 号是不会改变的。

4.5.3、存储文件

  1. 读取 GDT,找到各个(或部分)块组 imap 中未使用的 inode 号,并为待存储文件分配 inode 号;
  2. 在 inode table 中完善该 inode 号所在行的记录;
  3. 在目录的 data block 中添加一条该文件的相关记录;
  4. 将数据填充到 data block 中。
    • 注意,填充到 data block 中的时候会调用 block 分配器:一次分配 4KB 大小的 block 数量,当填充完 4KB 的 data block 后会继续调用 block 分配器分配 4KB 的 block,然后循环直到填充完所有数据。也就是说,如果存储一个 100M 的文件需要调用 block 分配器 100*1024/4=25600 次。
    • 另一方面,在 block 分配器分配 block 时,block 分配器并不知道真正有多少 block 要分配,只是每次需要分配时就分配,在每存储一个 data block 前,就去 bmap 中标记一次该 block 已使用,它无法实现一次标记多个 bmap 位。这一点在 ext4 中进行了优化。
  5. 填充完之后,去 inode table 中更新该文件 inode 记录中指向 data block 的寻址指针。

4.6、多文件系统关联

在单个文件系统中的文件操作和多文件系统中的操作有所不同。本文将对此做出非常详细的说明。

4.6.1、根文件系统的特殊性

首先明确的是,任何一个文件系统要在 Linux 上能正常使用,必须挂载在某个已经挂载好的文件系统中的某个目录下,例如 /dev/cdrom 挂载在 /mnt 上,/mnt 目录本身是在 "/" 文件系统下的。

而且任意文件系统的一级挂载点必须是在根文件系统的某个目录下,因为只有 "/" 是自引用的。这里要说明挂载点的级别和自引用的概念。

  • 挂载点的级别

    • 假如 /dev/sdb1 挂载在 /mydata 上,/dev/cdrom 挂载在 /mydata/cdrom 上,那么 /mydata 就是一级挂载点。
    • 此时 /mydata 已经是文件系统 /dev/sdb1 的入口了,而 /dev/cdrom 所挂载的目录 /mydata/cdrom 是文件系统 /dev/sdb1 中的某个目录,那么 /mydata/cdrom 就是二级挂载点。
    • 所以可简述为,文件系统 2 挂载在文件系统 1 中的某个目录下,而文件系统 1 又挂载在根文件系统中的某个目录下。
  • 自引用的概念

    • 首先要说的是,自引用的只能是文件系统,而文件系统表现形式是一个目录。

    • 自引用是指该目录的 data block 中,"." 和 ".." 的记录中的 inode 号都对应 inode table 中同一个 inode 记录,所以它们 inode 号是相同的,即互为硬链接。

    • 根文件系统是唯一可以自引用的文件系统。

    root@arm64v8:~# ll -ai /
    总用量 524
          2 drwxr-xr-x  22 root root   4096 9月  21  2020 ./
          2 drwxr-xr-x  22 root root   4096 9月  21  2020 ../
    
    # cd /.和cd /..的结果都还是在根下,这是自引用最直接的表现形式。
    root@arm64v8:~# cd /.
    root@arm64v8:/# 
    root@arm64v8:/# cd /..
    root@arm64v8:/#

    注意:根目录下的 "." 和 ".." 都是 "/" 目录的硬链接,且其 datablock 中不记录名为 "/" 的条目,因此除去根目录下子目录数后的硬链接数为 2。

4.6.2、挂载文件系统过程

挂载文件系统到某个目录下,例如 "mount /dev/vdb2 /mnt/",挂载成功后 /mnt 目录中的文件全都暂时不可见了,且挂载后权限和所有者(如果指定允许普通用户挂载)等都改变了。

下图是原始的文件系统结构,/mnt 是根文件系统中的一个目录,"/" 的 data block 中记录了 /mnt 的一些信息,其中包括 inode 号 n(inode指针),而在 inode table 中,/mnt 对应的 inode 记录中又存储了 block 指针 Block_n,此时这两个指针还是普通的指针。

mount

当文件系统 /dev/vdb2 挂载到 /mnt 上后,/mnt 此时就已经成为另一个文件系统的入口了,因此它需要连接两边文件系统的 inode 和 data block。但是如何连接呢?如下图。

mount1

  1. 在根文件系统的 inode table 中,为 /mnt 重新分配一个 inode 记录 m,该记录的 block 指针 block_m 指向文件系统 /dev/vdb2 中的 data block。
  2. 既然为 /mnt 分配了新的 inode 记录 m,那么在 "/" 目录的 data block 中,也需要修改其 inode 指针为 m 以指向 m 记录。同时,原来 inode table 中的 inode 记录 n 就被标记为暂时不可用(转成黑色表示)。
  3. Block_m 指向的是文件系统 /dev/vdb2 的 data block,所以严格说起来,除了 /mnt 的元数据信息即 inode 记录 m 还在根文件系统上,/mnt 的 data block 已经是在 /dev/vdb2 中的了。
  4. 这就是挂载新文件系统后实现的跨文件系统,它将挂载点的元数据信息和数据信息分别存储在不同的文件系统上。
  5. 挂载完成后,将在 /proc/self/{mounts,mountstats,mountinfo} 这三个文件中写入挂载记录和相关的挂载信息,并会将 /proc/self/mounts 中的信息同步到 /etc/mtab 文件中,当然,如果挂载时加了 -n 参数,将不会同步到 /etc/mtab。
  6. 而卸载文件系统,其实质是移除临时新建的 inode 记录(当然,在移除前会检查是否正在使用)及其指针,并将指针指回原来的 inode 记录,这样 inode 记录中的 block 指针也就同时生效而找回对应的 data block 了。
# 挂载前后/mnt的inode号会变
root@arm64v8:~# ll -id /mnt/
1700609 drwxr-xr-x 5 root root 4096 6月   9 15:10 /mnt//
root@arm64v8:~# 
root@arm64v8:~# mount /dev/vdb2 /mnt/
root@arm64v8:~# 
root@arm64v8:~# ll -id /mnt/
2 drwxr-xr-x 3 root root 4096 6月   2 16:09 /mnt//
root@arm64v8:~# 

# 挂载后/mnt里面原来的文件不可见
root@arm64v8:~# ll /mnt/
总用量 12
drwxr-xr-x  2 root root 4096 6月   9 21:02 ./
drwxr-xr-x 22 root root 4096 9月  21  2020 ../
-rw-r--r--  1 root root  187 6月   9 21:01 wpplog
-rw-r--r--  1 root root    0 6月   9 21:01 wpsd1000.log
srwxr-xr-x  1 root root    0 6月   9 21:01 wps_startup_ipcsvr=
root@arm64v8:~# 
root@arm64v8:~# mount /dev/vdb2 /mnt/
root@arm64v8:~# 
root@arm64v8:~# ll /mnt/
总用量 24
drwxr-xr-x  3 root root  4096 6月   2 16:09 ./
drwxr-xr-x 22 root root  4096 9月  21  2020 ../
drwx------  2 root root 16384 6月   2 16:09 lost+found/
root@arm64v8:~# 
  • 挂载文件系统后,挂载点原来的 inode 记录暂时被标记为不可用,关键是没有指向该 inode 记录的 inode 指针了,所以原文件不可见。
  • 在卸载文件系统后,又重新启用挂载点原来的 inode 记录,"/" 目录下的 mnt 的 inode 指针又重新指向该 inode 记录,原文件恢复可见。
  • 挂载后,挂载点的元数据和 data block 是分别存放在不同文件系统上的。
  • 挂载点即使在挂载后,也还是属于原文件系统的文件。

4.7、ext3 文件系统的日志功能

ext2 文件系统中,只有两个区,数据区和元数据区。

  • 如果正在向 data block 中填充数据时突然断电,那么下一次启动时就会检查文件系统中数据和状态的一致性,这段检查和修复可能会消耗大量时间,甚至检查后无法修复。

ext3 文件系统中,划分三个区,数据区、日志区和元数据区。

  • 每次存储数据时,先在日志区中进行 ext2 中元数据区的活动,直到文件存储完成后标记上 commit 才将日志区中的数据转存到元数据区。
  • 当存储文件时突然断电,下一次检查修复文件系统时,只需要检查日志区的记录,将 bmap 对应的 data block 标记为未使用,并把 inode 号标记未使用,这样就不需要扫描整个文件系统而耗费大量时间。

虽说 ext3 相比 ext2 多了一个日志区转写元数据区的动作而导致 ext3 相比 ext2 性能要差一点,特别是写众多小文件时。但是由于 ext3 其他方面的优化使得 ext3 和 ext2 性能几乎没有差距。

4.8、ext4 文件系统改进

Ext4 文件系统文件数据管理参考了现代文件系统的实现方式,也即 extent 方式。如下图所示,其数据管理的入口仍然是 inode 节点的 i_block 成员。

差异是此时 i_block 并非一个 32 位整数数组,而是一个描述 B 树结构的数据结构(包含 ext4_extent_header 和 ext4_extent_idx)。

在该数据结构中,只有叶子节点中存储的数据包含文件逻辑地址与磁盘物理地址的映射关系。

ext4

在数据管理中有 3 个关键的数据结构,分别是 ext4_extent_header、ext4_extent_idx 和 ext4_extent。

ext4_extent_header

该数据结构在一个磁盘逻辑块的最开始的位置,描述该磁盘逻辑块的 B 树属性,也即该逻辑块中数据的类型(例如是否为叶子节点)和数量。

如果 eh_depth 为 0,则该逻辑块中数据项为B树的叶子节点,此时其中存储的是 ext4_extent 数据结构实例。

如果 eh_depth>0,则其中存储的是非叶子节点,也即 ext4_extent_idx,用于存储指向下一级的索引。

struct ext4_extent_header {
        __le16  eh_magic;       /* 魔数 */
        __le16  eh_entries;     /* 可用的项目的数量 */
        __le16  eh_max;         /* 本区域可以存储最大项目数量 */
        __le16  eh_depth;       /* 当前层树的深度 */
        __le32  eh_generation;  
};

ext4_extent_idx

该数据结构是 B 树中的索引节点,该数据结构用于指向下一级,下一级可以仍然是索引节点,或者叶子节点。

struct ext4_extent_idx {
        __le32  ei_block;       /* 索引覆盖的逻辑块的数量,以块为单位 */
        __le32  ei_leaf_lo;     /* 指向下一级物理块的位置,*/
        __le16  ei_leaf_hi;     /* 物理块位置的高16位 */
        __u16   ei_unused;
};

ext4_extent

描述了文件逻辑地址与磁盘物理地址的关系。通过该数据结构,可以找到文件某个偏移的一段数据在磁盘的具体位置。

struct ext4_extent {
        __le32  ee_block;       /* 该extent覆盖的第一个逻辑地址,以块为单位 */       
        __le16  ee_len;         /* 该extent覆盖的逻辑块的位置 */  
        __le16  ee_start_hi;    /* 物理块的高16位 */ 
        __le32  ee_start_lo;    /* 物理块的低16位 */         
};

4.9、ext 文件系统不足

最大的缺点是它在创建文件系统的时候就划分好一切需要划分的东西,以后用到的时候可以直接进行分配,也就是说它不支持动态划分和动态分配。

对于较小的分区来说速度还好,但是对于一个超大的磁盘,速度很慢。

除了格式化速度超慢以外,ext4 文件系统还是非常可取的。当然,不同公司开发的文件系统都各有特色,最主要的还是根据需求选择合适的文件系统类型。

标签云