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

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

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

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

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

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

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

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

圖片摘自fuse內(nèi)核官方文檔
?
首先,fuse daemon會(huì)阻塞在讀/dev/fuse,當(dāng)app進(jìn)程在fuse掛載點(diǎn)下面有新的文件操作(unlink),這時(shí)系統(tǒng)調(diào)用會(huì)調(diào)用fuse內(nèi)核接口,并生成request,同時(shí)喚醒阻塞的fuse daemon。fuse daemon讀到request后,在libfuse中進(jìn)行解析,根據(jù)request的opcode來(lái)執(zhí)行對(duì)應(yīng)的ops,完成后會(huì)把處理結(jié)果返回給/dev/fuse。此時(shí)vfs調(diào)用阻塞的行為將被喚醒,最后返回vfs調(diào)用。
三、?總結(jié)
雖然Fuse簡(jiǎn)化了文件系統(tǒng)的實(shí)現(xiàn),給開(kāi)發(fā)者帶來(lái)了便利。但是其額外的內(nèi)核態(tài)/用戶態(tài)切換帶來(lái)的性能開(kāi)銷不能被忽視,所以fuse性能問(wèn)題,一直是業(yè)界繞不開(kāi)的話題。前面說(shuō)到的splice、多線程、writeback cache都是為了改善其性能問(wèn)題。后續(xù),我們?cè)倬唧w談?wù)刦use性能改善。
?
參考文獻(xiàn):
[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

