檔案系統架構

本文旨在概略說明 Fuchsia 檔案系統,從初始化開始,討論標準檔案系統作業 (例如開啟、讀取、寫入等),以及實作使用者空間檔案系統的特殊情況。此外,本文說明 VFS 層級的命名空間檢索作業,可用於與非儲存體實體 (例如系統服務) 通訊。

檔案系統是服務

與較常見的單體核心不同,Fuchsia 的檔案系統完全位於使用者空間。這些程序不會連結或載入至核心;它們只是使用者空間程序,可實作可顯示為檔案系統的伺服器。因此,Fuchsia 的檔案系統本身可以輕鬆變更,修改時也不需要重新編譯核心。

檔案系統區塊圖

圖 1:典型的檔案系統程序模塊圖。

與 Fuchsia 上的其他原生伺服器一樣,與檔案系統伺服器互動的首要模式是使用句柄原始碼,而非系統呼叫。核心不瞭解檔案、目錄或檔案系統。因此,檔案系統用戶端無法直接向核心要求「檔案系統存取權」。

這個架構暗示與檔案系統的互動僅限於下列介面:

  • 透過與檔案系統伺服器建立的通訊管道傳送的訊息。這些通訊管道可能是用於用戶端端的本機檔案系統,或用於遠端。
  • 初始化例行程序 (預期會根據個別檔案系統進行大量設定;網路檔案系統需要網路存取權,持久性檔案系統可能需要存取區塊裝置,記憶體內檔案系統只需要用來配置新暫時性頁面的機制)。

這個介面的好處是,任何可透過管道存取的資源,只要實作檔案或目錄的預期通訊協定,就能讓自己看起來像是檔案系統。舉例來說,元件會提供其

檔案生命週期

建立連線

如要開啟檔案,Fuchsia 程式 (用戶端) 會使用 FIDL 將 RPC 要求傳送至檔案系統伺服器。

FIDL 定義了用於在檔案系統用戶端和伺服器之間傳輸訊息和句柄的線格格式。Fuchsia 處理程序不會與核心實作的 VFS 層互動,而是將要求傳送至檔案系統服務,這些服務會實作檔案、目錄和裝置的通訊協定。如要傳送這類開啟要求,Fuchsia 程序必須透過現有句柄將 RPC 訊息傳送至目錄;如要進一步瞭解這個程序,請參閱「開啟文件的生命週期」。

命名空間

在 Fuchsia 上,命名空間是完全位於用戶端內的小型檔案系統。從最基本的層面來說,用戶端將「/」儲存為根目錄,並與之建立關聯的想法,是一種非常原始的命名空間。除了一般單一「全域」檔案系統命名空間外,Fuchsia 程序也可以提供任意目錄句柄來代表「根」,限制其命名空間的範圍。為了限制這個範圍,Fuchsia 檔案系統故意不允許透過 dotdot 存取父目錄

Fuchsia 程序還可能將特定路徑作業重新導向至個別的檔案系統伺服器。當用戶端參照「/bin」時,可以選擇將這些要求重新導向至代表「/bin」目錄的本機句柄,而不是直接將要求傳送至「root」目錄中的「bin」目錄。命名空間與所有檔案系統結構一樣,無法從核心顯示:而是在用戶端執行階段 (例如 libfdio) 中實作,並介於多數用戶端程式碼與遠端檔案系統的句柄之間。

由於命名空間會在句柄上運作,且大多數 Fuchsia 資源和服務都可以透過句柄存取,因此命名空間是相當強大的概念。檔案系統物件 (例如目錄和檔案)、服務、裝置、套件和環境 (可供特權程序檢視) 皆可透過句柄使用,且可在子程序中任意組合。因此,命名空間可在應用程式中提供可自訂的資源探索功能。一個程序在「/svc」中觀察到的服務,可能與其他程序看到的服務相符,也可能不相符,且可根據應用程式啟動政策進行限制或重新導向。

如要進一步瞭解用於限制程序能力的機制和政策,請參閱 沙箱說明文件。

傳遞資料

