FUSE 穿透

Android 12 支援 FUSE 直通,可將 FUSE 負擔降至最低,達到與直接存取較低層級檔案系統相近的效能。android12-5.4android12-5.10android-mainline (僅供測試) 核心支援 FUSE 傳遞,因此這項功能是否適用取決於裝置使用的核心,以及裝置執行的 Android 版本:

  • 從 Android 11 升級至 Android 12 的裝置無法支援 FUSE 傳遞,因為這些裝置的 Kernel 已凍結,無法移至已正式升級並包含 FUSE 傳遞變更的 Kernel。

  • 使用官方核心時,搭載 Android 12 的裝置可支援 FUSE 直通。對於這類裝置,實作 FUSE 直通的 Android 架構程式碼會嵌入 MediaProvider 主線模組,並自動升級。未將 MediaProvider 實作為主線模組的裝置 (例如 Android Go 裝置),也能存取公開分享的 MediaProvider 變更。

FUSE 與 SDCardFS

使用者空間中的檔案系統 (FUSE) 是一種機制,可讓核心 (FUSE 驅動程式) 將對 FUSE 檔案系統執行的作業外包給使用者空間程式 (FUSE 精靈),由該程式實作作業。Android 11 已淘汰 SDCardFS,並將 FUSE 設為儲存空間模擬的預設解決方案。為配合這項變更,Android 實作了專屬的 FUSE 精靈,可攔截檔案存取作業、強制執行額外的安全性和隱私權功能,以及在執行階段操控檔案。

FUSE 在處理可快取的資訊 (例如網頁或屬性) 時效能良好,但在存取外部儲存空間時會導致效能回歸,尤其是在中低階裝置上更是明顯。這些回歸問題是由一連串相互合作的元件所造成,這些元件實作了 FUSE 檔案系統,而且 FUSE 驅動程式和 FUSE 精靈之間的通訊會多次從核心空間切換至使用者空間 (相較於直接存取較精簡且完全在核心中實作的下層檔案系統)。

為減輕這些回歸問題,應用程式可使用拼接來減少資料複製作業,並使用 ContentProvider API 直接存取較低的檔案系統檔案。即使採用這些其他最佳化措施,與直接存取較低層級的檔案系統相比,使用 FUSE 時的讀取和寫入作業頻寬仍可能減少,尤其是隨機讀取作業,因為這類作業無法使用快取或預先讀取功能。如果應用程式透過舊版 /sdcard/ 路徑直接存取儲存空間,效能會明顯下降,尤其是在執行大量 I/O 作業時。

SDcardFS 使用者空間要求

使用 SDcardFS 可從核心移除使用者空間呼叫,藉此加快 FUSE 的儲存空間模擬和權限檢查速度。使用者空間要求遵循以下路徑:使用者空間 → VFS → sdcardfs → VFS → ext4 → 頁面快取/儲存空間。

FUSE 傳輸 SDcardFS

圖 1. SDcardFS 使用者空間要求

FUSE 使用者空間要求

FUSE 最初是用來啟用儲存空間模擬功能,並允許應用程式以透明方式使用內部儲存空間或外部 SD 卡。使用 FUSE 會產生一些額外負荷,因為每個使用者空間要求都會經過以下路徑:使用者空間 → VFS → FUSE 驅動程式 → FUSE 精靈 → VFS → ext4 → 頁面快取/儲存空間。

FUSE 傳輸 FUSE

圖 2. FUSE 使用者空間要求

FUSE 直通要求

系統會在開啟檔案時檢查大多數的檔案存取權限,並在讀取及寫入該檔案時進行額外的權限檢查。在某些情況下,系統可以在開啟檔案時判斷要求應用程式是否具有所要求檔案的完整存取權,因此不需要繼續將 FUSE 驅動程式的讀取和寫入要求轉送至 FUSE 精靈 (因為這樣只會將資料從一個位置移至另一個位置)。

