檔案系統架構

本文旨在從高層級觀點介紹 Fuchsia 檔案系統,從初始化開始,討論標準檔案系統作業 (例如開啟、讀取、寫入等),以及實作使用者空間檔案系統的特殊情況。此外,本文件也會介紹 VFS 層級的逐步導覽,此命名空間可用來與非儲存實體 (例如系統服務) 通訊。

檔案系統指的是服務

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

檔案系統區塊圖

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

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

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

  • 透過與檔案系統伺服器建立的通訊管道傳送的訊息。這些通訊管道可能是用戶端檔案系統或遠端的本機通訊管道。
  • 初始化處理常式 (預計會依個別檔案系統進行大量設定;網路檔案系統需要網路存取權、永久檔案系統可能需要封鎖裝置存取權,記憶體內檔案系統只需要配置新的暫存頁面即可)。

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

檔案生命週期

建立連線

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

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

命名空間

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

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

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

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

傳遞資料

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

如果是檔案、目錄、裝置和服務,這些作業會使用 FIDL 通訊協定。

舉例來說,如果要在檔案中進行搜尋,用戶端會在 FIDL 訊息中傳送含有所需位置和「whence」的 Seek 訊息,然後傳回新的搜尋位置。為了截斷檔案,系統可能會向新的所需檔案系統傳送 Truncate 訊息,並傳回狀態訊息。如要讀取目錄,系統可能會傳送 ReadDirents 訊息,並傳回內容清單。如果這些要求傳送至無法處理的檔案系統實體,系統會傳送錯誤訊息,且不會執行作業 (例如傳送至文字檔案的 ReadDirents 訊息)。

記憶體對應

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

透過傳回這些虛擬記憶體物件,用戶端就能快速存取代表檔案的內部位元組,而不會實際承擔來回 IPC 訊息的成本。有了這項功能,當用戶端嘗試在與檔案系統互動時達到高處理量時,mmap 就成為極具吸引力的選項。

其他對路徑執行的作業

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

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

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

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

  • 開啟父項來源向量 (「/foo/bar」代表開啟「/foo」)
  • 開啟目標父項 v 節點 (針對「baz」,這代表開啟目前的工作目錄),並使用 GetToken 作業 (此為檔案系統 Cookie 的控制代碼) 取得節點權杖。
  • 傳送「重新命名」要求至來源父項 V 節點,以及來源和目的地路徑 (「bar」和「baz」),以及先前取得的 V 節點符記。這為檔案系統提供了一個可安全地間接參照目的地節點的機制:如果用戶端提供無效的控制代碼,核心將拒絕存取 Cookie 的要求,而伺服器會傳回錯誤。

檔案系統生命週期

安裝方式

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

檔案系統管理

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

目前的檔案系統

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

MemFS:記憶體內檔案系統

MemFS 可用於實作對 /tmp 等暫時性檔案系統的要求,因為在這些系統中,檔案完全存在於 RAM 中,不會傳輸至底層的區塊裝置。這個檔案系統目前也用於「啟動檔案系統」通訊協定,其中一個大型的唯讀 VMO,代表一組檔案和目錄,會在開機時解壓縮供使用者存取的 V 節點 (這些檔案可在 /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 裝置/檔案的完整性