EROFS - 增強型只讀檔案系統

概述

EROFS 檔案系統代表增強型只讀檔案系統。 它的目標是為各種只讀用例形成通用的只讀檔案系統解決方案,而不是僅僅關注節省儲存空間而不考慮執行時效能的任何副作用。

它的設計旨在滿足靈活性、功能可擴充套件性和使用者負載友好性等需求。 除此之外,它仍然是一個簡單的隨機訪問友好的高效能檔案系統,可以擺脫與類似方法相比不必要的 I/O 放大和記憶體駐留開銷。

它被實現為以下場景的更好選擇

  • 只讀儲存介質或

  • 完全受信任的只讀解決方案的一部分,這意味著由於安全或其他考慮因素,它需要是不可變的並且與官方黃金映象逐位相符並且

  • 希望透過使用緊湊的佈局、透明的檔案壓縮和直接訪問來最大限度地減少額外的儲存空間,同時保證端到端效能,特別是對於那些具有有限記憶體的嵌入式裝置和具有大量容器的高密度主機。

以下是 EROFS 的主要特性

  • 小端磁碟設計;

  • 支援基於塊的分佈和基於檔案的 fscache 分佈;

  • 支援多個裝置引用外部 blobs,可用於容器映象;

  • 每個裝置 32 位塊地址,因此目前 4KiB 塊大小最多支援 16TiB 地址空間;

  • 兩種 inode 佈局,適用於不同的需求

    Inode 元資料大小

    32 位元組

    64 位元組

    最大檔案大小

    4 GiB

    16 EiB(也受最大卷大小限制)

    最大 uids/gids

    65536

    4294967296

    每個 inode 的時間戳

    是 (64 + 32 位時間戳)

    最大硬連結數

    65536

    4294967296

    保留的元資料

    8 位元組

    18 位元組

  • 支援擴充套件屬性作為選項;

  • 支援 bloom 過濾器,加快負擴充套件屬性查詢;

  • 透過使用擴充套件屬性支援 POSIX.1e ACL;

  • 支援透明資料壓縮作為選項:可以在每個檔案的基礎上使用 LZ4、MicroLZMA 和 DEFLATE 演算法; 此外,還支援原位解壓縮,以避免反彈壓縮緩衝區和不必要的頁面快取抖動。

  • 支援基於塊的資料去重和滾動雜湊壓縮資料去重;

  • 與位元組定址的未對齊元資料或較小的塊大小替代方案相比,支援 tailpacking 內聯;

  • 支援將尾端資料合併到特殊 inode 中作為片段。

  • 支援大 folios 以利用 THPs(透明大頁);

  • 支援對未壓縮檔案進行直接 I/O,以避免迴圈裝置的雙重快取;

  • 支援未壓縮映象上的 FSDAX,用於安全容器和 ramdisk,以避免不必要的頁面快取。

  • 支援透過 Fscache 基礎設施進行基於檔案的按需載入。

以下 git 樹提供了正在開發的檔案系統使用者空間工具,例如格式化工具 (mkfs.erofs)、磁碟一致性和相容性檢查工具 (fsck.erofs) 和除錯工具 (dump.erofs)

  • git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git

有關更多資訊,請參閱文件站點

歡迎提交錯誤和補丁,請幫助我們併發送到以下 linux-erofs 郵件列表

掛載選項

(no)user_xattr

設定擴充套件使用者屬性。 注意:如果選擇了 CONFIG_EROFS_FS_XATTR,則預設啟用 xattr。

(no)acl

設定 POSIX 訪問控制列表。 注意:如果選擇了 CONFIG_EROFS_FS_POSIX_ACL,則預設啟用 acl。

cache_strategy=%s

從現在開始選擇快取解壓縮的策略

disabled

僅原位 I/O 解壓縮;

readahead

快取最後一個不完整的壓縮物理叢集,以供進一步讀取。 它仍然對剩餘的壓縮物理叢集進行原位 I/O 解壓縮;

readaround

快取不完整的壓縮物理叢集的兩端,以供進一步讀取。 它仍然對剩餘的壓縮物理叢集進行原位 I/O 解壓縮。

dax={always,never}

使用直接訪問(無頁面快取)。 請參閱 檔案的直接訪問

dax

一箇舊選項,是 dax=always 的別名。

device=%s

指定要一起使用的額外裝置的路徑。

fsid=%s

為 Fscache 後端指定檔案系統映象 ID。

domain_id=%s

在 fscache 模式下指定域 ID,以便在給定域 ID 下具有相同 blobs 的不同映象可以共享儲存。

fsoffset=%llu

為主要裝置指定塊對齊的檔案系統偏移量。

Sysfs 條目