一旦建立檔案、目錄、裝置或服務的連線後,後續作業也會透過 RPC 訊息傳送。這些訊息會透過一或多個句柄傳送,並使用伺服器可驗證及瞭解的線路格式。

在檔案、目錄、裝置和服務的情況下,這些作業會使用 FIDL 通訊協定。

舉例來說,如果要在檔案中搜尋,用戶端會傳送 Seek 訊息,並在 FIDL 訊息中提供所需位置和「whence」,系統就會傳回新的搜尋位置。如要截斷檔案,您可以傳送 Truncate 訊息,並附上所需的新檔案系統,系統會傳回狀態訊息。如要讀取目錄,您可以傳送 ReadDirents 訊息,系統就會傳回目錄項目清單。如果這些要求傳送至無法處理的檔案系統實體,系統會傳送錯誤訊息,且不會執行作業 (例如傳送至文字檔案的 ReadDirents 訊息)。

記憶體對應

對於可支援記憶體對應檔案的檔案系統,記憶體對應檔案會稍微複雜一些。如要實際「mmap」檔案的一部分,用戶端會傳送「GetVmo」訊息,並接收虛擬記憶體物件 (VMO) 做為回應。接著,這個物件通常會使用虛擬記憶體位址區域 (VMAR) 對應至用戶端的位址空間。如果要將檔案內部「VMO」的有限檢視畫面傳回用戶端,中繼訊息傳遞層就必須進行額外作業,才能讓這些層知道自己傳回的是一個伺服器供應商提供的物件句柄。

透過傳回這些虛擬記憶體物件,用戶端就能快速存取代表檔案的內部位元組,而不會實際經歷來回 IPC 訊息的成本。這項功能讓 mmap 成為吸引人的選項,可讓嘗試在檔案系統互動中提高傳輸量的用戶端使用。

對路徑執行的其他作業

除了「開啟」作業之外,還有幾個值得討論的路徑相關作業:「重新命名」和「連結」。與「開啟」不同,這些作業實際上會一次對多個路徑執行,而非單一位置。這會使使用方式變得複雜:如果呼叫「rename(‘/foo/bar’, ‘baz’)」時,檔案系統需要找出以下方法:

  • 遍歷兩個路徑,即使兩者有不同的起點也一樣 (這裡就是這種情況;一個路徑從根目錄開始,另一個路徑從 CWD 開始)
  • 開啟兩個路徑的父目錄
  • 同時對父目錄和結尾路徑名稱進行操作

為滿足這項行為,VFS 層會利用名為「Cookie」的 Zircon 概念。這些 Cookie 可讓用戶端作業使用句柄在伺服器上儲存開啟狀態,並在日後使用相同的句柄參照該狀態。Fuchsia 檔案系統會利用這項功能,在對另一個 Vnode 執行動作時,參照另一個 Vnode。

這些多路徑作業會執行以下操作:

  • 開啟父項來源 vnode (對於「/foo/bar」,這表示開啟「/foo」)
  • 開啟目標父 vnode (對於「baz」,這表示開啟目前工作目錄),並使用 GetToken 作業取得 vnode 權杖,這是檔案系統 Cookie 的句柄。
  • 將「重新命名」要求傳送至來源父 vnode,以及來源和目的地路徑 (「bar」和「baz」),以及先前取得的 vnode 權杖。這會提供機制,讓檔案系統安全地間接參照目的地 vnode。如果用戶端提供無效的句柄,核心會拒絕存取 Cookie 的要求,而伺服器則可傳回錯誤。

檔案系統生命週期

安裝方式

Fshost 負責在系統上掛載檔案系統。在撰寫本文時,我們正在進行變更,讓檔案系統以元件形式執行 (雖然 fshost 仍會控制這些檔案系統的掛載)。盡可能使用靜態路由。請參閱 fuchsia.fs.startup/Startup 通訊協定。

檔案系統管理

有一系列的檔案系統作業被視為與「管理」相關,包括「卸載目前的檔案系統」。這些作業是由 admin.fidl 中的 fs.Admin 介面定義。檔案系統會匯出此服務,並提供檔案系統根目錄的存取權。

目前的檔案系統

由於 Fuchsia 架構的模組化特性,因此很容易在系統中新增檔案系統。目前有少數檔案系統,旨在滿足各種不同需求。

