什麼是 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 基於異地寫入,因此會產生許多分散在整個儲存中的過時塊。為了服務於新的空日誌空間,它需要無縫地回收這些過時塊以供使用者使用。這項工作稱為清理過程。

該過程包括以下三個操作。

  1. 透過引用段使用情況表來選擇犧牲段。

  2. 它載入由段摘要塊標識的犧牲段中所有資料的父索引結構。

  3. 它檢查資料及其父索引結構之間的交叉引用。

  4. 它有選擇地移動有效資料。

此清理工作可能會導致意外的長時間延遲,因此最重要的目標是向用戶隱藏延遲。而且,絕對應該減少要移動的有效資料量,並儘快移動它們。

主要特點

快閃記憶體感知

  • 擴大隨機寫入區域以獲得更好的效能,但提供高空間區域性性

  • 盡最大努力將 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 選項一起啟用,故障型別值如下所示,它支援單個或組合型別。

型別名稱

型別值

FAULT_KMALLOC

0x00000001

FAULT_KVMALLOC

0x00000002

FAULT_PAGE_ALLOC

0x00000004

FAULT_PAGE_GET

0x00000008

FAULT_ALLOC_BIO

0x00000010 (已過時)

FAULT_ALLOC_NID

0x00000020

FAULT_ORPHAN

0x00000040

FAULT_BLOCK

0x00000080

FAULT_DIR_DEPTH

0x00000100

FAULT_EVICT_INODE

0x00000200

FAULT_TRUNCATE

0x00000400

FAULT_READ_IO

0x00000800

FAULT_CHECKPOINT

0x00001000

FAULT_DISCARD

0x00002000

FAULT_WRITE_IO

0x00004000

FAULT_SLAB_ALLOC

0x00008000

FAULT_DQUOT_INIT

0x00010000

FAULT_LOCK_OP

0x00020000

FAULT_BLKADDR_VALIDITY

0x00040000

FAULT_BLKADDR_CONSISTENCE

0x00080000

FAULT_NO_SEGMENT

0x00100000

FAULT_INCONSISTENT_FOOTER

0x00200000

FAULT_TIMEOUT

0x00400000 (1000ms)

FAULT_VMALLOC

0x00800000

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

用法

  1. 下載使用者空間工具並編譯它們。

  2. 如果 f2fs 是在核心中靜態編譯的,則跳過。否則,插入 f2fs.ko 模組

    # insmod f2fs.ko
    
  3. 建立一個用於掛載的目錄

    # mkdir /mnt/f2fs
    
  4. 格式化塊裝置,然後掛載為 f2fs

    # mkfs.f2fs -l label /dev/block_device
    # mount -t f2fs /dev/block_device /mnt/f2fs
    

mkfs.f2fs

mkfs.f2fs 用於將分割槽格式化為 f2fs 檔案系統,該檔案系統構建基本的磁碟佈局。

快速選項包括

-l [label]

給出一個卷標,最多 512 個 unicode 名稱。

-a [0 1]

拆分每個區域的起始位置以進行基於堆的分配。

預設設定為 1,表示執行此操作。

-o [int]

設定卷大小的過度配置比率(以百分比表示)。

預設設定為 5。

-s [int]

設定每個段的段數。

預設設定為 1。

-z [int]

設定每個區域的段數。

預設設定為 1。

-e [str]

設定基本副檔名列表。例如“mp3,gif,mov”

-t [0 1]

停用丟棄命令與否。

預設設定為 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),它將分配具有零或隨機資料的磁碟塊地址,這對於以下場景很有用

  1. create(fd)

  2. ioctl(fd, F2FS_IOC_SET_PIN_FILE)

  3. fallocate(fd, 0, 0, size)

  4. address = fibmap(fd, offset)

  5. open(blkdev)

  6. 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。這不需要修改分割槽大小和檔案系統格式。