前言

本篇将对 Ext3 文件系统的 i-节点、目录项进行分析。

知识点总览

  • Ext3 文件系统 i-节点分析

  • Ext3 文件系统目录项分析

Ext3 文件系统 i-节点分析

The inode bitmap is used to provide an overview of the used and free inodes of a group. As usual, each
inode is represented as ‘‘used‘‘ or ‘‘free‘‘ by means of a single bit. The inode data are stored in the inode
table with the help of a large number of sequential inode structures.

Professional Linux® Kernel Architecture --Wolfgang Mauerer

Ext3 文件系统的 i-节点用来存储跟文件相关的除文件名以外的所有信息,i-节点被存储在 i-节点表中,i-节点表的位置在 i-节点位图后。

i-节点存储的信息包括指向文件的链接数文件大小文件的时间信息文件所属的用户 ID文件所属的组 ID文件内容存放地址的块指针等,这些重要信息也被称为元数据

i-节点的结构特点如下:

  1. Ext3 文件系统的每个块组中都有一个自己的 i-节点表。i-节点表由很多 i-节点组成,每个文件或者目录使用一个 i-节点

  2. i-节点表起始于 i-节点位图所在块的下一个块。

  1. 超级块中记录着文件系统的 i-节点总数和每块组包含的 i-节点数。

字节偏移
字段长度
(字节)
字段名和定义
0x00 ~ 0x03 4 i-节点总数
0x28 ~ 0x2B 4 每块组包含的 i-节点数
0x58 ~ 0x59 2 i-节点大小
  1. 每个 i-节点的基本大小为 128 字节,但当超级块中 4CH ~ 4FH 偏移处的“主版本号”被定义为“动态”时,i-节点的实际大小由超级块的 58H ~ 59H 处定义,i-节点可以被定义为大于 128 字节的“大 i-节点”。

The default inode size for ext3 filesystems are 128 bytes. A larger inode size can be used to store extended attributes, which can speed certain workloads that use a large number of extended attributes, such as SE Linux or Sambav4. In addition, at least 256 bytes of inodes is required for some ext4 features.

Considerations when creating ext3 filesystems -- Kernel.org

在 Ext3 文件系统中默认的 i-节点大小为 128 字节,较大的 i-节点大小可用于存储扩展属性,则可以加快某些大量扩展属性的工作负载(例如 SE Linux 或 Samba v4)。此外,至少需要 256 字节的 i-节点才能使用一些 Ext4 文件系统中的特性。

现在创建 Ext3 文件系统时,通常会使用 256 字节的 i-节点大小,以便在将来使用 Ext4 文件系统时,可以直接使用 256 字节的 i-节点大小,而不需要重新创建文件系统。

  1. 每个 i-节点都有一个编号,第一个 i-节点的编号为 1。

  2. 1 ~ 10 号 i-节点被系统保留,所以在超级块中会描述第一个非保留的 i-节点,这个值一般是 11。

  3. 前 10 个保留的 i-节点在 i-节点位图中被标示为已分配,其中 1 号 i-节点一般用于描述坏块;2 号 i-节点被分配给根目录使用;8 号 i-节点通常用于描述日志。

  4. 如果已知一个 i-节点号,就可以计算出该 i-节点所在块组,计算方法为 (i-节点号 −1)/ 每块组 i-节点数

Ext3 文件系统的 i-节点结构分析

字节偏移 字段长度 (字节) 字段名和定义
0x00~0x01 2 文件模式
0x02~0x03 2 用户 UID 的低 16 位
0x04~0x07 4 字节数的低 32 位
0x08~0x0B 4 最后访问时间
0x0C~0x0F 4 i-节点变化时间
0x10~0x13 4 最后修改时间
0x14~0x17 4 删除时间
0x18~0x19 2 组 ID 的低 16 位
0x1A~0x1B 2 硬链接数
0x1C~0x1F 4 文件所占扇区数(512 字节/块)
0x20~0x23 4 Flag
0x24~0x27 4 未用
0x28~0x2B 4 第 1 个直接块指针
0x2C~0x2F 4 第 2 个直接块指针
0x30~0x33 4 第 3 个直接块指针
0x34~0x37 4 第 4 个直接块指针
0x38~0x3B 4 第 5 个直接块指针
0x3C~0x3F 4 第 6 个直接块指针
0x40~0x43 4 第 7 个直接块指针
0x44~0x47 4 第 8 个直接块指针
0x48~0x4B 4 第 9 个直接块指针
0x4C~0x4F 4 第 10 个直接块指针
0x50~0x53 4 第 11 个直接块指针
0x54~0x57 4 第 12 个直接块指针
0x58~0x5B 4 间接块指针
0x5C~0x5F 4 二级间接块指针
0x60~0x63 4 三级间接块指针
0x64~0x67 4 文件版本号(用于 NFS)
0x68~0x6B + 扩展属性块的低 32 位
0x6C~0x6F 4 字节数的高 32 位
0x70~0x73 4 片段的数据块地址
0x74 1 片段索引
0x75 1 段大小
0x76~0x77 2 未用
0x78~0x79 2 UID 的高 16 位
0x7A~0x7B 2 GID 的高 16 位
0x7C~0x7F 4 未用

