索引节点

在常规 UNIX 文件系统中,inode 存储所有与文件相关的元数据(时间戳、块映射、扩展属性等),而不是目录项。要查找与文件关联的信息,必须遍历目录文件以找到与文件关联的目录项,然后加载 inode 以查找该文件的元数据。ext4 似乎为了性能原因稍微“作弊”:它在目录项中存储文件类型(通常存储在 inode 中)的副本。(与 FAT 相比,FAT 将所有文件信息直接存储在目录项中,但不支持硬链接,并且由于其更简单的块分配器和广泛使用链表,通常比 ext4 更频繁地进行寻道操作。)

inode 表是一个 struct ext4_inode 的线性数组。该表的大小足以存储至少 sb.s_inode_size * sb.s_inodes_per_group 字节。包含 inode 的块组号可以计算为 (inode_number - 1) / sb.s_inodes_per_group,而在该组表中的偏移量是 (inode_number - 1) % sb.s_inodes_per_group。不存在 inode 0。

inode 校验和是根据文件系统 UUID、inode 号和 inode 结构本身计算的。

inode 表项在 struct ext4_inode 中布局。

偏移量

大小

名称

描述

0x0

__le16

i_mode

文件模式。参见下方表格 i_mode

0x2

__le16

i_uid

所有者 UID 的低 16 位。

0x4

__le32

i_size_lo

文件大小(字节)的低 32 位。

0x8

__le32

i_atime

上次访问时间,自纪元以来的秒数。但是,如果设置了 EA_INODE inode 标志,则此 inode 存储扩展属性值,并且此字段包含该值的校验和。

0xC

__le32

i_ctime

上次 inode 更改时间,自纪元以来的秒数。但是,如果设置了 EA_INODE inode 标志,则此 inode 存储扩展属性值,并且此字段包含属性值的引用计数低 32 位。

0x10

__le32

i_mtime

上次数据修改时间,自纪元以来的秒数。但是,如果设置了 EA_INODE inode 标志,则此 inode 存储扩展属性值,并且此字段包含拥有该扩展属性的 inode 号。

0x14

__le32

i_dtime

删除时间,自纪元以来的秒数。

0x18

__le16

i_gid

GID 的低 16 位。

0x1A

__le16

i_links_count

硬链接计数。通常,ext4 不允许 inode 拥有超过 65,000 个硬链接。这适用于文件和目录,这意味着一个目录中不能有超过 64,998 个子目录(每个子目录的“..”条目算作一个硬链接,目录本身的“.”条目也算)。启用 DIR_NLINK 特性后,ext4 通过将此字段设置为 1 来支持超过 64,998 个子目录,表示硬链接数量未知。

0x1C

__le32

i_blocks_lo

“块”计数的低 32 位。如果文件系统上未设置 huge_file 特性标志,则文件在磁盘上占用 i_blocks_lo 个 512 字节块。如果 huge_file 已设置且 inode.i_flags 中未设置 EXT4_HUGE_FILE_FL,则文件在磁盘上占用 i_blocks_lo + (i_blocks_hi << 32) 个 512 字节块。如果 huge_file 已设置且 inode.i_flags 中已设置 EXT4_HUGE_FILE_FL,则此文件在磁盘上占用 (i_blocks_lo + i_blocks_hi << 32) 个文件系统块。

0x20

__le32

i_flags

Inode 标志。参见下方表格 i_flags

0x24

4 bytes

i_osd1

更多详细信息请参见表格 i_osd1

0x28

60 bytes

i_block[EXT4_N_BLOCKS=15]

块映射或 extent 树。参见“inode.i_block 的内容”一节。

0x64

__le32

i_generation

文件版本(用于 NFS)。

0x68

__le32

i_file_acl_lo

扩展属性块的低 32 位。ACL 当然是众多可能的扩展属性之一;我认为此字段的名称是由于首次使用扩展属性用于 ACL 的结果。

0x6C

__le32

i_size_high / i_dir_acl

文件/目录大小的高 32 位。在 ext2/3 中,此字段名为 i_dir_acl,尽管它通常设置为零且从未使用过。

0x70

__le32

i_obso_faddr

(已过时)碎片地址。

0x74

12 bytes

i_osd2

更多详细信息请参见表格 i_osd2

0x80

__le16

i_extra_isize

此 inode 的大小 - 128。或者,原始 ext2 inode 之外的扩展 inode 字段(包括此字段)的大小。

0x82

__le16

i_checksum_hi

inode 校验和的高 16 位。

0x84

__le32

i_ctime_extra

额外更改时间位。这提供亚秒精度。参见 inode 时间戳部分。

0x88

__le32

i_mtime_extra

额外修改时间位。这提供亚秒精度。

0x8C