有關已掛載 erofs 檔案系統的資訊可以在 /sys/fs/erofs 中找到。 每個已掛載的檔案系統將在 /sys/fs/erofs 中都有一個基於其裝置名稱的目錄(即,/sys/fs/erofs/sda)。 (另請參閱 ABI 檔案測試/sysfs-fs-erofs

磁碟細節

摘要

與其他只讀檔案系統不同,EROFS 卷的設計儘可能簡單

                              |-> aligned with the block size
 ____________________________________________________________
| |SB| | ... | Metadata | ... | Data | Metadata | ... | Data |
|_|__|_|_____|__________|_____|______|__________|_____|______|
0 +1K

所有資料區域應與塊大小對齊,但元資料區域可能不對齊。 現在可以在兩個不同的空間(檢視)中觀察到所有元資料

  1. Inode 元資料空間

    每個有效的 inode 應與一個 inode 插槽對齊,這是一個固定值(32 位元組),旨在與緊湊的 inode 大小保持一致。

    可以使用以下公式直接找到每個 inode

    inode 偏移量 = meta_blkaddr * block_size + 32 * nid

                                |-> aligned with 8B
                                           |-> followed closely
    + meta_blkaddr blocks                                      |-> another slot
      _____________________________________________________________________
    |  ...   | inode |  xattrs  | extents  | data inline | ... | inode ...
    |________|_______|(optional)|(optional)|__(optional)_|_____|__________
             |-> aligned with the inode slot size
                  .                   .
                .                         .
              .                              .
            .                                    .
          .                                         .
        .                                              .
      .____________________________________________________|-> aligned with 4B
      | xattr_ibody_header | shared xattrs | inline xattrs |
      |____________________|_______________|_______________|
      |->    12 bytes    <-|->x * 4 bytes<-|               .
                          .                .                 .
                    .                      .                   .
               .                           .                     .
           ._______________________________.______________________.
           | id | id | id | id |  ... | id | ent | ... | ent| ... |
           |____|____|____|____|______|____|_____|_____|____|_____|
                                           |-> aligned with 4B
                                                       |-> aligned with 4B
    

    Inode 可以是 32 或 64 位元組,可以從所有 inode 版本都有的公共欄位 -- i_format 中區分

     __________________               __________________
    |     i_format     |             |     i_format     |
    |__________________|             |__________________|
    |        ...       |             |        ...       |
    |                  |             |                  |
    |__________________| 32 bytes    |                  |
                                     |                  |
                                     |__________________| 64 bytes
    

    Xattrs、extents、資料內聯放置在相應 inode 之後,並進行適當的對齊,並且對於不同的資料對映,它們可以是可選的。 _目前_ 支援總共 5 種資料佈局

    0

    沒有資料內聯的平面檔案資料(沒有 extent);

    1

    固定大小輸出資料壓縮(具有非緊湊索引);

    2

    具有尾部打包資料內聯的平面檔案資料(沒有 extent);

    3

    固定大小輸出資料壓縮(具有緊湊索引,v5.3+);

    4

    基於塊的檔案 (v5.15+)。

    可選 xattrs 的大小由 inode 標頭中的 i_xattr_count 指示。 大型 xattrs 或許多不同檔案共享的 xattrs 可以儲存在共享 xattrs 元資料中,而不是直接內聯在 inode 之後。

  2. 共享 xattrs 元資料空間

    共享 xattrs 空間與上述 inode 空間類似,從由 xattr_blkaddr 指示的特定塊開始,一個接一個地組織,並進行適當的對齊。

    也可以透過以下公式直接找到每個共享 xattr

    xattr 偏移量 = xattr_blkaddr * block_size + 4 * xattr_id

                       |-> aligned by  4 bytes
+ xattr_blkaddr blocks                     |-> aligned with 4 bytes
 _________________________________________________________________________
|  ...   | xattr_entry |  xattr data | ... |  xattr_entry | xattr data  ...
|________|_____________|_____________|_____|______________|_______________

目錄

現在所有目錄都以緊湊的磁碟格式組織。 請注意,每個目錄塊都分為索引區和名稱區,以便支援隨機檔案查詢,並且所有目錄條目都 _嚴格_ 按字母順序記錄,以便支援改進的字首二進位制搜尋演算法(可以參考相關的原始碼)。

                 ___________________________
                /                           |
               /              ______________|________________
              /              /              | nameoff1       | nameoffN-1
 ____________.______________._______________v________________v__________
| dirent | dirent | ... | dirent | filename | filename | ... | filename |
|___.0___|____1___|_____|___N-1__|____0_____|____1_____|_____|___N-1____|
     \                           ^
      \                          |                           * could have
       \                         |                             trailing '\0'
        \________________________| nameoff0
                            Directory block

請注意,除了第一個檔名的偏移量之外,nameoff0 還指示此塊中目錄條目的總數,因為根本不需要引入另一個磁碟欄位。

基於塊的檔案

為了支援基於塊的資料去重,自 Linux v5.15 以來,支援一種新的 inode 資料佈局:檔案被分割成大小相等的資料塊,inode 元資料的 extents 區域指示如何獲取塊資料:這些可以簡單地是一個 4 位元組的塊地址陣列,也可以是 8 位元組的塊索引形式(有關更多詳細資訊,請參閱 erofs_fs.h 中的 struct erofs_inode_chunk_index。)

順便說一句,基於塊的檔案目前都是未壓縮的。

長擴充套件屬性名稱字首

在某些用例中,具有不同值的擴充套件屬性只能有幾個常見的字首(例如 overlayfs xattrs)。 在這種情況下,預定義的字首在映象大小和執行時效能方面都效率低下。

引入了長 xattr 名稱字首特性來解決這個問題。 總體思路是,除了現有的預定義字首之外,xattr 條目還可以引用使用者指定的長 xattr 名稱字首,例如“trusted.overlay.”。

當引用長 xattr 名稱字首時,erofs_xattr_entry.e_name_index 的最高位(bit 7)被設定,而較低位(bit 0-6)作為一個整體表示引用的長名稱字首在所有長名稱字首中的索引。 因此,只有名稱的尾部(除了長 xattr 名稱字首)儲存在 erofs_xattr_entry.e_name 中,如果完整的 xattr 名稱與其長 xattr 名稱字首完全匹配,則該尾部可能為空。

只要打包 inode 有效,或者在元 inode 中,所有長 xattr 字首都一個接一個地儲存在打包 inode 中。 xattr_prefix_count(在磁碟超級塊中)指示長 xattr 名稱字首的總數,而 (xattr_prefix_start * 4) 指示打包/元 inode 中長名稱字首的起始偏移量。 請注意,如果 xattr_prefix_count 為 0,則停用長擴充套件屬性名稱字首。

每個長名稱字首都以格式儲存:ALIGN({__le16 len, data}, 4),其中 len 表示資料部分的總大小。 資料部分實際上由 ‘struct erofs_xattr_long_prefix’ 表示,其中 base_index 表示預定義的 xattr 名稱字首的索引,例如,“trusted.overlay.” 長名稱字首的 EROFS_XATTR_INDEX_TRUSTED,而 infix 字串儲存剝離短字首後的字串,例如,上面示例中的“overlay.”。

資料壓縮

EROFS 實現了固定大小輸出壓縮,與現有的其他固定大小輸入解決方案相比,它可以從可變大小的輸入生成固定大小的壓縮資料塊。 由於當今流行的資料壓縮演算法大多基於 LZ77,並且這種固定大小輸出方法可以從歷史字典(又名滑動視窗)中受益,因此使用固定大小輸出壓縮可以獲得相對更高的壓縮率。

詳細地說,原始(未壓縮)資料被轉換為多個可變大小的 extents,同時壓縮為物理叢集(pclusters)。 為了記錄每個可變大小的 extent,引入了邏輯叢集(lclusters)作為壓縮索引的基本單位,以指示是否在範圍內生成了一個新的 extent (HEAD) 或沒有 (NONHEAD)。 現在 lclusters 的大小是固定的,如下圖所示

         |<-    variable-sized extent    ->|<-       VLE         ->|
       clusterofs                        clusterofs              clusterofs
         |                                 |                       |
_________v_________________________________v_______________________v________
... |    .         |              |        .     |              |  .   ...
____|____._________|______________|________.___ _|______________|__.________
    |-> lcluster <-|-> lcluster <-|-> lcluster <-|-> lcluster <-|
         (HEAD)        (NONHEAD)       (HEAD)        (NONHEAD)    .
          .             CBLKCNT            .                    .
           .                               .                  .
            .                              .                .
      _______._____________________________.______________._________________
         ... |              |              |              | ...
      _______|______________|______________|______________|_________________
             |->      big pcluster       <-|-> pcluster <-|

一個物理叢集可以看作是一個物理壓縮塊的容器,其中包含壓縮資料。 以前,只支援 lcluster 大小(4KB)的 pclusters。 自引入大 pcluster 特性(自 Linux v5.13 起可用)後,pcluster 可以是 lcluster 大小的倍數。

對於每個 HEAD lcluster,記錄 clusterofs 以指示新 extent 的開始位置,並使用 blkaddr 查詢壓縮資料。 對於每個 NONHEAD lcluster,可以使用 delta0 和 delta1 代替 blkaddr 來指示到其 HEAD lcluster 和下一個 HEAD lcluster 的距離。 PLAIN lcluster 也是 HEAD lcluster,除了它的資料未壓縮。 有關更多詳細資訊,請參閱 erofs_fs.h 中 “struct z_erofs_vle_decompressed_index” 周圍的註釋。

如果啟用了 big pcluster,則還需要記錄 lclusters 中 pcluster 的大小。 讓第一個 NONHEAD lcluster 的 delta0 儲存壓縮塊計數,並帶有一個特殊標誌,作為一種新呼叫的 CBLKCNT NONHEAD lcluster。 很容易理解它的 delta0 始終為 1,如下圖所示

 __________________________________________________________
| HEAD |  NONHEAD  | NONHEAD | ... | NONHEAD | HEAD | HEAD |
|__:___|_(CBLKCNT)_|_________|_____|_________|__:___|____:_|
   |<----- a big pcluster (with CBLKCNT) ------>|<--  -->|
         a lcluster-sized pcluster (without CBLKCNT) ^

如果另一個 HEAD 跟隨一個 HEAD lcluster,則沒有空間來記錄 CBLKCNT,但是很容易知道這種 pcluster 的大小也為 1 lcluster。

自 Linux v6.1 以來,每個 pcluster 都可以用於多個可變大小的 extents,因此它可以用於壓縮資料去重。