索引节点¶
在常规 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 特性标志,则文件在磁盘上占用 |
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 |
所有目录项数据应同步写入(参见 |
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 是一个快照 ( |
0x04000000 |
快照正在删除 ( |
0x08000000 |
快照收缩已完成 ( |
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 |
|
1901-12-13 至 1969-12-31 |
0 0 |
0 |
0 |
|
1970-01-01 至 2038-01-19 |
0 1 |
1 |
0x100000000 |
|
2038-01-19 至 2106-02-07 |
0 1 |
0 |
0x100000000 |
|
2106-02-07 至 2174-02-25 |
1 0 |
1 |
0x200000000 |
|
2174-02-25 至 2242-03-16 |
1 0 |
0 |
0x200000000 |
|
2242-03-16 至 2310-04-04 |
1 1 |
1 |
0x300000000 |
|
2310-04-04 至 2378-04-22 |
1 1 |
0 |
0x300000000 |
|
2378-04-22 至 2446-05-10 |
这是一种有些奇怪的编码,因为正值的数量实际上是负值的七倍。此外,自 2038 年以来,解码和编码日期一直存在长期存在的错误,截至内核 3.12 和 e2fsprogs 1.42.8 似乎尚未修复。64 位内核错误地将额外纪元位 1,1 用于 1901 年至 1970 年之间的日期。在某个时候,内核将会修复此问题,并且 e2fsck 也会修复此情况,前提是在 2310 年之前运行它。