什么是 Flash-Friendly File System (F2FS)?¶
基于 NAND 闪存的存储设备,例如 SSD、eMMC 和 SD 卡,已配备在从移动系统到服务器系统的各种系统中。由于已知它们与传统的旋转磁盘具有不同的特性,因此文件系统(存储设备的上层)应该适应设计层面的变化。
F2FS 是一种利用基于 NAND 闪存的存储设备的文件系统,它基于日志结构文件系统 (LFS)。该设计侧重于解决 LFS 中的基本问题,即游走树的雪球效应和高清理开销。
由于基于 NAND 闪存的存储设备根据其内部几何形状或闪存管理方案(即 FTL)显示出不同的特性,因此 F2FS 及其工具支持各种参数,不仅用于配置磁盘布局,还用于选择分配和清理算法。
以下 git 树提供了文件系统格式化工具 (mkfs.f2fs)、一致性检查工具 (fsck.f2fs) 和调试工具 (dump.f2fs)。
git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs-tools.git
对于发送补丁,请使用以下邮件列表
对于报告错误,请使用以下 f2fs bug 跟踪器链接
背景和设计问题¶
日志结构文件系统 (LFS)¶
“日志结构文件系统以日志状结构按顺序将所有修改写入磁盘,从而加快文件写入和崩溃恢复。日志是磁盘上唯一的结构;它包含索引信息,以便可以有效地从日志中读取文件。为了在磁盘上保持大的可用区域以进行快速写入,我们将日志划分为段,并使用段清理器来压缩来自严重碎片化段的实时信息。” 来自 Rosenblum, M. 和 Ousterhout, J. K., 1992, “日志结构文件系统的设计与实现”, ACM Trans. Computer Systems 10, 1, 26–52.
游走树问题¶
在 LFS 中,当文件数据被更新并写入日志末尾时,由于位置发生变化,其直接指针块会被更新。然后,间接指针块也会因直接指针块更新而更新。以这种方式,诸如 inode、inode 映射和检查点块之类的上层索引结构也会递归更新。这个问题被称为游走树问题 [1],为了提高性能,应该尽可能消除或放松更新传播。
[1] Bityutskiy, A. 2005. JFFS3 设计问题. http://www.linux-mtd.infradead.org/
清理开销¶
由于 LFS 基于异地写入,因此会产生许多分散在整个存储中的过时块。为了服务于新的空日志空间,它需要无缝地回收这些过时块以供用户使用。这项工作称为清理过程。
该过程包括以下三个操作。
通过引用段使用情况表来选择牺牲段。
它加载由段摘要块标识的牺牲段中所有数据的父索引结构。
它检查数据及其父索引结构之间的交叉引用。
它有选择地移动有效数据。
此清理工作可能会导致意外的长时间延迟,因此最重要的目标是向用户隐藏延迟。而且,绝对应该减少要移动的有效数据量,并尽快移动它们。
主要特点¶
闪存感知¶
扩大随机写入区域以获得更好的性能,但提供高空间局部性
尽最大努力将 FS 数据结构与 FTL 中的操作单元对齐
游走树问题¶
使用术语“节点”来表示 inode 以及各种指针块
引入包含所有“节点”块位置的节点地址表 (NAT);这将切断更新传播。
清理开销¶
支持后台清理过程
支持贪婪和成本效益算法的牺牲段选择策略
支持多头日志以分离静态/动态热数据和冷数据
引入自适应日志记录以实现高效的块分配
挂载选项¶
background_gc=%s |
打开/关闭清理操作,即在 I/O 子系统空闲时在后台触发的垃圾回收。如果 background_gc=on,它将打开垃圾回收,如果 background_gc=off,垃圾回收将被关闭。如果 background_gc=sync,它将打开在后台运行的同步垃圾回收。此选项的默认值为 on。因此,默认情况下垃圾回收是打开的。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
gc_merge |
当 background_gc 打开时,可以启用此选项以让后台 GC 线程处理前台 GC 请求,它可以消除由慢速前台 GC 操作引起的缓慢问题,尤其当 GC 是由具有有限 I/O 和 CPU 资源的进程触发时。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
nogc_merge |
禁用 GC 合并功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
disable_roll_forward |
禁用前滚恢复例程 |
||||||||||||||||||||||||||||||||||||||||||||||||||
norecovery |
禁用前滚恢复例程,以只读方式挂载(即,-o ro,disable_roll_forward) |
||||||||||||||||||||||||||||||||||||||||||||||||||
discard/nodiscard |
启用/禁用 f2fs 中的实时丢弃,如果启用了 discard,则 f2fs 将在清理段时发出 discard/TRIM 命令。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
heap/no_heap |
已弃用。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
nouser_xattr |
禁用扩展用户属性。注意:如果选择了 CONFIG_F2FS_FS_XATTR,则默认启用 xattr。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
noacl |
禁用 POSIX 访问控制列表。注意:如果选择了 CONFIG_F2FS_FS_POSIX_ACL,则默认启用 acl。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
active_logs=%u |
支持配置活动日志的数量。在当前设计中,f2fs 仅支持 2、4 和 6 个日志。默认数量为 6。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
disable_ext_identify |
禁用由 mkfs 配置的扩展列表,因此 f2fs 不知道冷文件,例如媒体文件。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
inline_xattr |
启用内联 xattrs 功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
noinline_xattr |
禁用内联 xattrs 功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
inline_xattr_size=%u |
支持配置内联 xattr 大小,它取决于灵活的内联 xattr 功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
inline_data |
启用内联数据功能:新创建的小 (<~3.4k) 文件可以写入 inode 块。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
inline_dentry |
启用内联目录功能:新创建的目录条目中的数据可以写入 inode 块。用于存储内联目录项的 inode 块空间限制为 ~3.4k。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
noinline_dentry |
禁用内联目录条目功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
flush_merge |
尽可能合并并发的 cache_flush 命令,以消除冗余命令问题。如果底层设备处理 cache_flush 命令相对较慢,建议启用此选项。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
nobarrier |
如果底层存储保证其缓存数据应写入非易失性区域,则可以使用此选项。如果设置了此选项,则不会发出 cache_flush 命令,但 f2fs 仍保证所有数据写入的写入顺序。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
barrier |
如果设置了此选项,则允许发出 cache_flush 命令。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
fastboot |
当系统希望尽可能减少挂载时间时,可以使用此选项,即使正常的性能可能会受到影响。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
extent_cache |
启用基于 rb-tree 的 extent 缓存,它可以缓存每个 inode 中连续逻辑地址和物理地址之间的 extent 映射,从而提高缓存命中率。默认设置。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
noextent_cache |
显式禁用基于 rb-tree 的 extent 缓存,请参阅上面的 extent_cache 挂载选项。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
noinline_data |
禁用内联数据功能,默认情况下启用内联数据功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
data_flush |
在检查点之前启用数据刷新,以便持久化常规和符号链接的数据。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
reserve_root=%d |
支持配置保留空间,该空间用于具有指定 uid 或 gid 的特权用户的分配,单位:4KB,默认限制为用户块的 0.2%。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
resuid=%d |
可以使用保留块的用户 ID。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
resgid=%d |
可以使用保留块的组 ID。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
fault_injection=%d |
以指定的注入率在所有支持的类型中启用故障注入。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
fault_type=%d |
支持配置故障注入类型,应与 fault_injection 选项一起启用,故障类型值如下所示,它支持单个或组合类型。
|
||||||||||||||||||||||||||||||||||||||||||||||||||
mode=%s |
控制块分配模式,它支持“adaptive”和“lfs”。在“lfs”模式下,不应对主区域进行随机写入。“fragment:segment”和“fragment:block”是此处新添加的。这些是开发人员选项,用于实验以模拟文件系统碎片/GC 后的情况本身。开发人员使用这些模式来很好地了解文件系统碎片/GC 后的情况,并最终获得一些见解来更好地处理它们。在“fragment:segment”中,f2fs 在随机位置分配一个新的段。通过这样做,我们可以模拟 GC 后的情况。在“fragment:block”中,我们可以使用“max_fragment_chunk”和“max_fragment_hole”sysfs 节点分散块分配。我们向块大小和孔大小添加了一些随机性,以使其接近真实的 IO 模式。因此,在此模式下,f2fs 将以块形式分配 1..<max_fragment_chunk> 块,并在长度为 1..<max_fragment_hole> 的孔中依次分配。通过这样做,新分配的块将分散在整个分区中。请注意,“fragment:block”隐式启用“fragment:segment”选项以获得更多随机性。请将这些选项用于您的实验,我们强烈建议在使用这些选项后重新格式化文件系统。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
usrquota |
启用普通用户磁盘配额记帐。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
grpquota |
启用普通组磁盘配额记帐。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
prjquota |
启用普通项目配额记帐。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
usrjquota=<文件> |
在挂载期间指定文件和类型,以便配额 |
||||||||||||||||||||||||||||||||||||||||||||||||||
grpjquota=<文件> |
信息可以在恢复流程中正确更新, |
||||||||||||||||||||||||||||||||||||||||||||||||||
prjjquota=<文件> |
<配额文件>:必须位于根目录中; |
||||||||||||||||||||||||||||||||||||||||||||||||||
jqfmt=<配额类型> |
<配额类型>:[vfsold,vfsv0,vfsv1]。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
offusrjquota |
关闭用户日志配额。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
offgrpjquota |
关闭组日志配额。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
offprjjquota |
关闭项目日志配额。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
quota |
启用普通用户磁盘配额记帐。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
noquota |
禁用所有普通磁盘配额选项。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
alloc_mode=%s |
调整块分配策略,它支持“reuse”和“default”。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
fsync_mode=%s |
控制 fsync 的策略。目前支持“posix”、“strict”和“nobarrier”。在“posix”模式下(默认模式),fsync 将遵循 POSIX 语义并执行轻量级操作以提高文件系统性能。在“strict”模式下,fsync 将是繁重的,并且其行为与 xfs、ext4 和 btrfs 一致,其中 xfstest generic/342 将通过,但性能将倒退。“nobarrier”基于“posix”,但不像“nobarrier”挂载选项那样为非原子文件发出 flush 命令。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
test_dummy_encryption |
|||||||||||||||||||||||||||||||||||||||||||||||||||
test_dummy_encryption=%s |
启用虚拟加密,它提供了一个虚假的 fscrypt 上下文。xfstests 使用虚假的 fscrypt 上下文。该参数可以是“v1”或“v2”,以便选择相应的 fscrypt 策略版本。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
checkpoint=%s[:%u[%]] |
设置为“disable”以关闭检查点。设置为“enable”以重新启用检查点。默认启用。禁用后,任何卸载或意外关闭都将导致文件系统内容显示为使用该选项挂载文件系统时的状态。使用 checkpoint=disable 挂载时,文件系统必须运行垃圾回收以确保可以使用所有可用空间。如果这花费太长时间,挂载可能会返回 EAGAIN。您可以选择添加一个值以指示您愿意暂时放弃多少磁盘空间以避免额外的垃圾回收。可以将其指定为块数或百分比。例如,使用 checkpoint=disable:100% 挂载将始终成功,但它可能会隐藏多达所有剩余的可用空间。无法使用的实际空间可以在 /sys/fs/f2fs/<disk>/unusable 中查看。一旦 checkpoint=enable,此空间将被回收。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
checkpoint_merge |
当启用检查点时,可以使用此选项创建一个内核守护程序,并使其尽可能合并并发的检查点请求,以消除冗余的检查点问题。此外,当在 cgroup 中具有低 i/o 预算和 cpu 份额的进程上下文中完成检查点时,我们可以消除由慢速检查点操作引起的缓慢问题。为了使其做得更好,我们将内核守护程序的默认 i/o 优先级设置为“3”,以使其比其他内核线程具有更高的优先级。这与为 ext4 文件系统的 jbd2 日志线程提供 I/O 优先级的方式相同。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
nocheckpoint_merge |
禁用检查点合并功能。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_algorithm=%s |
控制压缩算法,目前 f2fs 支持“lzo”、“lz4”、“zstd”和“lzo-rle”算法。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_algorithm=%s:%d |
控制压缩算法及其压缩级别,现在,只有“lz4”和“zstd”支持压缩级别配置。算法级别范围 lz4 3 - 16 zstd 1 - 22 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_log_size=%u |
支持配置压缩簇大小。大小将为 4KB * (1 << %u)。默认大小和最小大小为 16KB。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_extension=%s |
支持添加指定的扩展名,以便 f2fs 可以在那些相应的文件上启用压缩,例如,如果所有带有“.ext”的文件都具有高压缩率,我们可以将“.ext”设置在压缩扩展名列表中,并默认在这些文件上启用压缩,而不是通过 ioctl 启用。对于其他文件,我们仍然可以通过 ioctl 启用压缩。请注意,有一个保留的特殊扩展名“*”,可以将其设置为对所有文件启用压缩。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
nocompress_extension=%s |
支持添加指定的扩展名,以便 f2fs 可以在那些相应的文件上禁用压缩,这与压缩扩展名相反。如果您确切知道哪些文件无法压缩,则可以使用此选项。相同的扩展名不能同时出现在压缩扩展名和 nocompress 扩展名中。如果压缩扩展名指定了所有文件,则 nocompress 扩展名指定的类型将被视为特殊情况,并且不会被压缩。不允许使用“*”来指定 nocompress 扩展名中的所有文件。添加 nocompress_extension 后,优先级应为:dir_flag < comp_extention,nocompress_extension < comp_file_flag,no_comp_file_flag。请在压缩部分中查看更多内容。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_chksum |
支持验证压缩簇中原始数据的校验和。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_mode=%s |
控制文件压缩模式。这支持“fs”和“user”模式。在“fs”模式(默认模式)下,f2fs 对启用了压缩的文件执行自动压缩。在“user”模式下,f2fs 禁用自动压缩,并让用户自行决定选择目标文件和时间。用户可以使用 ioctl 对启用了压缩的文件执行手动压缩/解压缩。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
compress_cache |
支持使用文件系统管理的 inode 的地址空间来缓存压缩块,以便提高随机读取的缓存命中率。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
inlinecrypt |
如果可能,使用 blk-crypto 框架而不是文件系统层加密来加密/解密加密文件的内容。这允许使用内联加密硬件。磁盘格式不受影响。有关更多详细信息,请参阅内联加密。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
atgc |
启用年龄阈值垃圾回收,它为后台 GC 提供了高效率和效益。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
discard_unit=%s |
控制丢弃单元,参数可以是“block”、“segment”和“section”,发出的丢弃命令的偏移量/大小将与该单元对齐,默认情况下设置“discard_unit=block”,以便启用小丢弃功能。对于 blkzoned 设备,默认情况下将设置“discard_unit=section”,这有助于大型 SMR 或 ZNS 设备通过消除文件系统元数据支持小丢弃来降低内存成本。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
memory=%s |
控制内存模式。这支持“normal”和“low”模式。“low”模式是为了支持低内存设备而引入的。由于低内存设备的特性,在此模式下,f2fs 将尝试通过牺牲性能来节省内存。“normal”模式是默认模式,与以前相同。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
age_extent_cache |
启用基于 rb-tree 的年龄 extent 缓存。它记录每个 inode 的 extent 的数据块更新频率,以便为数据块分配提供更好的温度提示。 |
||||||||||||||||||||||||||||||||||||||||||||||||||
errors=%s |
指定 f2fs 在发生严重错误时的行为。这支持以下模式:“panic”、“continue”和“remount-ro”,分别立即触发 panic、继续而不做任何事情以及以只读模式重新挂载分区。默认情况下,它使用“continue”模式。====================== =============== =============== ======== 模式 continue remount-ro panic ====================== =============== =============== ======== 访问操作 normal normal N/A 系统调用错误 -EIO -EROFS N/A 挂载选项 rw ro N/A 挂起的目录写入 keep keep N/A 挂起的非目录写入 drop keep N/A 挂起的节点写入 drop keep N/A 挂起的元数据写入 keep keep N/A ====================== =============== =============== ======== |
||||||||||||||||||||||||||||||||||||||||||||||||||
nat_bits |
启用 nat_bits 功能以增强完整/空 nat 块的访问,默认情况下禁用该功能。 |
Debugfs 条目¶
/sys/kernel/debug/f2fs/ 包含有关所有作为 f2fs 挂载的分区的信息。每个文件显示整个 f2fs 信息。
/sys/kernel/debug/f2fs/status 包括
当前由 f2fs 管理的主要文件系统信息
有关整个段的平均 SIT 信息
f2fs 当前消耗的内存占用。
Sysfs 条目¶
可以在 /sys/fs/f2fs 中找到有关已挂载的 f2fs 文件系统的信息。每个已挂载的文件系统在 /sys/fs/f2fs 中都有一个目录,该目录基于其设备名称(即,/sys/fs/f2fs/sda)。每个设备目录中的文件如下表所示。
/sys/fs/f2fs/<devname> 中的文件(另请参阅ABI 文件测试/sysfs-fs-f2fs)
用法¶
下载用户空间工具并编译它们。
如果 f2fs 是在内核中静态编译的,则跳过。否则,插入 f2fs.ko 模块
# insmod f2fs.ko
创建一个用于挂载的目录
# mkdir /mnt/f2fs
格式化块设备,然后挂载为 f2fs
# mkfs.f2fs -l label /dev/block_device # mount -t f2fs /dev/block_device /mnt/f2fs
mkfs.f2fs¶
mkfs.f2fs 用于将分区格式化为 f2fs 文件系统,该文件系统构建基本的磁盘布局。
快速选项包括
|
给出一个卷标,最多 512 个 unicode 名称。 |
|
拆分每个区域的起始位置以进行基于堆的分配。 默认设置为 1,表示执行此操作。 |
|
设置卷大小的过度配置比率(以百分比表示)。 默认设置为 5。 |
|
设置每个段的段数。 默认设置为 1。 |
|
设置每个区域的段数。 默认设置为 1。 |
|
设置基本扩展名列表。例如“mp3,gif,mov” |
|
禁用丢弃命令与否。 默认设置为 1,表示执行丢弃。 |
注意:请参阅 mkfs.f2fs(8) 的手册页以获取完整的选项列表。
fsck.f2fs¶
fsck.f2fs 是一种用于检查 f2fs 格式化分区一致性的工具,它检查文件系统元数据和用户生成的数据是否正确交叉引用。请注意,该工具的初始版本不会修复任何不一致。
快速选项包括
-d debug level [default:0]
注意:请参阅 fsck.f2fs(8) 的手册页以获取完整的选项列表。
dump.f2fs¶
dump.f2fs 显示特定 inode 的信息,并将 SSA 和 SIT 转储到文件中。每个文件分别是 dump_ssa 和 dump_sit。
dump.f2fs 用于调试 f2fs 文件系统的磁盘数据结构。它显示由给定 inode 编号识别的磁盘 inode 信息,并且能够将所有 SSA 和 SIT 条目转储到预定义的文件 ./dump_ssa 和 ./dump_sit 中。
选项包括
-d debug level [default:0]
-i inode no (hex)
-s [SIT dump segno from #1~#2 (decimal), for all 0~-1]
-a [SSA dump segno from #1~#2 (decimal), for all 0~-1]
示例
# dump.f2fs -i [ino] /dev/sdx
# dump.f2fs -s 0~-1 /dev/sdx (SIT dump)
# dump.f2fs -a 0~-1 /dev/sdx (SSA dump)
注意:请参阅 dump.f2fs(8) 的手册页以获取完整的选项列表。
sload.f2fs¶
sload.f2fs 提供了一种将文件和目录插入到现有磁盘映像中的方法。在构建给定编译文件的 f2fs 映像时,此工具非常有用。
注意:请参阅 sload.f2fs(8) 的手册页以获取完整的选项列表。
resize.f2fs¶
resize.f2fs 允许用户调整 f2fs 格式化的磁盘映像的大小,同时保留存储在映像中的所有文件和目录。
注意:请参阅 resize.f2fs(8) 的手册页以获取完整的选项列表。
defrag.f2fs¶
defrag.f2fs 可用于对磁盘上分散写入的数据以及文件系统元数据进行碎片整理。这可以通过提供更多连续的可用空间来提高写入速度。
注意:请参阅 defrag.f2fs(8) 的手册页以获取完整的选项列表。
f2fs_io¶
f2fs_io 是一个简单的工具,用于发出各种文件系统 API 以及 f2fs 特定的 API,这对于 QA 测试非常有用。
注意:请参阅 f2fs_io(8) 的手册页以获取完整的选项列表。
设计¶
磁盘布局¶
F2FS 将整个卷划分为多个段,每个段的大小固定为 2MB。一个扇区由连续的段组成,一个区域由一组扇区组成。默认情况下,扇区大小和区域大小被设置为相同的段大小,但是用户可以通过 mkfs 轻松地修改这些大小。
F2FS 将整个卷划分为六个区域,除了超级块之外的所有区域都由多个段组成,如下所述
align with the zone size <-|
|-> align with the segment size
_________________________________________________________________________
| | | Segment | Node | Segment | |
| Superblock | Checkpoint | Info. | Address | Summary | Main |
| (SB) | (CP) | Table (SIT) | Table (NAT) | Area (SSA) | |
|____________|_____2______|______N______|______N______|______N_____|__N___|
. .
. .
. .
._________________________________________.
|_Segment_|_..._|_Segment_|_..._|_Segment_|
. .
._________._________
|_section_|__...__|_
. .
.________.
|__zone__|
- 超级块 (SB)
它位于分区的开头,并且存在两个副本以避免文件系统崩溃。它包含基本分区信息和 f2fs 的一些默认参数。
- 检查点 (CP)
它包含文件系统信息、有效 NAT/SIT 集的位图、孤立 inode 列表以及当前活动段的摘要条目。
- 段信息表 (SIT)
它包含段信息,例如有效块计数和所有块有效性的位图。
- 节点地址表 (NAT)
它由存储在主区域中的所有节点块的块地址表组成。
- 段摘要区域 (SSA)
它包含摘要条目,该条目包含存储在主区域中的所有数据和节点块的所有者信息。
- 主区域
它包含文件和目录数据,包括它们的索引。
为了避免文件系统和基于闪存的存储之间的未对齐,F2FS 将 CP 的起始块地址与段大小对齐。此外,它通过在 SSA 区域中保留一些段来将主区域的起始块地址与区域大小对齐。
有关其他技术细节,请参阅以下调查。https://wiki.linaro.org/WorkingGroups/Kernel/Projects/FlashCardSurvey
文件系统元数据结构¶
F2FS 采用检查点方案来维护文件系统一致性。在挂载时,F2FS 首先尝试通过扫描 CP 区域来查找最后一个有效检查点数据。为了减少扫描时间,F2FS 仅使用两个 CP 副本。其中一个始终指示最后一个有效数据,这被称为影子副本机制。除了 CP 之外,NAT 和 SIT 也采用了影子副本机制。
对于文件系统一致性,每个 CP 指向哪些 NAT 和 SIT 副本有效,如下所示
+--------+----------+---------+
| CP | SIT | NAT |
+--------+----------+---------+
. . . .
. . . .
. . . .
+-------+-------+--------+--------+--------+--------+
| CP #0 | CP #1 | SIT #0 | SIT #1 | NAT #0 | NAT #1 |
+-------+-------+--------+--------+--------+--------+
| ^ ^
| | |
`----------------------------------------'
索引结构¶
管理数据位置的关键数据结构是“节点”。与传统文件结构类似,F2FS 有三种类型的节点:inode、直接节点、间接节点。F2FS 将 4KB 分配给一个 inode 块,该块包含 923 个数据块索引、两个直接节点指针、两个间接节点指针和一个双重间接节点指针,如下所述。一个直接节点块包含 1018 个数据块,一个间接节点块也包含 1018 个节点块。因此,一个 inode 块(即一个文件)覆盖
4KB * (923 + 2 * 1018 + 2 * 1018 * 1018 + 1018 * 1018 * 1018) := 3.94TB.
Inode block (4KB)
|- data (923)
|- direct node (2)
| `- data (1018)
|- indirect node (2)
| `- direct node (1018)
| `- data (1018)
`- double indirect node (1)
`- indirect node (1018)
`- direct node (1018)
`- data (1018)
请注意,所有节点块都由 NAT 映射,这意味着每个节点的位置都由 NAT 表转换。考虑到游走树问题,F2FS 能够切断由叶数据写入引起的节点更新的传播。
目录结构¶
目录条目占用 11 个字节,其中包括以下属性。
hash 文件名的哈希值
ino inode 编号
len 文件名的长度
type 文件类型,例如目录、符号链接等
一个目录项块由 214 个目录项槽和文件名组成。其中一个位图用于表示每个目录项是否有效。一个目录项块占用 4KB,其组成如下。
Dentry Block(4 K) = bitmap (27 bytes) + reserved (3 bytes) +
dentries(11 * 214 bytes) + file name (8 * 214 bytes)
[Bucket]
+--------------------------------+
|dentry block 1 | dentry block 2 |
+--------------------------------+
. .
. .
. [Dentry Block Structure: 4KB] .
+--------+----------+----------+------------+
| bitmap | reserved | dentries | file names |
+--------+----------+----------+------------+
[Dentry Block: 4KB] . .
. .
. .
+------+------+-----+------+
| hash | ino | len | type |
+------+------+-----+------+
[Dentry Structure: 11 bytes]
F2FS 为目录结构实现了多级哈希表。每个级别都有一个哈希表,其中包含专用数量的哈希桶,如下所示。请注意,“A(2B)”表示一个桶包含 2 个数据块。
----------------------
A : bucket
B : block
N : MAX_DIR_HASH_DEPTH
----------------------
level #0 | A(2B)
|
level #1 | A(2B) - A(2B)
|
level #2 | A(2B) - A(2B) - A(2B) - A(2B)
. | . . . .
level #N/2 | A(2B) - A(2B) - A(2B) - A(2B) - A(2B) - ... - A(2B)
. | . . . .
level #N | A(4B) - A(4B) - A(4B) - A(4B) - A(4B) - ... - A(4B)
块数和桶数由以下因素确定
,- 2, if n < MAX_DIR_HASH_DEPTH / 2,
# of blocks in level #n = |
`- 4, Otherwise
,- 2^(n + dir_level),
| if n + dir_level < MAX_DIR_HASH_DEPTH / 2,
# of buckets in level #n = |
`- 2^((MAX_DIR_HASH_DEPTH / 2) - 1),
Otherwise
当 F2FS 在目录中找到一个文件名时,首先会计算文件名的哈希值。然后,F2FS 扫描 0 级哈希表以查找包含文件名及其 inode 号码的目录项。如果未找到,F2FS 扫描 1 级的下一个哈希表。通过这种方式,F2FS 以递增的方式扫描每个级别的哈希表,从 1 到 N。在每个级别中,F2FS 只需要扫描由以下公式确定的一个桶,该公式显示 O(log(# of files)) 复杂度
bucket number to scan in level #n = (hash value) % (# of buckets in level #n)
在文件创建的情况下,F2FS 查找覆盖文件名的连续空槽。F2FS 以与查找操作相同的方式,从 1 到 N 搜索整个级别的哈希表中的空槽。
下图显示了两个拥有子项的示例
--------------> Dir <--------------
| |
child child
child - child [hole] - child
child - child - child [hole] - [hole] - child
Case 1: Case 2:
Number of children = 6, Number of children = 3,
File size = 7 File size = 7
默认块分配¶
在运行时,F2FS 在“Main”区域内管理六个活动日志:热/温/冷节点和热/温/冷数据。
热节点包含目录的直接节点块。
温节点包含除热节点块之外的直接节点块。
冷节点包含间接节点块
热数据包含目录项块
温数据包含除热数据和冷数据块之外的数据块
冷数据包含多媒体数据或迁移的数据块
LFS 有两种用于空闲空间管理的方案:线程日志和复制压缩。复制压缩方案(也称为清理)非常适合显示非常好的顺序写入性能的设备,因为空闲段始终用于写入新数据。但是,在高利用率下,它会受到清理开销的影响。相反,线程日志方案会受到随机写入的影响,但不需要清理过程。F2FS 采用混合方案,默认采用复制压缩方案,但根据文件系统状态动态更改为线程日志方案。
为了使 F2FS 与底层基于闪存的存储对齐,F2FS 以节为单位分配一个段。F2FS 期望节大小与 FTL 中垃圾回收的单位大小相同。此外,关于 FTL 中的映射粒度,F2FS 尽可能从不同的区域分配活动日志的每个节,因为 FTL 可以根据其映射粒度将活动日志中的数据写入一个分配单元中。
清理过程¶
F2FS 根据需要在后台进行清理。当没有足够的空闲段来服务 VFS 调用时,会触发按需清理。后台清理器由内核线程操作,并在系统空闲时触发清理作业。
F2FS 支持两种受害者选择策略:贪婪算法和成本效益算法。在贪婪算法中,F2FS 选择具有最小有效块数的受害者段。在成本效益算法中,F2FS 根据段年龄和有效块数选择受害者段,以解决贪婪算法中的日志块颠簸问题。F2FS 对按需清理器采用贪婪算法,而后台清理器采用成本效益算法。
为了确定受害者段中的数据是否有效,F2FS 管理一个位图。每一位代表一个块的有效性,位图由覆盖主区域中所有块的位流组成。
写入提示策略¶
F2FS 始终使用以下策略设置 whint。
用户 |
F2FS |
块 |
---|---|---|
N/A |
META |
WRITE_LIFE_NONE|REQ_META |
N/A |
HOT_NODE |
WRITE_LIFE_NONE |
N/A |
WARM_NODE |
WRITE_LIFE_MEDIUM |
N/A |
COLD_NODE |
WRITE_LIFE_LONG |
ioctl(COLD) |
COLD_DATA |
WRITE_LIFE_EXTREME |
扩展列表 |
“ |
“ |
-- 缓冲 io |
||
N/A |
COLD_DATA |
WRITE_LIFE_EXTREME |
N/A |
HOT_DATA |
WRITE_LIFE_SHORT |
N/A |
WARM_DATA |
WRITE_LIFE_NOT_SET |
-- 直接 io |
||
WRITE_LIFE_EXTREME |
COLD_DATA |
WRITE_LIFE_EXTREME |
WRITE_LIFE_SHORT |
HOT_DATA |
WRITE_LIFE_SHORT |
WRITE_LIFE_NOT_SET |
WARM_DATA |
WRITE_LIFE_NOT_SET |
WRITE_LIFE_NONE |
“ |
WRITE_LIFE_NONE |
WRITE_LIFE_MEDIUM |
“ |
WRITE_LIFE_MEDIUM |
WRITE_LIFE_LONG |
“ |
WRITE_LIFE_LONG |
Fallocate(2) 策略¶
默认策略遵循以下 POSIX 规则。
- 分配磁盘空间
fallocate() 的默认操作(即,mode 为零)在 offset 和 len 指定的范围内分配磁盘空间。如果 offset+len 大于文件大小,则文件大小(由 stat(2) 报告)将发生更改。在调用之前不包含数据的 offset 和 len 指定的范围内的任何子区域都将初始化为零。此默认行为与 posix_fallocate(3) 库函数的行为非常相似,旨在作为优化实现该函数的方法。
但是,一旦 F2FS 在 fallocate(fd, DEFAULT_MODE) 之前收到 ioctl(fd, F2FS_IOC_SET_PIN_FILE),它将分配具有零或随机数据的磁盘块地址,这对于以下场景很有用
create(fd)
ioctl(fd, F2FS_IOC_SET_PIN_FILE)
fallocate(fd, 0, 0, size)
address = fibmap(fd, offset)
open(blkdev)
write(blkdev, address)
压缩实现¶
新术语簇被定义为压缩的基本单位,文件可以在逻辑上分为多个簇。一个簇包括 4 << n (n >= 0) 个逻辑页面,压缩大小也是簇大小,每个簇可以压缩或不压缩。
在簇元数据布局中,一个特殊的块地址用于指示簇是压缩的还是正常的;对于压缩簇,以下元数据将簇映射到 [1, 4 << n - 1] 个物理块,其中 f2fs 存储包括压缩头和压缩数据的数据。
为了消除覆盖期间的写入放大,F2FS 仅支持一次写入文件上的压缩,只有当簇中的所有逻辑块都包含有效数据并且簇数据的压缩率低于指定阈值时,才能压缩数据。
要在常规 inode 上启用压缩,有四种方法
chattr +c 文件
chattr +c 目录; touch 目录/文件
mount w/ -o compress_extension=ext; touch file.ext
mount w/ -o compress_extension=*; touch any_file
要在常规 inode 上禁用压缩,有两种方法
chattr -c 文件
mount w/ -o nocompress_extension=ext; touch file.ext
FS_COMPR_FL、FS_NOCOMP_FS、扩展之间的优先级
compress_extension=so; nocompress_extension=zip; chattr +c 目录; touch 目录/foo.so; touch 目录/bar.zip; touch 目录/baz.txt; 然后 foo.so 和 baz.txt 应该被压缩,bar.zip 应该未压缩。 chattr +c dir/bar.zip 可以在 bar.zip 上启用压缩。
compress_extension=so; nocompress_extension=zip; chattr -c 目录; touch 目录/foo.so; touch 目录/bar.zip; touch 目录/baz.txt; 然后 foo.so 应该被压缩,bar.zip 和 baz.txt 应该未压缩。 chattr+c dir/bar.zip; chattr+c dir/baz.txt; 可以在 bar.zip 和 baz.txt 上启用压缩。
此时,压缩功能不会直接向用户公开压缩空间,以保证以后可能的数据更新到该空间。相反,主要目标是尽可能减少对闪存盘的数据写入,从而延长磁盘寿命并缓解 IO 拥塞。或者,我们添加了 ioctl(F2FS_IOC_RELEASE_COMPRESS_BLOCKS) 接口来回收压缩空间,并在将特殊标志设置为 inode 后将其显示给用户。一旦压缩空间被释放,该标志将阻止将数据写入文件,直到通过 ioctl(F2FS_IOC_RESERVE_COMPRESS_BLOCKS) 保留压缩空间或将文件大小截断为零为止。
压缩元数据布局
[Dnode Structure]
+-----------------------------------------------+
| cluster 1 | cluster 2 | ......... | cluster N |
+-----------------------------------------------+
. . . .
. . . .
. Compressed Cluster . . Normal Cluster .
+----------+---------+---------+---------+ +---------+---------+---------+---------+
|compr flag| block 1 | block 2 | block 3 | | block 1 | block 2 | block 3 | block 4 |
+----------+---------+---------+---------+ +---------+---------+---------+---------+
. .
. .
. .
+-------------+-------------+----------+----------------------------+
| data length | data chksum | reserved | compressed data |
+-------------+-------------+----------+----------------------------+
压缩模式¶
f2fs 支持带有“compression_mode”挂载选项的“fs”和“user”压缩模式。使用此选项,f2fs 提供了一种选择,用于选择如何压缩已启用压缩的文件(有关如何在常规 inode 上启用压缩,请参阅“压缩实现”部分)。
1) compress_mode=fs 这是默认选项。 f2fs 在启用压缩文件的回写中进行自动压缩。
2) compress_mode=user 这将禁用自动压缩,并使用户可以自行决定选择目标文件和时机。用户可以使用 F2FS_IOC_DECOMPRESS_FILE 和 F2FS_IOC_COMPRESS_FILE ioctl 对已启用压缩的文件进行手动压缩/解压缩,如下所示。
要解压缩文件,
fd = open(filename, O_WRONLY, 0); ret = ioctl(fd, F2FS_IOC_DECOMPRESS_FILE);
要压缩文件,
fd = open(filename, O_WRONLY, 0); ret = ioctl(fd, F2FS_IOC_COMPRESS_FILE);
NVMe 分区命名空间设备¶
ZNS 定义了每个分区的容量,该容量可以等于或小于分区大小。分区容量是分区中可用的块数。 F2FS 检查分区容量是否小于分区大小,如果是,则在初始挂载时,从分区容量之后开始的任何段都会在空闲段位图中标记为非空闲。这些段被标记为永久使用,因此不会分配用于写入,因此也不需要进行垃圾回收。如果分区容量未与默认段大小 (2MB) 对齐,则段可以在分区容量之前启动并跨越分区容量边界。这种跨越段也被认为是可用的段。超过分区容量的所有块都被认为是这些段中不可用的。
设备别名功能¶
f2fs 可以使用一个名为“设备别名文件”的特殊文件。此文件允许使用单个大范围映射整个存储设备,而不是使用通常的 f2fs 节点结构。此映射区域被固定,主要用于保存空间。
从本质上讲,这种机制允许临时保留 f2fs 区域的一部分并由另一个文件系统使用或用于其他目的。一旦外部使用完成,就可以删除设备别名文件,从而将保留的空间释放回 F2FS 以供其自身使用。
<用例>
# ls /dev/vd* /dev/vdb (32GB) /dev/vdc (32GB) # mkfs.ext4 /dev/vdc # mkfs.f2fs -c /dev/vdc@vdc.file /dev/vdb # mount /dev/vdb /mnt/f2fs # ls -l /mnt/f2fs vdc.file # df -h /dev/vdb 64G 33G 32G 52% /mnt/f2fs
# mount -o loop /dev/vdc /mnt/ext4 # df -h /dev/vdb 64G 33G 32G 52% /mnt/f2fs /dev/loop7 32G 24K 30G 1% /mnt/ext4 # umount /mnt/ext4
# f2fs_io getflags /mnt/f2fs/vdc.file 获取 /mnt/f2fs/vdc.file 上的标志 ret=0, flags=nocow(pinned),immutable # f2fs_io setflags noimmutable /mnt/f2fs/vdc.file 获取 noimmutable 上的标志 ret=0, flags=800010 设置 /mnt/f2fs/vdc.file 上的标志 ret=0, flags=noimmutable # rm /mnt/f2fs/vdc.file # df -h /dev/vdb 64G 753M 64G 2% /mnt/f2fs
因此,关键思想是,用户可以对 /dev/vdc 执行任何文件操作,并在使用后回收空间,而该空间计为 /data。这不需要修改分区大小和文件系统格式。