3. 全局结构¶
文件系统被分片成多个块组,每个块组在固定位置都有静态元数据。
3.1. 超级块¶
超级块记录了有关封闭文件系统的各种信息,例如块计数、inode 计数、支持的功能、维护信息等等。
如果设置了 sparse_super 功能标志,则超级块和组描述符的冗余副本仅保留在组号为 0 或 3、5 或 7 的幂的组中。如果未设置该标志,则冗余副本保留在所有组中。
超级块校验和是根据超级块结构计算的,其中包括 FS UUID。
ext4 超级块在 struct ext4_super_block
中布局如下
偏移 |
大小 |
名称 |
描述 |
---|---|---|---|
0x0 |
__le32 |
s_inodes_count |
inode 总数。 |
0x4 |
__le32 |
s_blocks_count_lo |
块总数。 |
0x8 |
__le32 |
s_r_blocks_count_lo |
只有超级用户才能分配此数量的块。 |
0xC |
__le32 |
s_free_blocks_count_lo |
可用块数。 |
0x10 |
__le32 |
s_free_inodes_count |
可用 inode 数。 |
0x14 |
__le32 |
s_first_data_block |
第一个数据块。对于 1k 块文件系统,这必须至少为 1,对于所有其他块大小,通常为 0。 |
0x18 |
__le32 |
s_log_block_size |
块大小为 2 ^ (10 + s_log_block_size)。 |
0x1C |
__le32 |
s_log_cluster_size |
如果启用了 bigalloc,则簇大小为 2 ^ (10 + s_log_cluster_size) 块。否则,s_log_cluster_size 必须等于 s_log_block_size。 |
0x20 |
__le32 |
s_blocks_per_group |
每个组的块数。 |
0x24 |
__le32 |
s_clusters_per_group |
如果启用了 bigalloc,则每个组的簇数。否则,s_clusters_per_group 必须等于 s_blocks_per_group。 |
0x28 |
__le32 |
s_inodes_per_group |
每个组的 inode 数。 |
0x2C |
__le32 |
s_mtime |
挂载时间,自纪元以来的秒数。 |
0x30 |
__le32 |
s_wtime |
写入时间,自纪元以来的秒数。 |
0x34 |
__le16 |
s_mnt_count |
自上次 fsck 以来挂载的次数。 |
0x36 |
__le16 |
s_max_mnt_count |
超过此挂载次数需要 fsck。 |
0x38 |
__le16 |
s_magic |
魔术签名,0xEF53 |
0x3A |
__le16 |
s_state |
文件系统状态。有关更多信息,请参见 super_state。 |
0x3C |
__le16 |
s_errors |
检测到错误时的行为。有关更多信息,请参见 super_errors。 |
0x3E |
__le16 |
s_minor_rev_level |
次修订级别。 |
0x40 |
__le32 |
s_lastcheck |
上次检查的时间,自纪元以来的秒数。 |
0x44 |
__le32 |
s_checkinterval |
检查之间的最大时间,以秒为单位。 |
0x48 |
__le32 |
s_creator_os |
创建者操作系统。有关更多信息,请参见表 super_creator。 |
0x4C |
__le32 |
s_rev_level |
修订级别。有关更多信息,请参见表 super_revision。 |
0x50 |
__le16 |
s_def_resuid |
保留块的默认 uid。 |
0x52 |
__le16 |
s_def_resgid |
保留块的默认 gid。 |
这些字段仅适用于 EXT4_DYNAMIC_REV 超级块。 注意:兼容功能集和不兼容功能集之间的区别在于,如果内核不知道不兼容功能集中设置的位,则应拒绝挂载文件系统。 e2fsck 的要求更为严格;如果它不知道兼容或不兼容功能集中的功能,则必须中止,并且不要试图干预它不理解的事情... |
|||
0x54 |
__le32 |
s_first_ino |
第一个非保留的 inode。 |
0x58 |
__le16 |
s_inode_size |
inode 结构的大小,以字节为单位。 |
0x5A |
__le16 |
s_block_group_nr |
此超级块的块组编号。 |
0x5C |
__le32 |
s_feature_compat |
兼容功能集标志。即使内核不理解标志,仍然可以读取/写入此 fs;fsck 不应该这样做。有关更多信息,请参见 super_compat 表。 |
0x60 |
__le32 |
s_feature_incompat |
不兼容功能集。如果内核或 fsck 不理解这些位中的一位,它应该停止。有关更多信息,请参见 super_incompat 表。 |
0x64 |
__le32 |
s_feature_ro_compat |
只读兼容功能集。如果内核不理解这些位中的一位,它仍然可以只读挂载。有关更多信息,请参见 super_rocompat 表。 |
0x68 |
__u8 |
s_uuid[16] |
卷的 128 位 UUID。 |
0x78 |
char |
s_volume_name[16] |
卷标。 |
0x88 |
char |
s_last_mounted[64] |
文件系统上次挂载的目录。 |
0xC8 |
__le32 |
s_algorithm_usage_bitmap |
用于压缩(在 e2fsprogs/Linux 中未使用) |
性能提示。只有在启用 EXT4_FEATURE_COMPAT_DIR_PREALLOC 标志时,才应发生目录预分配。 |
|||
0xCC |
__u8 |
s_prealloc_blocks |
#. 要尝试为 ... 文件预分配的块?(在 e2fsprogs/Linux 中未使用) |
0xCD |
__u8 |
s_prealloc_dir_blocks |
#. 为目录预分配的块。(在 e2fsprogs/Linux 中未使用) |
0xCE |
__le16 |
s_reserved_gdt_blocks |
为将来的文件系统扩展保留的 GDT 条目数。 |
只有在设置了 EXT4_FEATURE_COMPAT_HAS_JOURNAL 时,日志记录支持才有效。 |
|||
0xD0 |
__u8 |
s_journal_uuid[16] |
日志超级块的 UUID |
0xE0 |
__le32 |
s_journal_inum |
日志文件的 inode 号。 |
0xE4 |
__le32 |
s_journal_dev |
如果设置了外部日志功能标志,则为日志文件的设备号。 |
0xE8 |
__le32 |
s_last_orphan |
要删除的孤立 inode 列表的开头。 |
0xEC |
__le32 |
s_hash_seed[4] |
HTREE 哈希种子。 |
0xFC |
__u8 |
s_def_hash_version |
用于目录哈希的默认哈希算法。有关更多信息,请参见 super_def_hash。 |
0xFD |
__u8 |
s_jnl_backup_type |
如果此值为 0 或 EXT3_JNL_BACKUP_BLOCKS (1),则 |
0xFE |
__le16 |
s_desc_size |
如果设置了 64 位不兼容功能标志,则组描述符的大小,以字节为单位。 |
0x100 |
__le32 |
s_default_mount_opts |
默认挂载选项。有关更多信息,请参见 super_mountopts 表。 |
0x104 |
__le32 |
s_first_meta_bg |
如果启用了 meta_bg 功能,则为第一个元块块组。 |
0x108 |
__le32 |
s_mkfs_time |
文件系统创建时间,自纪元以来的秒数。 |
0x10C |
__le32 |
s_jnl_blocks[17] |
日志 inode 的 |
只有在设置了 EXT4_FEATURE_COMPAT_64BIT 时,64 位支持才有效。 |
|||
0x150 |
__le32 |
s_blocks_count_hi |
块计数的高 32 位。 |
0x154 |
__le32 |
s_r_blocks_count_hi |
保留块计数的高 32 位。 |
0x158 |
__le32 |
s_free_blocks_count_hi |
可用块计数的高 32 位。 |
0x15C |
__le16 |
s_min_extra_isize |
所有 inode 至少有 # 个字节。 |
0x15E |
__le16 |
s_want_extra_isize |
新的 inode 应该保留 # 个字节。 |
0x160 |
__le32 |
s_flags |
杂项标志。有关更多信息,请参见 super_flags 表。 |
0x164 |
__le16 |
s_raid_stride |
RAID 条带大小。这是在移动到下一个磁盘之前从磁盘读取或写入的逻辑块数。这会影响文件系统元数据的放置,这将有望使 RAID 存储更快。 |
0x166 |
__le16 |
s_mmp_interval |
#. 在多重挂载防止 (MMP) 检查中等待的秒数。理论上,MMP 是一种在超级块中记录哪个主机和设备已挂载文件系统的机制,以防止多次挂载。此功能似乎没有实现... |
0x168 |
__le64 |
s_mmp_block |
用于多重挂载保护数据的块号。 |
0x170 |
__le32 |
s_raid_stripe_width |
RAID 条带宽度。这是在返回到当前磁盘之前从磁盘读取或写入的逻辑块数。块分配器使用它来尝试减少 RAID5/6 中的读取-修改-写入操作的数量。 |
0x174 |
__u8 |
s_log_groups_per_flex |
灵活块组的大小为 2 ^ |
0x175 |
__u8 |
s_checksum_type |
元数据校验和算法类型。唯一有效的值是 1 (crc32c)。 |
0x176 |
__le16 |
s_reserved_pad |
|
0x178 |
__le64 |
s_kbytes_written |
在此文件系统的生命周期内写入的千字节数。 |
0x180 |
__le32 |
s_snapshot_inum |
活动快照的 inode 号。(在 e2fsprogs/Linux 中未使用。) |
0x184 |
__le32 |
s_snapshot_id |
活动快照的顺序 ID。(在 e2fsprogs/Linux 中未使用。) |
0x188 |
__le64 |
s_snapshot_r_blocks_count |
为活动快照的未来使用保留的块数。(在 e2fsprogs/Linux 中未使用。) |
0x190 |
__le32 |
s_snapshot_list |
磁盘快照列表头部的 inode 号。(在 e2fsprogs/Linux 中未使用。) |
0x194 |
__le32 |
s_error_count |
看到的错误数量。 |
0x198 |
__le32 |
s_first_error_time |
首次发生错误的时间,自 Unix 纪元以来的秒数。 |
0x19C |
__le32 |
s_first_error_ino |
首次错误涉及的 inode。 |
0x1A0 |
__le64 |
s_first_error_block |
首次错误涉及的块号。 |
0x1A8 |
__u8 |
s_first_error_func[32] |
发生错误的函数名称。 |
0x1C8 |
__le32 |
s_first_error_line |
发生错误的行号。 |
0x1CC |
__le32 |
s_last_error_time |
最近一次错误的时间,自 Unix 纪元以来的秒数。 |
0x1D0 |
__le32 |
s_last_error_ino |
最近一次错误涉及的 inode。 |
0x1D4 |
__le32 |
s_last_error_line |
最近一次发生错误的行号。 |
0x1D8 |
__le64 |
s_last_error_block |
最近一次错误涉及的块号。 |
0x1E0 |
__u8 |
s_last_error_func[32] |
最近一次发生错误的函数名称。 |
0x200 |
__u8 |
s_mount_opts[64] |
挂载选项的 ASCIIZ 字符串。 |
0x240 |
__le32 |
s_usr_quota_inum |
用户配额文件的 inode 号。 |
0x244 |
__le32 |
s_grp_quota_inum |
组配额文件的 inode 号。 |
0x248 |
__le32 |
s_overhead_blocks |
文件系统中开销块/簇的数量。(嗯?此字段始终为零,这意味着内核会动态计算它。) |
0x24C |
__le32 |
s_backup_bgs[2] |
包含超级块备份的块组(如果使用 sparse_super2)。 |
0x254 |
__u8 |
s_encrypt_algos[4] |
正在使用的加密算法。在任何时候最多可以使用四种算法;有效的算法代码在下面的 super_encrypt 表中给出。 |
0x258 |
__u8 |
s_encrypt_pw_salt[16] |
用于加密的 string2key 算法的盐值。 |
0x268 |
__le32 |
s_lpf_ino |
lost+found 的 inode 号。 |
0x26C |
__le32 |
s_prj_quota_inum |
跟踪项目配额的 inode。 |
0x270 |
__le32 |
s_checksum_seed |
用于 metadata_csum 计算的校验和种子。该值为 crc32c(~0, $orig_fs_uuid)。 |
0x274 |
__u8 |
s_wtime_hi |
s_wtime 字段的高 8 位。 |
0x275 |
__u8 |
s_mtime_hi |
s_mtime 字段的高 8 位。 |
0x276 |
__u8 |
s_mkfs_time_hi |
s_mkfs_time 字段的高 8 位。 |
0x277 |
__u8 |
s_lastcheck_hi |
s_lastcheck 字段的高 8 位。 |
0x278 |
__u8 |
s_first_error_time_hi |
s_first_error_time 字段的高 8 位。 |
0x279 |
__u8 |
s_last_error_time_hi |
s_last_error_time 字段的高 8 位。 |
0x27A |
__u8 |
s_pad[2] |
零填充。 |
0x27C |
__le16 |
s_encoding |
文件名字符集编码。 |
0x27E |
__le16 |
s_encoding_flags |
文件名字符集编码标志。 |
0x280 |
__le32 |
s_orphan_file_inum |
孤立文件 inode 号。 |
0x284 |
__le32 |
s_reserved[94] |
填充到块的末尾。 |
0x3FC |
__le32 |
s_checksum |
超级块校验和。 |
超级块状态是以下各项的组合
值 |
描述 |
---|---|
0x0001 |
已干净卸载 |
0x0002 |
检测到错误 |
0x0004 |
正在恢复孤立项 |
超级块错误策略是以下之一
值 |
描述 |
---|---|
1 |
继续 |
2 |
重新挂载为只读 |
3 |
内核崩溃 |
文件系统创建者是以下之一
值 |
描述 |
---|---|
0 |
Linux |
1 |
Hurd |
2 |
Masix |
3 |
FreeBSD |
4 |
Lites |
超级块修订版本是以下之一
值 |
描述 |
---|---|
0 |
原始格式 |
1 |
具有动态 inode 大小的 v2 格式 |
请注意,EXT4_DYNAMIC_REV
指的是修订版本 1 或更新的文件系统。
超级块兼容特性字段是以下各项的任意组合
值 |
描述 |
---|---|
0x1 |
目录预分配 (COMPAT_DIR_PREALLOC)。 |
0x2 |
“imagic inodes”。从代码中不清楚它做了什么 (COMPAT_IMAGIC_INODES)。 |
0x4 |
具有日志 (COMPAT_HAS_JOURNAL)。 |
0x8 |
支持扩展属性 (COMPAT_EXT_ATTR)。 |
0x10 |
具有用于文件系统扩展的保留 GDT 块 (COMPAT_RESIZE_INODE)。需要 RO_COMPAT_SPARSE_SUPER。 |
0x20 |
具有目录索引 (COMPAT_DIR_INDEX)。 |
0x40 |
“Lazy BG”。不在 Linux 内核中,似乎用于未初始化的块组? (COMPAT_LAZY_BG) |
0x80 |
“排除 inode”。未使用。(COMPAT_EXCLUDE_INODE)。 |
0x100 |
“排除位图”。似乎用于指示与快照相关的排除位图的存在?在内核中未定义或在 e2fsprogs 中未使用 (COMPAT_EXCLUDE_BITMAP)。 |
0x200 |
稀疏超级块,v2。如果设置此标志,则 SB 字段 s_backup_bgs 指向包含备份超级块的两个块组 (COMPAT_SPARSE_SUPER2)。 |
0x400 |
支持快速提交。尽管快速提交块向后不兼容,但快速提交块并非始终存在于日志中。如果快速提交块存在于日志中,则设置 JBD2 不兼容特性 (JBD2_FEATURE_INCOMPAT_FAST_COMMIT) (COMPAT_FAST_COMMIT)。 |
0x1000 |
已分配孤立文件。这是一个特殊文件,用于更有效地跟踪未链接但仍处于打开状态的 inode。当文件中可能存在任何条目时,我们还会设置适当的 rocompat 特性 (RO_COMPAT_ORPHAN_PRESENT)。 |
超级块不兼容特性字段是以下各项的任意组合
值 |
描述 |
---|---|
0x1 |
压缩 (INCOMPAT_COMPRESSION)。 |
0x2 |
目录条目记录文件类型。请参阅下面的 ext4_dir_entry_2 (INCOMPAT_FILETYPE)。 |
0x4 |
文件系统需要恢复 (INCOMPAT_RECOVER)。 |
0x8 |
文件系统具有单独的日志设备 (INCOMPAT_JOURNAL_DEV)。 |
0x10 |
元块组。请参阅前面有关此功能的讨论 (INCOMPAT_META_BG)。 |
0x40 |
此文件系统中的文件使用 extent (INCOMPAT_EXTENTS)。 |
0x80 |
启用 2^64 块的文件系统大小 (INCOMPAT_64BIT)。 |
0x100 |
多重挂载保护 (INCOMPAT_MMP)。 |
0x200 |
灵活块组。请参阅前面有关此功能的讨论 (INCOMPAT_FLEX_BG)。 |
0x400 |
Inode 可用于存储大型扩展属性值 (INCOMPAT_EA_INODE)。 |
0x1000 |
目录条目中的数据 (INCOMPAT_DIRDATA)。(未实现?) |
0x2000 |
元数据校验和种子存储在超级块中。此功能使管理员能够在文件系统挂载时更改 metadata_csum 文件系统的 UUID;如果没有此功能,则校验和定义需要重写所有元数据块 (INCOMPAT_CSUM_SEED)。 |
0x4000 |
大型目录 >2GB 或 3 级 htree (INCOMPAT_LARGEDIR)。在此功能之前,目录的大小不能超过 4GiB,并且 htree 的深度不能超过 2 级。如果启用此功能,则目录可以大于 4GiB,并且最大 htree 深度为 3。 |
0x8000 |
inode 中的数据 (INCOMPAT_INLINE_DATA)。 |
0x10000 |
文件系统上存在加密的 inode。(INCOMPAT_ENCRYPT)。 |
超级块只读兼容特性字段是以下各项的任意组合
值 |
描述 |
---|---|
0x1 |
稀疏超级块。请参阅前面有关此功能的讨论 (RO_COMPAT_SPARSE_SUPER)。 |
0x2 |
此文件系统已用于存储大于 2GiB 的文件 (RO_COMPAT_LARGE_FILE)。 |
0x4 |
未在内核或 e2fsprogs 中使用 (RO_COMPAT_BTREE_DIR)。 |
0x8 |
此文件系统中的文件大小以逻辑块(而不是 512 字节扇区)为单位表示。这意味着一个非常大的文件! (RO_COMPAT_HUGE_FILE) |
0x10 |
组描述符具有校验和。除了检测损坏外,这对于使用未初始化的组进行延迟格式化也很有用 (RO_COMPAT_GDT_CSUM)。 |
0x20 |
指示旧的 ext3 32,000 子目录限制不再适用 (RO_COMPAT_DIR_NLINK)。如果目录的 i_links_count 递增超过 64,999,则将其设置为 1。 |
0x40 |
指示此文件系统上存在大型 inode (RO_COMPAT_EXTRA_ISIZE)。 |
0x80 |
此文件系统具有快照 (RO_COMPAT_HAS_SNAPSHOT)。 |
0x100 |
配额 (RO_COMPAT_QUOTA)。 |
0x200 |
此文件系统支持“bigalloc”,这意味着文件 extent 以簇(块)而不是块为单位跟踪 (RO_COMPAT_BIGALLOC)。 |
0x400 |
此文件系统支持元数据校验和。(RO_COMPAT_METADATA_CSUM;暗示 RO_COMPAT_GDT_CSUM,但不能设置 GDT_CSUM) |
0x800 |
文件系统支持副本。此功能既不在内核中也不在 e2fsprogs 中。(RO_COMPAT_REPLICA) |
0x1000 |
只读文件系统映像;内核不会以读写方式挂载此映像,并且大多数工具会拒绝写入该映像。(RO_COMPAT_READONLY) |
0x2000 |
文件系统跟踪项目配额。(RO_COMPAT_PROJECT) |
0x8000 |
文件系统上可能存在 Verity inode。(RO_COMPAT_VERITY) |
0x10000 |
指示孤立文件可能具有有效的孤立条目,因此我们需要在挂载文件系统时清理它们 (RO_COMPAT_ORPHAN_PRESENT)。 |
s_def_hash_version
字段是以下之一
值 |
描述 |
---|---|
0x0 |
旧版。 |
0x1 |
半 MD4。 |
0x2 |
Tea。 |
0x3 |
旧版,未签名。 |
0x4 |
半 MD4,未签名。 |
0x5 |
Tea,未签名。 |
s_default_mount_opts
字段是以下各项的任意组合
值 |
描述 |
---|---|
0x0001 |
在(重新)挂载时打印调试信息。(EXT4_DEFM_DEBUG) |
0x0002 |
新文件采用包含目录的 gid(而不是当前进程的 fsgid)。(EXT4_DEFM_BSDGROUPS) |
0x0004 |
支持用户空间提供的扩展属性。(EXT4_DEFM_XATTR_USER) |
0x0008 |
支持 POSIX 访问控制列表 (ACL)。(EXT4_DEFM_ACL) |
0x0010 |
不支持 32 位 UID。(EXT4_DEFM_UID16) |
0x0020 |
所有数据和元数据都提交到日志。(EXT4_DEFM_JMODE_DATA) |
0x0040 |
所有数据在元数据提交到日志之前刷新到磁盘。(EXT4_DEFM_JMODE_ORDERED) |
0x0060 |
不保留数据排序;数据可以在元数据写入后写入。(EXT4_DEFM_JMODE_WBACK) |
0x0100 |
禁用写入刷新。(EXT4_DEFM_NOBARRIER) |
0x0200 |
跟踪文件系统中哪些块是元数据,因此不应将其用作数据块。此选项将在 3.18 上默认启用,希望如此。(EXT4_DEFM_BLOCK_VALIDITY) |
0x0400 |
启用 DISCARD 支持,其中会告知存储设备有关块变为未使用的信息。(EXT4_DEFM_DISCARD) |
0x0800 |
禁用延迟分配。(EXT4_DEFM_NODELALLOC) |
s_flags
字段是以下各项的任意组合
值 |
描述 |
---|---|
0x0001 |
正在使用签名的目录哈希。 |
0x0002 |
正在使用未签名的目录哈希。 |
0x0004 |
用于测试开发代码。 |
s_encrypt_algos
列表可以包含以下任何项
值 |
描述 |
---|---|
0 |
无效算法 (ENCRYPTION_MODE_INVALID)。 |
1 |
XTS 模式下的 256 位 AES (ENCRYPTION_MODE_AES_256_XTS)。 |
2 |
GCM 模式下的 256 位 AES 加密 (ENCRYPTION_MODE_AES_256_GCM)。 |
3 |
CBC 模式下的 256 位 AES 加密 (ENCRYPTION_MODE_AES_256_CBC)。 |
超级块的总大小为 1024 字节。
3.2. 块组描述符¶
文件系统上的每个块组都有一个与之关联的描述符。如上面的“布局”部分所述,组描述符(如果存在)是块组中的第二个项。标准配置是每个块组包含块组描述符表的完整副本,除非设置了 sparse_super 功能标志。
请注意,组描述符如何记录位图和 inode 表的位置(即它们可以浮动)。这意味着在块组中,只有超级块和组描述符表是固定位置的数据结构。 flex_bg 机制利用此属性将多个块组分组到一个弹性组中,并将所有组的位图和 inode 表布局到弹性组的第一个组中的一个长运行中。
如果设置了 meta_bg 功能标志,则会将多个块组分组到一个元组中。请注意,在 meta_bg 的情况下,较大的元组中的前两个和最后两个块组仅包含元组内组的组描述符。
flex_bg 和 meta_bg 似乎不是互斥的功能。
在 ext2、ext3 和 ext4(未启用 64 位功能时)中,块组描述符仅长 32 个字节,因此在 bg_checksum 处结束。在启用了 64 位功能的 ext4 文件系统上,块组描述符扩展到至少下面描述的 64 个字节;大小存储在超级块中。
如果设置了 gdt_csum 并且未设置 metadata_csum,则块组校验和是 FS UUID、组号和组描述符结构的 crc16。如果设置了 metadata_csum,则块组校验和是 FS UUID、组号和组描述符结构的校验和的低 16 位。块和 inode 位图校验和都是针对 FS UUID、组号和整个位图计算的。
块组描述符在 struct ext4_group_desc
中布局。
偏移 |
大小 |
名称 |
描述 |
---|---|---|---|
0x0 |
__le32 |
bg_block_bitmap_lo |
块位图位置的低 32 位。 |
0x4 |
__le32 |
bg_inode_bitmap_lo |
inode 位图位置的低 32 位。 |
0x8 |
__le32 |
bg_inode_table_lo |
inode 表位置的低 32 位。 |
0xC |
__le16 |
bg_free_blocks_count_lo |
空闲块计数的低 16 位。 |
0xE |
__le16 |
bg_free_inodes_count_lo |
空闲 inode 计数的低 16 位。 |
0x10 |
__le16 |
bg_used_dirs_count_lo |
目录计数的低 16 位。 |
0x12 |
__le16 |
bg_flags |
块组标志。请参阅下面的 bgflags 表。 |
0x14 |
__le32 |
bg_exclude_bitmap_lo |
快照排除位图位置的低 32 位。 |
0x18 |
__le16 |
bg_block_bitmap_csum_lo |
块位图校验和的低 16 位。 |
0x1A |
__le16 |
bg_inode_bitmap_csum_lo |
inode 位图校验和的低 16 位。 |
0x1C |
__le16 |
bg_itable_unused_lo |
未使用的 inode 计数的低 16 位。如果设置,则无需扫描超过此组的 inode 表中的第 |
0x1E |
__le16 |
bg_checksum |
组描述符校验和;如果设置了 RO_COMPAT_GDT_CSUM 功能,则为 crc16(sb_uuid+group_num+bg_desc),如果设置了 RO_COMPAT_METADATA_CSUM 功能,则为 crc32c(sb_uuid+group_num+bg_desc) & 0xFFFF。在计算 crc16 校验和时跳过 bg_desc 中的 bg_checksum 字段,如果使用 crc32c 校验和,则将其设置为零。 |
仅当启用了 64 位功能且 s_desc_size > 32 时,这些字段才存在。 |
|||
0x20 |
__le32 |
bg_block_bitmap_hi |
块位图位置的高 32 位。 |
0x24 |
__le32 |
bg_inode_bitmap_hi |
inode 位图位置的高 32 位。 |
0x28 |
__le32 |
bg_inode_table_hi |
inode 表位置的高 32 位。 |
0x2C |
__le16 |
bg_free_blocks_count_hi |
空闲块计数的高 16 位。 |
0x2E |
__le16 |
bg_free_inodes_count_hi |
空闲 inode 计数的高 16 位。 |
0x30 |
__le16 |
bg_used_dirs_count_hi |
目录计数的高 16 位。 |
0x32 |
__le16 |
bg_itable_unused_hi |
未使用的 inode 计数的高 16 位。 |
0x34 |
__le32 |
bg_exclude_bitmap_hi |
快照排除位图位置的高 32 位。 |
0x38 |
__le16 |
bg_block_bitmap_csum_hi |
块位图校验和的高 16 位。 |
0x3A |
__le16 |
bg_inode_bitmap_csum_hi |
inode 位图校验和的高 16 位。 |
0x3C |
__u32 |
bg_reserved |
填充到 64 个字节。 |
块组标志可以是以下各项的任意组合
值 |
描述 |
---|---|
0x1 |
inode 表和位图未初始化 (EXT4_BG_INODE_UNINIT)。 |
0x2 |
块位图未初始化 (EXT4_BG_BLOCK_UNINIT)。 |
0x4 |
inode 表已置零 (EXT4_BG_INODE_ZEROED)。 |
3.3. 块和 inode 位图¶
数据块位图跟踪块组内数据块的使用情况。
inode 位图记录 inode 表中哪些条目正在使用中。
与大多数位图一样,一位表示一个数据块或 inode 表条目的使用状态。这意味着块组大小为 8 * 逻辑块中的字节数。
注意:如果为给定的块组设置了 BLOCK_UNINIT
,则内核和 e2fsprogs 代码的各个部分会假装块位图包含零(即组中的所有块都是空闲的)。但是,并非一定没有使用中的块 - 如果设置了 meta_bg
,则位图和组描述符位于组内。不幸的是,ext2fs_test_block_bitmap2() 将为这些位置返回“0”,这会产生令人困惑的 debugfs 输出。
3.4. Inode 表¶
Inode 表在 mkfs 时静态分配。每个块组描述符都指向表的开头,超级块记录每个组的 inode 数量。有关更多信息,请参阅有关 inode 的部分。
3.5. 多重挂载保护¶
多重挂载保护 (MMP) 是一项保护文件系统免受多个主机同时尝试使用文件系统的功能。当打开文件系统(用于挂载或 fsck 等)时,节点(称其为节点 A)上运行的 MMP 代码会检查序列号。如果序列号为 EXT4_MMP_SEQ_CLEAN,则继续打开。如果序列号为 EXT4_MMP_SEQ_FSCK,则 (希望) fsck 正在运行,并且打开立即失败。否则,打开代码将等待两倍的指定 MMP 检查间隔,并再次检查序列号。如果序列号已更改,则表示文件系统在另一台机器上处于活动状态,并且打开失败。如果 MMP 代码通过所有这些检查,则会生成一个新的 MMP 序列号并写入 MMP 块,并且挂载继续进行。
在文件系统处于活动状态时,内核会设置一个计时器,以在指定的 MMP 检查间隔重新检查 MMP 块。要执行重新检查,将重新读取 MMP 序列号;如果它与内存中的 MMP 序列号不匹配,则另一个节点(节点 B)已挂载文件系统,并且节点 A 以只读方式重新挂载文件系统。如果序列号匹配,则内存和磁盘中的序列号都会递增,并且重新检查完成。
只要打开操作成功,主机名和设备文件名就会写入 MMP 块。MMP 代码不使用这些值;它们纯粹是为了提供信息。
针对 FS UUID 和 MMP 结构计算校验和。MMP 结构 (struct mmp_struct
) 如下所示
偏移 |
类型 |
名称 |
描述 |
---|---|---|---|
0x0 |
__le32 |
mmp_magic |
MMP 的魔术数,0x004D4D50 (“MMP”)。 |
0x4 |
__le32 |
mmp_seq |
序列号,定期更新。 |
0x8 |
__le64 |
mmp_time |
上次更新 MMP 块的时间。 |
0x10 |
char[64] |
mmp_nodename |
打开文件系统的节点的主机名。 |
0x50 |
char[32] |
mmp_bdevname |
文件系统的块设备名称。 |
0x70 |
__le16 |
mmp_check_interval |
MMP 重新检查间隔,以秒为单位。 |
0x72 |
__le16 |
mmp_pad1 |
零。 |
0x74 |
__le32[226] |
mmp_pad2 |
零。 |
0x3FC |
__le32 |
mmp_checksum |
MMP 块的校验和。 |
3.6. 日志 (jbd2)¶
在 ext3 中引入的 ext4 文件系统使用日志来保护文件系统在系统崩溃时免受元数据不一致的影响。最多可以在文件系统中保留 10,240,000 个文件系统块(有关日志大小限制的更多详细信息,请参阅 man mke2fs(8)),作为将“重要”数据写入磁盘的最快位置。一旦重要的数据事务完全写入磁盘并从磁盘写入缓存中刷新,也会将提交数据的记录写入日志。在稍后的某个时间点,日志代码会将事务写入它们在磁盘上的最终位置(这可能涉及大量寻道或大量小型读写擦除),然后再擦除提交记录。如果系统在第二次慢速写入期间崩溃,则日志可以一直重放到最新的提交记录,从而保证通过日志写入磁盘的任何内容的原子性。这样做的效果是保证文件系统不会在元数据更新的中途卡住。
出于性能原因,ext4 默认只通过日志写入文件系统元数据。这意味着在崩溃后,无法保证文件数据块处于任何一致的状态。如果此默认保证级别 (data=ordered
) 不令人满意,则可以使用挂载选项来控制日志行为。如果 data=journal
,则所有数据和元数据都会通过日志写入磁盘。这比较慢,但最安全。如果 data=writeback
,则在通过日志将元数据写入磁盘之前,不会将脏数据块刷新到磁盘。
在 data=ordered
模式下,Ext4 还支持快速提交,这有助于显着减少提交延迟。默认的 data=ordered
模式通过将元数据块记录到日志中来工作。在快速提交模式下,Ext4 仅将重新创建受影响的元数据所需的最小增量存储在与 JBD2 共享的快速提交空间中。一旦快速提交区域填满,或者如果快速提交不可行,或者如果 JBD2 提交计时器关闭,Ext4 将执行传统的完整提交。完整提交会使之前发生的所有快速提交失效,从而使快速提交区域为空以进行进一步的快速提交。此功能需要在 mkfs 时启用。
日志 inode 通常是 inode 8。日志 inode 的前 68 个字节在 ext4 超级块中复制。日志本身是文件系统中的普通(但隐藏的)文件。该文件通常会占用整个块组,尽管 mke2fs 尝试将其放置在磁盘的中间位置。
jbd2 中的所有字段都以大端顺序写入磁盘。这与 ext4 相反。
注意:ext4 和 ocfs2 都使用 jbd2。
嵌入在 ext4 文件系统中的日志的最大大小为 2^32 个块。jbd2 本身似乎并不关心。
3.6.1. 布局¶
一般来说,日志具有以下格式
超级块 (Superblock) |
描述符块 (descriptor_block) (数据块 data_blocks 或 撤销块 revocation_block) [更多数据或撤销] 提交块 (commit_block) |
[更多事务...] |
---|---|---|
一个事务 |
请注意,一个事务以描述符和一些数据,或者以块撤销列表开始。一个完成的事务总是以提交结束。如果没有提交记录(或者校验和不匹配),该事务将在重放期间被丢弃。
3.6.2. 外部日志¶
可选地,可以创建一个带有外部日志设备的 ext4 文件系统(而不是使用保留 inode 的内部日志)。在这种情况下,在文件系统设备上,s_journal_inum
应该为零,并且 s_journal_uuid
应该被设置。在日志设备上,通常的位置会有一个 ext4 超级块,并具有匹配的 UUID。日志超级块将位于超级块之后的下一个完整块中。
1024 字节的填充 |
ext4 超级块 |
日志超级块 |
描述符块 (descriptor_block) (数据块 data_blocks 或 撤销块 revocation_block) [更多数据或撤销] 提交块 (commit_block) |
[更多事务...] |
---|---|---|---|---|
一个事务 |
3.6.3. 块头¶
日志中的每个块都以一个通用的 12 字节的头 struct journal_header_s
开始。
偏移 |
类型 |
名称 |
描述 |
---|---|---|---|
0x0 |
__be32 |
h_magic |
jbd2 魔数,0xC03B3998。 |
0x4 |
__be32 |
h_blocktype |
描述此块包含的内容。请参阅下面的 jbd2_blocktype 表。 |
0x8 |
__be32 |
h_sequence |
与此块相关的事务 ID。 |
日志块类型可以是以下任何一种:
值 |
描述 |
---|---|
1 |
描述符。此块位于一系列通过日志写入的数据块之前,这些数据块在事务期间写入。 |
2 |
块提交记录。此块表示事务完成。 |
3 |
日志超级块,v1。 |
4 |
日志超级块,v2。 |
5 |
块撤销记录。这通过使日志能够跳过写入随后被重写过的块来加速恢复。 |
3.6.4. 超级块¶
与 ext4 的超级块相比,日志的超级块要简单得多。其中保留的关键数据是日志的大小,以及在哪里找到事务日志的起始位置。
日志超级块记录为 struct journal_superblock_s
,长度为 1024 字节。
偏移 |
类型 |
名称 |
描述 |
---|---|---|---|
描述日志的静态信息。 |
|||
0x0 |
journal_header_t (12 字节) |
s_header |
标识此为超级块的公共头。 |
0xC |
__be32 |
s_blocksize |
日志设备块大小。 |
0x10 |
__be32 |
s_maxlen |
此日志中的块总数。 |
0x14 |
__be32 |
s_first |
日志信息的第一个块。 |
描述日志当前状态的动态信息。 |
|||
0x18 |
__be32 |
s_sequence |
日志中期望的第一个提交 ID。 |
0x1C |
__be32 |
s_start |
日志起始位置的块号。与注释相反,此字段为零并不表示日志是干净的! |
0x20 |
__be32 |
s_errno |
错误值,由 |
剩余字段仅在 v2 超级块中有效。 |
|||
0x24 |
__be32 |
s_feature_compat; |
兼容功能集。请参阅下面的 jbd2_compat 表。 |
0x28 |
__be32 |
s_feature_incompat |
不兼容的功能集。请参阅下面的 jbd2_incompat 表。 |
0x2C |
__be32 |
s_feature_ro_compat |
只读兼容的功能集。目前没有任何这些功能。 |
0x30 |
__u8 |
s_uuid[16] |
日志的 128 位 uuid。在挂载时,会将其与 ext4 超级块中的副本进行比较。 |
0x40 |
__be32 |
s_nr_users |
共享此日志的文件系统数量。 |
0x44 |
__be32 |
s_dynsuper |
动态超级块副本的位置。(未使用?) |
0x48 |
__be32 |
s_max_transaction |
每个事务的日志块限制。(未使用?) |
0x4C |
__be32 |
s_max_trans_data |
每个事务的数据块限制。(未使用?) |
0x50 |
__u8 |
s_checksum_type |
用于日志的校验和算法。有关更多信息,请参阅 jbd2_checksum_type。 |
0x51 |
__u8[3] |
s_padding2 |
|
0x54 |
__be32 |
s_num_fc_blocks |
日志中快速提交块的数量。 |
0x58 |
__be32 |
s_head |
日志头(第一个未使用的块)的块号,仅当日志为空时才为最新状态。 |
0x5C |
__u32 |
s_padding[40] |
|
0xFC |
__be32 |
s_checksum |
整个超级块的校验和,此字段设置为零。 |
0x100 |
__u8 |
s_users[16*48] |
所有共享日志的文件系统的 ID。e2fsprogs/Linux 不允许共享外部日志,但我认为使用 jbd2 代码的 Lustre (或 ocfs2?) 可能会这样做。 |
日志兼容功能是以下各项的任意组合:
值 |
描述 |
---|---|
0x1 |
日志维护数据块上的校验和。(JBD2_FEATURE_COMPAT_CHECKSUM) |
日志不兼容功能是以下各项的任意组合:
值 |
描述 |
---|---|
0x1 |
日志具有块撤销记录。(JBD2_FEATURE_INCOMPAT_REVOKE) |
0x2 |
日志可以处理 64 位块号。(JBD2_FEATURE_INCOMPAT_64BIT) |
0x4 |
日志异步提交。(JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT) |
0x8 |
此日志使用磁盘校验和格式的 v2 版本。每个日志元数据块都有自己的校验和,并且描述符表中的块标签包含日志中每个数据块的校验和。(JBD2_FEATURE_INCOMPAT_CSUM_V2) |
0x10 |
此日志使用磁盘校验和格式的 v3 版本。这与 v2 相同,但日志块标签大小是固定的,与块号大小无关。(JBD2_FEATURE_INCOMPAT_CSUM_V3) |
0x20 |
日志具有快速提交块。(JBD2_FEATURE_INCOMPAT_FAST_COMMIT) |
日志校验和类型代码是以下之一。crc32 或 crc32c 是最有可能的选择。
值 |
描述 |
---|---|
1 |
CRC32 |
2 |
MD5 |
3 |
SHA1 |
4 |
CRC32C |
3.6.5. 描述符块¶
描述符块包含一个日志块标签数组,这些标签描述了日志中后续数据块的最终位置。描述符块是开放编码的,而不是由数据结构完全描述,但无论如何,这里是块结构。描述符块至少消耗 36 个字节,但使用一个完整的块。
偏移 |
类型 |
名称 |
描述符 |
---|---|---|---|
0x0 |
journal_header_t |
(开放编码) |
公共块头。 |
0xC |
struct journal_block_tag_s |
开放编码数组[] |
足够的标签来填充块或描述此描述符块之后的所有数据块。 |
日志块标签具有以下任何格式,具体取决于设置的日志功能和块标签标志。
如果设置了 JBD2_FEATURE_INCOMPAT_CSUM_V3,则日志块标签定义为 struct journal_block_tag3_s
,其外观如下。大小为 16 或 32 字节。
偏移 |
类型 |
名称 |
描述符 |
---|---|---|---|
0x0 |
__be32 |
t_blocknr |
对应数据块应在磁盘上结束位置的低 32 位。 |
0x4 |
__be32 |
t_flags |
与描述符一起使用的标志。有关更多信息,请参阅 jbd2_tag_flags 表。 |
0x8 |
__be32 |
t_blocknr_high |
对应数据块应在磁盘上结束位置的高 32 位。如果未启用 JBD2_FEATURE_INCOMPAT_64BIT,则此值为零。 |
0xC |
__be32 |
t_checksum |
日志 UUID、序列号和数据块的校验和。 |
此字段似乎是开放编码的。它始终位于标签的末尾,在 t_checksum 之后。如果设置了“相同 UUID”标志,则此字段不存在。 |
|||
0x8 或 0xC |
char |
uuid[16] |
与此标签一起使用的 UUID。此字段似乎是从 |
日志标签标志是以下各项的任意组合:
值 |
描述 |
---|---|
0x1 |
磁盘上的块被转义。数据块的前四个字节恰好与 jbd2 魔数匹配。 |
0x2 |
此块与之前的 UUID 相同,因此省略了 UUID 字段。 |
0x4 |
数据块已通过事务删除。(未使用?) |
0x8 |
这是此描述符块中的最后一个标签。 |
如果未设置 JBD2_FEATURE_INCOMPAT_CSUM_V3,则日志块标签定义为 struct journal_block_tag_s
,其外观如下。大小为 8、12、24 或 28 字节。
偏移 |
类型 |
名称 |
描述符 |
---|---|---|---|
0x0 |
__be32 |
t_blocknr |
对应数据块应在磁盘上结束位置的低 32 位。 |
0x4 |
__be16 |
t_checksum |
日志 UUID、序列号和数据块的校验和。请注意,仅存储较低的 16 位。 |
0x6 |
__be16 |
t_flags |
与描述符一起使用的标志。有关更多信息,请参阅 jbd2_tag_flags 表。 |
仅当超级块指示支持 64 位块号时,才存在下一个字段。 |
|||
0x8 |
__be32 |
t_blocknr_high |
对应数据块应在磁盘上结束位置的高 32 位。 |
此字段似乎是开放编码的。它始终位于标签的末尾,在 t_flags 或 t_blocknr_high 之后。如果设置了“相同 UUID”标志,则此字段不存在。 |
|||
0x8 或 0xC |
char |
uuid[16] |
与此标签一起使用的 UUID。此字段似乎是从 |
如果设置了 JBD2_FEATURE_INCOMPAT_CSUM_V2 或 JBD2_FEATURE_INCOMPAT_CSUM_V3,则块的末尾是 struct jbd2_journal_block_tail
,其外观如下
偏移 |
类型 |
名称 |
描述符 |
---|---|---|---|
0x0 |
__be32 |
t_checksum |
日志 UUID + 描述符块的校验和,此字段设置为零。 |
3.6.6. 数据块¶
通常,通过日志写入到磁盘的数据块在描述符块之后按原样写入到日志文件中。但是,如果块的前四个字节与 jbd2 魔数匹配,则会将这四个字节替换为零,并在描述符块标签中设置“转义”标志。
3.6.7. 撤销块¶
撤销块用于防止在早期事务中重放块。这用于标记曾经记录在日志中但不再记录在日志中的块。通常,如果元数据块被释放并重新分配为文件数据块,则会发生这种情况;在这种情况下,在将文件块写入磁盘后,日志重放会导致损坏。
注意:此机制不用于表达“此日志块被此其他日志块取代”,正如作者 (djwong) 错误地认为的那样。添加到事务的任何块都将导致删除该块的所有现有撤销记录。
撤销块在 struct jbd2_journal_revoke_header_s
中描述,至少为 16 字节长,但使用一个完整的块
偏移 |
类型 |
名称 |
描述 |
---|---|---|---|
0x0 |
journal_header_t |
r_header |
公共块头。 |
0xC |
__be32 |
r_count |
此块中使用的字节数。 |
0x10 |
__be32 或 __be64 |
blocks[0] |
要撤销的块。 |
在 r_count 之后是一个线性块号数组,这些块号在此事务中被有效撤销。如果超级块声明支持 64 位块号,则每个块号的大小为 8 个字节,否则为 4 个字节。
如果设置了 JBD2_FEATURE_INCOMPAT_CSUM_V2 或 JBD2_FEATURE_INCOMPAT_CSUM_V3,则撤销块的末尾是 struct jbd2_journal_revoke_tail
,其格式如下
偏移 |
类型 |
名称 |
描述 |
---|---|---|---|
0x0 |
__be32 |
r_checksum |
日志 UUID + 撤销块的校验和 |
3.6.8. 提交块¶
提交块是一个哨兵,指示事务已完全写入日志。一旦此提交块到达日志,则存储在此事务中的数据就可以写入其在磁盘上的最终位置。
提交块由 struct commit_header
描述,其长度为 32 字节(但使用一个完整的块)。
偏移 |
类型 |
名称 |
描述符 |
---|---|---|---|
0x0 |
journal_header_s |
(开放编码) |
公共块头。 |
0xC |
unsigned char |
h_chksum_type |
用于验证事务中数据块完整性的校验和类型。有关更多信息,请参阅 jbd2_checksum_type。 |
0xD |
unsigned char |
h_chksum_size |
校验和使用的字节数。最可能是 4。 |
0xE |
unsigned char |
h_padding[2] |
|
0x10 |
__be32 |
h_chksum[JBD2_CHECKSUM_BYTES] |
用于存储校验和的 32 字节空间。如果设置了 JBD2_FEATURE_INCOMPAT_CSUM_V2 或 JBD2_FEATURE_INCOMPAT_CSUM_V3,则第一个 |
0x30 |
__be64 |
h_commit_sec |
事务提交的时间,以自纪元以来的秒数表示。 |
0x38 |
__be32 |
h_commit_nsec |
上述时间戳的纳秒部分。 |
3.6.9. 快速提交¶
快速提交区域组织为标签长度值的日志。每个 TLV 的开头都有一个 struct ext4_fc_tl
,其中存储了整个字段的标签和长度。其后是可变长度的特定于标签的值。以下是支持的标签及其含义的列表
标签 |
含义 |
值结构体 |
描述 |
---|---|---|---|
EXT4_FC_TAG_HEAD |
快速提交区域头部 |
|
存储这些快速提交应应用后的事务 TID。 |
EXT4_FC_TAG_ADD_RANGE |
向 inode 添加范围 |
|
存储要在此 inode 中添加的 inode 编号和范围 |
EXT4_FC_TAG_DEL_RANGE |
删除 inode 的逻辑偏移 |
|
存储需要删除的 inode 编号和逻辑偏移范围 |
EXT4_FC_TAG_CREAT |
为新创建的文件创建目录条目 |
|
存储新创建文件的父 inode 编号、inode 编号和目录条目 |
EXT4_FC_TAG_LINK |
将目录条目链接到 inode |
|
存储父 inode 编号、inode 编号和目录条目 |
EXT4_FC_TAG_UNLINK |
取消链接 inode 的目录条目 |
|
存储父 inode 编号、inode 编号和目录条目 |
EXT4_FC_TAG_PAD |
填充(未使用的区域) |
无 |
快速提交区域中未使用的字节。 |
EXT4_FC_TAG_TAIL |
标记快速提交的结尾 |
|
存储提交的 TID,此标签表示其结尾的快速提交的 CRC |
3.6.10. 快速提交重放幂等性¶
如果恢复代码遵循某些规则,则快速提交标签本质上是幂等的。提交路径在提交时遵循的指导原则是,它存储特定操作的结果,而不是存储过程。
让我们考虑这个重命名操作:“mv /a /b”。假设 dirent '/a' 与 inode 10 相关联。在快速提交期间,我们不将此操作存储为过程“将 a 重命名为 b”,而是将结果文件系统状态存储为一系列“结果”
将 dirent b 链接到 inode 10
取消链接 dirent a
inode 10 具有有效的引用计数
现在,当恢复代码运行时,它需要在文件系统上“强制执行”此状态。这就是保证快速提交重放的幂等性的原因。
让我们以一个非幂等过程为例,看看快速提交如何使其幂等。考虑以下操作序列
rm A
mv B A
read A
如果我们按原样存储此操作序列,则重放不是幂等的。假设在重放时,我们在 (2) 之后崩溃。在第二次重放期间,文件 A(实际上是作为“mv B A”操作的结果创建的)将被删除。因此,当我们尝试读取 A 时,名为 A 的文件将不存在。因此,此操作序列不是幂等的。但是,如上所述,快速提交不是存储过程,而是存储每个过程的结果。因此,上述过程的快速提交日志如下
(假设在重放之前,dirent A 链接到 inode 10,dirent B 链接到 inode 11)
取消链接 A
将 A 链接到 inode 11
取消链接 B
inode 11
如果我们崩溃在 (3) 之后,我们将有文件 A 链接到 inode 11。在第二次重放期间,我们将删除文件 A(inode 11)。但是我们将重新创建它并使其指向 inode 11。我们找不到 B,因此我们将跳过该步骤。此时,inode 11 的引用计数不可靠,但这可以通过重放最后一个 inode 11 标签来修复。因此,通过将非幂等过程转换为一系列幂等结果,快速提交确保了重放期间的幂等性。
3.6.11. 日志检查点¶
对日志进行检查点操作可确保所有事务及其关联的缓冲区都已提交到磁盘。正在进行的事务将被等待并包含在检查点中。检查点在对文件系统进行关键更新(包括日志恢复、文件系统大小调整和释放 journal_t 结构)时在内部使用。
可以通过 ioctl EXT4_IOC_CHECKPOINT 从用户空间触发日志检查点。此 ioctl 采用单个 u64 参数作为标志。当前支持三个标志。首先,EXT4_IOC_CHECKPOINT_FLAG_DRY_RUN 可用于验证 ioctl 的输入。如果存在任何无效输入,则会返回错误,否则会返回成功,而不会执行任何检查点操作。这可用于检查系统上是否存在 ioctl,并验证参数或标志是否存在问题。另外两个标志是 EXT4_IOC_CHECKPOINT_FLAG_DISCARD 和 EXT4_IOC_CHECKPOINT_FLAG_ZEROOUT。这些标志分别导致日志块在日志检查点完成后被丢弃或用零填充。EXT4_IOC_CHECKPOINT_FLAG_DISCARD 和 EXT4_IOC_CHECKPOINT_FLAG_ZEROOUT 不能同时设置。当快照系统或为了符合内容删除 SLO 时,ioctl 可能很有用。
3.7. 孤立文件¶
在 unix 中,可能存在从目录层次结构取消链接但由于它们是打开的而仍然存活的 inode。在发生崩溃的情况下,文件系统必须清理这些 inode,否则它们(以及它们引用的块)将泄漏。类似地,如果我们截断或扩展文件,我们需要不能在单个日志事务中执行该操作。在这种情况下,我们将 inode 跟踪为孤立 inode,以便在发生崩溃时截断分配给文件的额外块。
传统上,ext4 以单链表的形式跟踪孤立 inode,其中超级块包含最后一个孤立 inode 的 inode 编号(s_last_orphan 字段),然后每个 inode 包含先前孤立的 inode 的 inode 编号(我们为此重载 i_dtime inode 字段)。但是,对于导致大量创建孤立 inode 的工作负载,此文件系统全局单链表是一个可伸缩性瓶颈。启用孤立文件功能 (COMPAT_ORPHAN_FILE) 后,文件系统有一个特殊的 inode(通过 s_orphan_file_inum 从超级块引用),其中包含多个块。这些块中的每一个都具有以下结构
偏移 |
类型 |
名称 |
描述 |
---|---|---|---|
0x0 |
__le32 条目数组 |
孤立 inode 条目 |
每个 __le32 条目要么为空 (0),要么包含孤立 inode 的 inode 编号。 |
块大小 - 8 |
__le32 |
ob_magic |
存储在孤立块尾部的魔术值 (0x0b10ca04) |
块大小 - 4 |
__le32 |
ob_checksum |
孤立块的校验和。 |
当可写地挂载具有孤立文件功能的文件系统时,我们在超级块中设置 RO_COMPAT_ORPHAN_PRESENT 功能,以指示可能存在有效的孤立条目。如果我们在挂载文件系统时看到此功能,我们将读取整个孤立文件,并像往常一样处理那里找到的所有孤立 inode。当干净地卸载文件系统时,我们会删除 RO_COMPAT_ORPHAN_PRESENT 功能,以避免不必要地扫描孤立文件,并使文件系统与较旧的内核完全兼容。