透過 FUSE 傳遞,處理開啟要求的 FUSE 精靈可以通知 FUSE 驅動程式作業已獲准,後續所有讀取和寫入要求都可以直接轉送至較低的檔案系統。這樣可避免額外負擔,不必等待使用者空間 FUSE 精靈回覆 FUSE 驅動程式要求。

下表比較了 FUSE 和 FUSE 傳遞要求。

FUSE 直通比較

圖 3. FUSE 要求與 FUSE 直通要求

應用程式執行 FUSE 檔案系統存取時,會發生下列作業:

  1. FUSE 驅動程式會處理要求並將其加入佇列,然後透過 /dev/fuse 檔案上的特定連線執行個體,將要求提供給處理該 FUSE 檔案系統的 FUSE 精靈,而 FUSE 精靈會遭到封鎖,無法讀取要求。

  2. 當 FUSE 精靈收到開啟檔案的要求時,會決定該檔案是否應使用 FUSE 直通功能。如果可用,精靈會執行下列動作:

    1. 將這項要求通知 FUSE 驅動程式。

    2. 使用 FUSE_DEV_IOC_PASSTHROUGH_OPEN ioctl 為檔案啟用 FUSE 直通,這必須在已開啟 /dev/fuse 的檔案描述元上執行。

  3. ioctl 會接收 (做為參數) 包含下列項目的資料結構:

    • 下層檔案系統檔案的檔案描述元,是直通功能的目標。

    • 目前正在處理的 FUSE 要求的專屬 ID (必須為開啟或建立並開啟)。

    • 額外欄位,可留空,供日後實作。

  4. 如果 ioctl 成功,FUSE 精靈會完成開啟要求,FUSE 驅動程式會處理 FUSE 精靈回覆,並將對下層檔案系統檔案的參照新增至核心中的 FUSE 檔案。應用程式要求對 FUSE 檔案執行讀取/寫入作業時,FUSE 驅動程式會檢查是否有可用的下層檔案系統檔案參照。

    • 如有參照,驅動程式會建立新的虛擬檔案系統 (VFS) 要求,並以相同參數為目標,指向較低的檔案系統檔案。

    • 如果沒有參照,驅動程式會將要求轉送至 FUSE 常駐程式。

上述作業會針對一般檔案的讀取/寫入和讀取-迭代/寫入-迭代,以及記憶體對應檔案的讀取/寫入作業執行。特定檔案的 FUSE 傳遞功能會持續運作,直到該檔案關閉為止。

實作 FUSE 透視功能

如要在搭載 Android 12 的裝置上啟用 FUSE 傳遞功能,請在目標裝置的 $ANDROID_BUILD_TOP/device/…/device.mk 檔案中加入下列程式碼。

# Use FUSE passthrough
PRODUCT_PRODUCT_PROPERTIES += \
    persist.sys.fuse.passthrough.enable=true

如要停用 FUSE 傳遞,請省略上述設定變更或將 persist.sys.fuse.passthrough.enable 設為 false。如果先前已啟用 FUSE 傳遞,停用後裝置就無法使用 FUSE 傳遞,但仍可正常運作。

如要在不刷寫裝置的情況下啟用/停用 FUSE 傳遞,請使用 ADB 指令變更系統屬性。範例如下所示。

adb root
adb shell setprop persist.sys.fuse.passthrough.enable {true,false}
adb reboot

如需其他協助,請參閱參考實作

驗證 FUSE 直通

如要驗證 MediaProvider 是否使用 FUSE 直通,請檢查 logcat 的偵錯訊息。例如:

adb logcat FuseDaemon:V \*:S
--------- beginning of main
03-02 12:09:57.833  3499  3773 I FuseDaemon: Using FUSE passthrough
03-02 12:09:57.833  3499  3773 I FuseDaemon: Starting fuse...

記錄中的 FuseDaemon: Using FUSE passthrough 項目可確保 FUSE 直通功能正在使用中。

Android 12 CTS 包含 CtsStorageTest,其中包含會觸發 FUSE 傳遞的測試。如要手動執行測試,請使用 atest,如下所示:

atest CtsStorageTest