__le32

i_atime_extra

额外访问时间位。这提供亚秒精度。

0x90

__le32

i_crtime

文件创建时间,自纪元以来的秒数。

0x94

__le32

i_crtime_extra

额外文件创建时间位。这提供亚秒精度。

0x98

__le32

i_version_hi

版本号的高 32 位。

0x9C

__le32

i_projid

项目 ID。

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 (FIFO)

0x2000

S_IFCHR (字符设备)

0x4000

S_IFDIR (目录)

0x6000

S_IFBLK (块设备)

0x8000

S_IFREG (常规文件)

0xA000

S_IFLNK (符号链接)

0xC000

S_IFSOCK (套接字)

i_flags 字段是这些值的组合

描述

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

加密 inode (EXT4_ENCRYPT_FL)。此位值以前是 EXT4_ECOMPR_FL(压缩错误),从未被使用。

0x1000

目录具有哈希索引 (EXT4_INDEX_FL)。

0x2000

AFS 魔法目录 (EXT4_IMAGIC_FL)。

0x4000

文件数据必须始终通过日志写入 (EXT4_JOURNAL_DATA_FL)。

0x8000

文件尾部不应合并 (EXT4_NOTAIL_FL)。(ext4 未使用)

0x10000

所有目录项数据应同步写入(参见 dirsync)(EXT4_DIRSYNC_FL)。

0x20000

目录层次结构的顶部 (EXT4_TOPDIR_FL)。

0x40000

这是一个大文件 (EXT4_HUGE_FILE_FL)。

0x80000

Inode 使用 extent (EXT4_EXTENTS_FL)。

0x100000

Verity 保护的文件 (EXT4_VERITY_FL)。

0x200000

Inode 在其数据块中存储大型扩展属性值 (EXT4_EA_INODE_FL)。

0x400000

此文件有超出文件尾的已分配块 (EXT4_EOFBLOCKS_FL)。(已弃用)

0x01000000

Inode 是一个快照 (EXT4_SNAPFILE_FL)。(不在主线中)

0x04000000

快照正在删除 (EXT4_SNAPFILE_DELETED_FL)。(不在主线中)

0x08000000

快照收缩已完成 (EXT4_SNAPFILE_SHRUNK_FL)。(不在主线中)

0x10000000

Inode 具有内联数据 (EXT4_INLINE_DATA_FL)。

0x20000000

使用相同的项目 ID 创建子级 (EXT4_PROJINHERIT_FL)。

0x80000000

为 ext4 库保留 (EXT4_RESERVED_FL)。

聚合标志

0x705BDFFF

用户可见标志。

0x604BC0FF

用户可修改标志。请注意,虽然 EXT4_JOURNAL_DATA_FL 和 EXT4_EXTENTS_FL 可以通过 setattr 设置,但它们不在内核的 EXT4_FL_USER_MODIFIABLE 掩码中,因为内核需要以特殊方式处理这些标志的设置,并且它们从直接保存到 i_flags 的标志集中被屏蔽掉。

osd1 字段根据创建者的不同有多种含义

Linux

偏移量

大小

名称

描述

0x0

__le32

l_i_version

Inode 版本。但是,如果设置了 EA_INODE inode 标志,则此 inode 存储扩展属性值,并且此字段包含属性值引用计数的高 32 位。

Hurd

偏移量

大小

名称

描述

0x0

__le32

h_i_translator

??

Masix

偏移量

大小

名称

描述

0x0

__le32

m_i_reserved

??

osd2 字段根据文件系统创建者的不同有多种含义

Linux

偏移量

大小

名称

描述

0x0

__le16

l_i_blocks_high

块计数的高 16 位。请参见 i_blocks_lo 附带的注释。

0x2

__le16

l_i_file_acl_high

扩展属性块(历史上是文件 ACL 位置)的高 16 位。参见下方扩展属性一节。

0x4

__le16

l_i_uid_high

所有者 UID 的高 16 位。

0x6

__le16

l_i_gid_high

GID 的高 16 位。

0x8

__le16

l_i_checksum_lo

inode 校验和的低 16 位。

0xA

__le16

l_i_reserved

未使用。

Hurd

偏移量

大小

名称

描述

0x0

__le16

h_i_reserved1

??

0x2

__u16

h_i_mode_high

文件模式的高 16 位。

0x4

__le16

h_i_uid_high

所有者 UID 的高 16 位。

0x6

__le16

h_i_gid_high

GID 的高 16 位。

0x8

__u32

h_i_author

作者代码?

Masix

偏移量

大小

名称

描述

0x0

__le16

h_i_reserved1

??

0x2

__u16

m_i_file_acl_high

扩展属性块(历史上是文件 ACL 位置)的高 16 位。

0x4