MemFS:記憶體內檔案系統

MemFS 可用於實作對 /tmp 等暫時性檔案系統的要求,因為在這些系統中,檔案會完全存在於 RAM 中,而不會傳輸至底層的區塊裝置。這個檔案系統目前也用於「啟動檔案系統」通訊協定,其中代表檔案和目錄集合的大型唯讀 VMO 會在開機時解開為使用者可存取的 Vnode (這些檔案可在 /boot 中存取)。

MinFS:永久性檔案系統

MinFS 是一種簡單的傳統檔案系統,可持續儲存檔案。與 MemFS 一樣,它會大量使用先前提到的 VFS 層,但與 MemFS 不同的是,它需要額外的區塊裝置句柄 (會在啟動時傳送至新的 MinFS 程序)。為了方便使用,MinFS 也提供各種工具:「mkfs」用於格式設定、「fsck」用於驗證,以及「mount」和「umount」,可透過指令列將 MinFS 檔案系統加入或移除至命名空間。

Blobfs:不可變動的完整性驗證套件儲存檔案系統

Blobfs 是一種簡單的平面檔案系統,可針對「寫入一次,然後只讀」的已簽署資料進行最佳化,例如套件。除了兩個小前置條件 (檔案名稱,這是用於完整性驗證的檔案 Merkle Tree 根節點的確定性內容可尋址雜湊) 和檔案大小的推測知識 (在將 blob 寫入儲存空間之前,透過呼叫「ftruncate」向 Blobfs 指出) 之外,Blobfs 就會像是一般檔案系統。可以掛載和卸載,似乎包含單一扁平的雜湊目錄,且可透過「open」、「read」、「stat」和「mmap」等作業存取 blob。

FVM

Fuchsia Volume Manager 是一種「邏輯磁碟區管理員」,可為現有區塊裝置提供更大的彈性。目前的功能包括新增、移除、擴充及縮小虛擬分區。為了讓這些功能可行,FVM 會在內部維護從 (虛擬分割區、區塊) 到 (切片、實體區塊) 的實體到虛擬對應關係。為盡量減少維護開銷,它允許分割區以稱為「切片」的片段縮小/擴充。切片是原生區塊大小的倍數。除了中繼資料之外,裝置的其餘部分會分割成多個區塊。每個切片都是自由的,或屬於單一分區。如果切片屬於某個分區,FVM 會維護中繼資料,說明哪個分區使用該切片,以及該分區內切片的虛擬位址。

FVM 的磁碟上版面配置如下所示,並在此處宣告。

      +---------------------------------+ <- Physical block 0
      |           metadata              |
      | +-----------------------------+ |
      | |       metadata copy 1       | |
      | |  +------------------------+ | |
      | |  |    superblock          | | |
      | |  +------------------------+ | |
      | |  |    partition table     | | |
      | |  +------------------------+ | |
      | |  | slice allocation table | | |
      | |  +------------------------+ | |
      | +-----------------------------+ | <- Size of metadata is described by
      | |       metadata copy 2       | |    superblock
      | +-----------------------------+ |
      +---------------------------------+ <- Superblock describes start of
      |                                 |    slices
      |             Slice 1             |
      +---------------------------------+
      |                                 |
      |             Slice 2             |
      +---------------------------------+
      |                                 |
      |             Slice 3             |
      +---------------------------------+
      |                                 |

分區表是由多個虛擬分區項目 (VPartitionEntry) 組成。除了包含名稱和分區 ID 之外,每個 vpart 項目還包含此分區的已分配切片數量。

切片分配表是由緊密排列的切片項目 (SliceEntry) 組成。每個項目都包含

  • 分配狀態
  • 如果已分配,
    • 所屬分區
    • 切片對應至分區內的哪個邏輯切片

如要查看 FVM 程式庫,請按這裡。在鋪面期間,系統會將部分分區從主機複製到目標。因此,分區和 FVM 檔案本身可能會在主機上建立。如要執行這項操作,請使用這裡的代管端公用程式。您可以使用 fvm-check 詳細驗證 FVM 裝置/檔案的完整性