以下为额外属性

字节偏移 字段长度 (字节) 字段名和定义
0x80~0xFF 128 额外属性
0x80~0x81 2 该段属性的大小
0x82~0x83 2 高 16 位的 i-节点校验和
0x84~0x87 4 i-节点变化时间(次秒级精度)
0x88~0x8B 4 最后修改时间(次秒级精度)
0x8C~0x8F 4 最后访问时间(次秒级精度)
0x90~0x93 4 文件的创建时间
0x94~0x97 4 创建时间的次秒精度
0x98~0x9B 4 版本号的高 32 位
0x9C~0x9F 4 项目编号
0xA0~0xFF 96 未用(其他额外属性可用)

Ext3 Inode 的结构具体解析

  1. 0x00~0x01: 文件模式

    展开/收起 文件模式(i_mode)的解析

    “文件模式”参数占用两个字节,按照其对应的 16 位二进制分成三组:

    • 第一组为 0 ~ 8 位,描述权限标志

      16 位的“文件模式”参数中 0 ~ 8 位用来描述用户的权限。

      Linux 的用户被分成三类,即属主、同组用户和其他用户

      属主是指 i-节点中记录的用户 ID 的所有者;

      组用户以 i-节点中的组 ID 进行区分;

      其他用户为除属主、同组用户以外的所有用户。

      用户的权限分为读、写和执行三种

    • 第二组为 9 ~ 11 位,用来定义可执行文件和目录

      16 位的“文件模式”参数中 9 ~ 11 位用来定义可执行文件和目录。

      这三个位设置的值不同:

      如果是可执行文件,其执行方式就会有所不同;

      如果是目录,则该目录中的文件会具有特定的属性

    • 第三组为 12 ~ 15 位,描述文件类型。

      16 位的“文件模式”参数中 12 ~ 15 位用来定义文件类型

      文件类型的各种值只能单独使用(互斥),不能同时将几个值组合起来使用。

      以下为文件模式(i_mode)值的组合:

      | 值 | 描述 |
      | ——— | ———————————————- |
      | 0x1 | S_IXOTH(其他人可执行) |
      | 0x2 | S_IWOTH(其他人可写) |
      | 0x4 | S_IROTH(其他人可读) |
      | 0x8 | S_IXGRP(组成员可执行) |
      | 0x10 | S_IWGRP(组成员可写) |
      | 0x20 | S_IRGRP(组成员可读) |
      | 0x40 | S_IXUSR(所有者可执行) |
      | 0x80 | S_IWUSR(所有者可写) |
      | 0x100 | S_IRUSR(所有者可读) |
      | 0x200 | S_ISVTX(粘接位) |
      | 0x400 | S_ISGID(设置 GID) |
      | 0x800 | S_ISUID(设置 UID) |
      | | 这些是互斥的文件类型: |
      | 0x1000 | S_IFIFO(先进先出(管道)) |
      | 0x2000 | S_IFCHR(字符设备(裸设备)) |
      | 0x4000 | S_IFDIR(目录) |
      | 0x6000 | S_IFBLK(块设备) |
      | 0x8000 | S_IFREG(正常文件) |
      | 0xA000 | S_IFLNK(符号链接) |
      | 0xC000 | S_IFSOCK(套接字) |

      “先进先出”也称为管道,用于两个进程间的数据传送。一个进程可以打开文件接收信息,但这些信息必须是由另一个进程写入的,在这个过程中数据存储在内存中而不是在磁盘上。

      “字符设备”也称为裸设备,用于不需要一次读取一个数据块的设备,如键盘。也可以给块设备设置字符设备属性,但在不以块大小为读取和写入单位时会产生错误。i-节点通常会保存某些信息用以说明哪些块已经分配给记录设备标识符的文件。

      “块设备”用于只能以块大小为单位进行读取操作的设备,如硬盘。从硬盘读取数据时,一次至少要读取 512 个字节,即 1 个扇区。如果一个程序要从块设备中读取少于 1 个扇区的数据,操作系统也需要读取 1 个扇区,然后只把需要的数据返回给程序。

      “动态链接”是一种指向其他文件或目录的特殊文件,它的“内容”是所指向的目标文件的内容。动态链接其实就是文件或目录的快捷方式。

      “套接字”用于进程间的双向通信,如命名管道,这些数据并不写入磁盘。

  2. 0x02~0x03: 用户 UID 的低 16 位

  3. 0x04~0x07: 字节数的低 32 位

    如果该 i-节点是一个文件的 i-节点,则“总字节数”是指文件的大小;如果该 i-节点是一个目录的 i-节点,则“总字节数”是指该目录的下级目录区的大小。

    在 Ext 及 Ext2 文件系统中,用 32 位来描述“总字节数”这个参数,能够管理的文件大小最大为 4GB;从 Ext3 开始,文件系统能够支持大文件,也就是用 64 位存储“总字节数”这个参数,该位置为低 32 位,高 32 位在 0x6C ~ 0x6F 偏移处描述。

  4. 0x08~0x0B: 最后访问时间

  5. 0x0C~0x0F: i-节点变化时间

  6. 0x10~0x13: 最后修改时间

  7. 0x14~0x17: 删除时间

    Ext3 的 i-节点中描述了四个时间信息,包括最后访问时间、i-节点变化时间、最后修改时间及删除时间。

    注意:这些时间信息是从 1970 年 1 月 1 日 0 时算起,并且使用格林威治标准时间,能够描述的时间上限为 2038 年 1 月 18 日。

    故在现版本的 Linux 系统中格式化的 Ext3 文件系统的 i-节点大小通常大于 128 字节,在多分配的部分中会有额外属性,额外属性中通常会包含对时间信息的补充数据。

    提示:当新建立一个文件时,最后访问时间、i-节点变化时间、最后修改时间这三个时间值都被设置为文件创建时的当前时间,删除时间则不设置,只有在文件被删除时,才设置文件的删除时间。

  8. 0x18~0x19: 组 ID 的低 16 位

  9. 0x1A~0x1B: 硬链接数

  10. 0x1C~0x1F: 文件所占扇区数(512 字节/块)

  11. 0x20~0x23: Flag。用来定义文件的属性

    Flag值的组合
    描述
    0x1 此文件需要安全删除 (EXT4_SECRM_FL) (未实现)
    0x2 文件需要被保护,阻止删除操作 (EXT4_UNRM_FL) (未实现)
    0x4 文件已经被压缩 (EXT4_COMPR_FL) (未真正实现)
    0x8 对文件的所有写操作必须是同步的 (EXT4_SYNC_FL)
    0x10 文件是不可变的 (EXT4_IMMUTABLE_FL)
    0x20 文件只能被追加 (EXT4_APPEND_FL)
    0x40 dump(1) 工具不应该转储这个文件 (EXT4_NODUMP_FL)
    0x80 不更新访问时间 (EXT4_NOATIME_FL)
    0x100 脏压缩文件 (EXT4_DIRTY_FL) (未使用)
    0x200 文件具有一个或多个压缩群集 (EXT4_COMPRBLK_FL) (未使用)
    0x400 文件不能被压缩 (EXT4_NOCOMPR_FL) (未使用)
    0x800 加密的 i-节点 (EXT4_ENCRYPT_FL)
    此位值以前是 EXT4_ECOMPR_FL (压缩错误),从未使用过
    0x1000 目录具有散列索引 (EXT4_INDEX_FL)
    0x2000 AFS 魔术目录 (EXT4_IMAGIC_FL)
    0x4000 文件数据必须始终通过日志写入 (EXT4_JOURNAL_DATA_FL)
    0x8000 不应合并文件尾部 (EXT4_NOTAIL_FL) (分机 4 未使用)
    0x10000 所有目录条目数据应同步写入 (EXT4_DIRSYNC_FL)
    0x20000 目录层次结构的顶部 (EXT4_TOPDIR_FL)
    0x40000 此文件的大小非常大 (EXT4_HUGE_FILE_FL)
    0x80000 i-节点使用扩展数据块 (EXT4_EXTENTS_FL)
    0x100000 文件受验证保护 (EXT4_VERITY_FL)
    0x200000 Inode 在其数据块中存储大型扩展属性值 (EXT4_EA_INODE_FL)
    0x400000 此文件具有在 EOF 之后分配的块 (EXT4_EOFBLOCKS_FL) (已弃用)
    0x01000000 i-节点是一个快照 (EXT4_SNAPFILE_FL) (不在主线中)
    0x04000000 正在删除快照 (EXT4_SNAPFILE_DELETED_FL) (不在主线中)
    0x08000000 快照收缩已完成 (EXT4_SNAPFILE_SHRUNK_FL) (不在主线中)
    0x10000000 i-节点具有内联数据 (EXT4_INLINE_DATA_FL)
    0x20000000 创建具有相同项目 ID 的子项 (EXT4_PROJINHERIT_FL)
    0x80000000 保留给 ext4 库 (EXT4_RESERVED_FL)
    聚合标志:
    0x705BDFFF 用户可见的 Flag
    0x604BC0FF 用户可修改的 Flag
  12. 0x24~0x27: 未用

  13. 0x28~0x63: 块指针

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

    如果文件大于 12 个块,则第 13 个块指针是一个间接块指针,它指向的块内存放的是直接块指针而不是文件内容。

    如果间接块指针指向的块依然存放不下文件所占的块号,就需要二级间接块指针了。第 14 个块指针是二级间接块指针,它指向的块内存放的是一级间接块指针而不是文件内容。

    二级间接块指针如果还是无法满足文件的块号需求,就要用第 15 个块指针了。这是三级间接块指针,它指向的块内存放的是二级间接块指针而不是文件内容。

  14. 0x64~0x67: 文件版本号(用于 NFS)

  15. 0x68~0x6B: 扩展属性块的低 32 位

    该字段中可能有许多种扩展属性,ACL 是其中之一

  16. 0x6C~0x6F: 字节数的高 32 位

  17. 0x70~0x73: 片段的数据块地址

  18. 0x74 : 片段索引

  19. 0x75 : 段大小

  20. 0x76~0x77: 未用

  21. 0x78~0x79: UID 的高 16 位

  22. 0x7A~0x7B: GID 的高 16 位

  23. 0x7C~0x7F: 未用