__u32

m_i_reserved2[2]

??

Inode 大小

在 ext2 和 ext3 中,inode 结构大小固定为 128 字节 (EXT2_GOOD_OLD_INODE_SIZE),每个 inode 的磁盘记录大小为 128 字节。从 ext4 开始,可以在格式化时为文件系统中的所有 inode 分配更大的磁盘 inode,以提供超出原始 ext2 inode 结尾的空间。磁盘 inode 记录大小在超级块中记录为 s_inode_size。实际被 struct ext4_inode 使用的超出原始 128 字节 ext2 inode 的字节数记录在每个 inode 的 i_extra_isize 字段中,这允许 struct ext4_inode 在新的内核中增长而无需升级所有磁盘上的 inode。访问超出 EXT2_GOOD_OLD_INODE_SIZE 的字段应验证其是否在 i_extra_isize 范围内。默认情况下,ext4 inode 记录为 256 字节,并且(截至 2019 年 8 月)inode 结构为 160 字节 (i_extra_isize = 32)。inode 结构末尾与 inode 记录末尾之间的额外空间可用于存储扩展属性。每个 inode 记录可以和文件系统块大小一样大,尽管这效率不高。

查找 Inode

每个块组包含 sb->s_inodes_per_group 个 inode。因为 inode 0 被定义为不存在,所以可以使用以下公式找到 inode 所在的块组:bg = (inode_num - 1) / sb->s_inodes_per_group。特定的 inode 可以在块组的 inode 表中通过 index = (inode_num - 1) % sb->s_inodes_per_group 找到。要获取 inode 表中的字节地址,请使用 offset = index * sb->s_inode_size

Inode 时间戳

inode 结构的低 128 字节中记录了四个时间戳——inode 更改时间 (ctime)、访问时间 (atime)、数据修改时间 (mtime) 和删除时间 (dtime)。这四个字段是 32 位带符号整数,表示自 Unix 纪元(1970-01-01 00:00:00 GMT)以来的秒数,这意味着这些字段将在 2038 年 1 月溢出。如果文件系统没有 orphan_file 特性,则未从任何目录链接但仍打开的 inode(孤立 inode)的 dtime 字段被重载用于孤立列表。s_last_orphan 超级块字段指向孤立列表中的第一个 inode;dtime 则是下一个孤立 inode 的编号,如果没有更多孤立 inode 则为零。

如果 inode 结构大小 sb->s_inode_size 大于 128 字节,并且 i_inode_extra 字段足够大以包含相应的 i_[cma]time_extra 字段,则 ctime、atime 和 mtime inode 字段将被扩展为 64 位。在这个“额外”的 32 位字段中,低两位用于将 32 位秒字段扩展为 34 位宽;高 30 位用于提供纳秒级时间戳精度。因此,时间戳直到 2446 年 5 月才应溢出。dtime 未被扩展。还有一个第五个时间戳用于记录 inode 创建时间 (crtime);此字段为 64 位宽,并以与 64 位 [cma]time 相同的方式解码。crtime 和 dtime 都无法通过常规的 stat() 接口访问,尽管 debugfs 会报告它们。

我们使用 32 位带符号时间值加上 (2^32 * (额外纪元位))。换句话说:

额外纪元位

32 位时间的最高有效位 (MSB)

从 32 位带符号调整到 64 位 tv_sec 的调整

解码后的 64 位 tv_sec

有效时间范围

0 0

1

0

-0x80000000 - -0x00000001

1901-12-13 至 1969-12-31

0 0

0

0

0x000000000 - 0x07fffffff

1970-01-01 至 2038-01-19

0 1

1

0x100000000

0x080000000 - 0x0ffffffff

2038-01-19 至 2106-02-07

0 1

0

0x100000000

0x100000000 - 0x17fffffff

2106-02-07 至 2174-02-25

1 0

1

0x200000000

0x180000000 - 0x1ffffffff

2174-02-25 至 2242-03-16

1 0

0

0x200000000

0x200000000 - 0x27fffffff

2242-03-16 至 2310-04-04

1 1

1

0x300000000

0x280000000 - 0x2ffffffff

2310-04-04 至 2378-04-22

1 1

0

0x300000000

0x300000000 - 0x37fffffff

2378-04-22 至 2446-05-10

这是一种有些奇怪的编码,因为正值的数量实际上是负值的七倍。此外,自 2038 年以来,解码和编码日期一直存在长期存在的错误,截至内核 3.12 和 e2fsprogs 1.42.8 似乎尚未修复。64 位内核错误地将额外纪元位 1,1 用于 1901 年至 1970 年之间的日期。在某个时候,内核将会修复此问题,并且 e2fsck 也会修复此情况,前提是在 2310 年之前运行它。