FUSE文件系統(tǒng)
Fuse(filesystem in userspace),是一個用戶空間的文件系統(tǒng)。通過fuse內(nèi)核模塊的支持,開發(fā)者只需要根據(jù)fuse提供的接口實現(xiàn)具體的文件操作就可以實現(xiàn)一個文件系統(tǒng)。由于其主要實現(xiàn)代碼位于用戶空間中,而不需要重新編譯內(nèi)核,這給開發(fā)者帶來了眾多便利。Google在Android 11上,為了實現(xiàn)scoped storage,也引入了fuse。下面我們從Fuse的架構(gòu)設(shè)計以及具體的實現(xiàn)細節(jié)來談一談fuse文件系統(tǒng)。
一、?Fuse架構(gòu)設(shè)計

圖片摘自《To FUSE or Not to FUSE: Performance of User-Space File Systems》
Fuse包含一個內(nèi)核模塊和一個用戶空間守護進程(下文稱fuse daemon)。內(nèi)核模塊加載時被注冊成 Linux 虛擬文件系統(tǒng)的一個 fuse 文件系統(tǒng)驅(qū)動。此外,還注冊了一個/dev/fuse的塊設(shè)備。該塊設(shè)備作為fuse daemon與內(nèi)核通信的橋梁,fuse daemon通過/dev/fuse讀取fuse request,處理后將reply寫入/dev/fuse。
上圖詳細展示了fuse的構(gòu)架。當application掛在fuse文件系統(tǒng)上,并且執(zhí)行一些系統(tǒng)調(diào)用時,VFS會將這些操作路由至fuse driver,fuse driver創(chuàng)建了一個fuse request結(jié)構(gòu)體,并把request保存在請求隊列中。此時,執(zhí)行操作的進程會被阻塞,同時fuse daemon通過讀取/dev/fuse將request從內(nèi)核隊列中取出,并且提交操作到底層文件系統(tǒng)中(例如 EXT4 或 F2FS)。當處理完請求后,fuse daemon會將reply寫回/dev/fuse,fuse driver此時把requset標記為completed,最終喚醒用戶進程。
二、?Fuse實現(xiàn)細節(jié)
下面我們基于Android 11 AOSP 以及 kernel4.19的開源代碼,討論一些fuse的實現(xiàn)細節(jié),包括:fuse 用戶空間流程、內(nèi)核隊列、/dev/fuse的讀寫流程等。
1.?fuse用戶空間流程
(1)?fuse mount

Fuse的掛載通過mount函數(shù),將指定的fuse_path掛載到/dev/fuse設(shè)備上。之后對于fuse_path下的文件操作,都會通過fuse文件系統(tǒng),并通過/dev/fuse被fuse daemon讀取處理。
(2)?fuse thread

Fuse ?daemon還會創(chuàng)建一個服務(wù)線程,基于libfuse庫來處理文件操作請求。這里主要關(guān)注fuse_session_new和fuse_session_loop_mt。通過fuse_session_new在libfuse中注冊了fuse daemon實現(xiàn)的fuse_lowlevel_ops,之后通過fuse的所有的文件操作,都會通過libfuse回調(diào)到fuse daemon進行處理。
fuse_session_loop_mt在libfuse中實現(xiàn)了一個多線程模式來讀取請求,相比單線程,在請求處理上效率更高。
(3)?libfuse
由fuse_session_loop_mt在libfuse中的調(diào)用流程如下:

這里我們關(guān)注兩點:
2.?fuse內(nèi)核隊列

圖片摘自《To FUSE or Not to FUSE: Performance of User-Space File Systems》
?
fuse在內(nèi)核中維護了五個隊列,分別為:Backgroud、Pending、Processing、Interrupts、Forgets。一個請求在任何時候只會存在于一個隊列中。
?
3.?/dev/fuse 讀寫調(diào)用流程
Fuse driver加載過程中注冊了對/dev/fuse的操作接口fuse_dev_operations。fuse_dev_do_read/fuse_dev_do_write分別對應(yīng)fuse daemon從內(nèi)核讀取請求,以及處理完請求后寫回reply的函數(shù)調(diào)用。我們分別看下具體的代碼片段

當pending 、interrups、forgets隊列都沒有請求時,讀進程進入休眠。一旦有請求到達,這個等待隊列上的進程將被喚醒。Interrups 和 forgets的請求優(yōu)先級高于pending隊列。當請求的數(shù)據(jù)內(nèi)容被拷貝至用戶空間后,該請求會被移至processing隊列,并且req->flags會保存當前請求的狀態(tài)。

當fuse daemon處理完請求后,會將結(jié)果寫回到/dev/fuse。寫數(shù)據(jù)保存在struct ?fuse_copy_state中,并且會根據(jù)unique id在fc(fuse_conn)中找到對應(yīng)的req,并將寫回的參數(shù)從fuse_copy_state拷貝至req->out。
?
最后我們以unlink為例,看下fuse整體是如何工作的:

圖片摘自fuse內(nèi)核官方文檔
?
首先,fuse daemon會阻塞在讀/dev/fuse,當app進程在fuse掛載點下面有新的文件操作(unlink),這時系統(tǒng)調(diào)用會調(diào)用fuse內(nèi)核接口,并生成request,同時喚醒阻塞的fuse daemon。fuse daemon讀到request后,在libfuse中進行解析,根據(jù)request的opcode來執(zhí)行對應(yīng)的ops,完成后會把處理結(jié)果返回給/dev/fuse。此時vfs調(diào)用阻塞的行為將被喚醒,最后返回vfs調(diào)用。
三、?總結(jié)
雖然Fuse簡化了文件系統(tǒng)的實現(xiàn),給開發(fā)者帶來了便利。但是其額外的內(nèi)核態(tài)/用戶態(tài)切換帶來的性能開銷不能被忽視,所以fuse性能問題,一直是業(yè)界繞不開的話題。前面說到的splice、多線程、writeback cache都是為了改善其性能問題。后續(xù),我們再具體談?wù)刦use性能改善。
?
參考文獻:
[1]?Bharath Kumar Reddy Vangoor, Vasily Tarasov, Erez Zadok.To FUSE or Not to FUSE: Performance of User-Space File Systems. in Proceedings of the 15th USENIX Conference on File and Storage Technologies (FAST ’17), 2017 ? Santa Clara, CA, USA