Ext3 文件系统目录项分析

目录项用来存放文件及目录的 i-节点号、目录项的长度、文件名等信息,它们存储在分配给目录的块中

Ext3 文件系统目录项的结构分析

上图文件名中的绿色标记为文件名补位部分,不计入“文件名长度”项中

字节偏移 字段长度(字节) 字段名和定义
0x00 ~ 0x03 4 i-节点号
0x04 ~ 0x05 2 目录项长度
0x06 ~ 0x06 1 文件名长度
0x07 ~ 0x07 1 文件类型
0x08 ~ 变长 文件名

目录项中“文件类型”的具体分类

类型值 类型含义
0x01 文件
0x02 目录
0x07 符号链接

目录项结构特点

Ext3 文件系统的目录项有如下特点:

  1. 目录项存储在目录区中,i-节点内有描述目录区地址的块指针。

  2. 目录项的长度不是固定的,其长度随着文件名长度的不同而不同,文件名最长为 255 个字符,并且使用 ASCII 码进行存储。

  3. Ext3 的目录项长度虽然不固定,但一定是 4 字节的倍数,每个目录项以 4 字节为边界进行排列,即每个目录项的起始字节必须是位于目录内可被 4 整除的相对偏移字节处。

  4. 另外,Ext3 目录项的结束位置如果没有达到 4 字节的边界,那么也需要在文件名后加空字节以达到 4 字节的边界

  5. 目录区中的前两个目录项一定是“.”和“. .”目录,“.”目录表示当前目录,“. .”目录表示父目录。

  6. 每个目录项中有一个长度值指向下一个目录项,最后一个目录项的长度则指向本块的结尾处