linux用于支持用戶空間文件系統(tǒng)的內(nèi)核模塊名叫FUSE。fuse全稱“Filesystem in Userspace”,中文意思為“用戶空間文件系統(tǒng)”,指完全在用戶態(tài)實現(xiàn)的文件系統(tǒng),是Linux中用于掛載某些網(wǎng)絡(luò)空間,是一個通用操作系統(tǒng)重要的組成部分。
本教程操作環(huán)境:linux7.3系統(tǒng)、Dell G3電腦。
linux fuse是什么
用戶空間文件系統(tǒng)(Filesystem in Userspace),指完全在用戶態(tài)實現(xiàn)的文件系統(tǒng),是Linux 中用于掛載某些網(wǎng)絡(luò)空間,如ssh,到本地文件系統(tǒng)的模塊,在SourceForge上可以找到相關(guān)內(nèi)容。
Linux用于支持用戶空間文件系統(tǒng)的內(nèi)核模塊名叫FUSE,F(xiàn)USE一詞有時特指Linux下的用戶空間文件系統(tǒng)。是一個通用操作系統(tǒng)重要的組成部分。傳統(tǒng)上操作系統(tǒng)在內(nèi)核層面上對文件系統(tǒng)提供支持。而通常內(nèi)核態(tài)的代碼難以調(diào)試,生產(chǎn)率較低。
所謂“用戶態(tài)文件系統(tǒng)”,是指一個文件系統(tǒng)的data和metadata都是由用戶態(tài)的進(jìn)程提供的(這種進(jìn)程被稱為”daemon“)。對于micro-kernel的操作系統(tǒng)來說,在用戶態(tài)實現(xiàn)文件系統(tǒng)不算什么,但對于macro-kernel的Linux來說,意義就有所不同。
雖然叫做用戶態(tài)文件系統(tǒng),但不代表其完全不需要內(nèi)核的參與,因為在Linux中,對文件的訪問都是統(tǒng)一通過VFS層提供的內(nèi)核接口進(jìn)行的(比如open/read),因此當(dāng)一個進(jìn)程(稱為”user”)訪問由daemon實現(xiàn)的文件系統(tǒng)時,依然需要途徑VFS。
當(dāng)VFS接到user進(jìn)程對文件的訪問請求,并且判斷出該文件是屬于某個用戶態(tài)文件系統(tǒng)(根據(jù)mount type),就會將這個請求轉(zhuǎn)交給一個名為”fuse”的內(nèi)核模塊。而后,”fuse”將該請求轉(zhuǎn)換為和daemon之間約定的協(xié)議格式,傳送給daemon進(jìn)程。
可見,在這個三方關(guān)系中,”fuse”這個內(nèi)核模塊起的是一個轉(zhuǎn)接的作用,它幫助建立了VFS(也可以說是user進(jìn)程)和daemon之間的交流通道,通俗點說,它的角色其實就是一個「代理」。
這一整套框架的實現(xiàn)在Linux中即為FUSE (Filesystem in Userspace)。如圖1所示,紅框的部分才是FUSE類型文件系統(tǒng)的具體實現(xiàn),才是用戶態(tài)文件系統(tǒng)的設(shè)計者可以發(fā)揮的空間。目前,已有不下百種基于FUSE實現(xiàn)的文件系統(tǒng)(一些基于內(nèi)核的文件系統(tǒng)也可以porting成用戶態(tài)文件系統(tǒng),比如ZFS和NTFS),而本文將選用一個現(xiàn)成的fuse-sshfs來進(jìn)行演示。
首先安裝fuse-sshfs的軟件包,使用如下的命令進(jìn)行文件系統(tǒng)的mount(將遠(yuǎn)端機器的”remote-dir”目錄掛載到本機的”local-dir”目錄):
sshfs
:
之后,在”/sys/fs”目錄下,將生成一個名為”fuse”的文件夾,同時可以看到”fuse”內(nèi)核模塊已被加載(其對應(yīng)的設(shè)備為”/dev/fuse”),并且本機的掛載目錄的類型已成為”fuse.sshfs”:

生成設(shè)備節(jié)點的目的是方便用戶態(tài)的控制,但是對于文件系統(tǒng)這種級別的應(yīng)用來說,直接使用 ioctl() 來訪問設(shè)備還是顯得麻煩,因為呈現(xiàn)了太多的細(xì)節(jié),所以libfuse作為一個中間層應(yīng)運而生,daemon進(jìn)程實際都是通過libfuse提供的接口來操作fuse設(shè)備文件的。
你來我往
接下來,以在”fuse.sshfs”文件系統(tǒng)中通過”touch”命令新建一個文件為例,查看fuse內(nèi)核模塊和daemon進(jìn)程(即”sshfs”)具體的交互流程(代碼部分基于內(nèi)核5.2.0版本):
【第一輪】
最開始是permission的校驗,不過這里的校驗并不等同于VFS的權(quán)限校驗,它的主要目的是為了避免其他user訪問到了自己私有的fuse文件系統(tǒng)。

然后就是根據(jù)文件路徑查找文件的inode。由于是新建的文件,inode并不在內(nèi)核的inode cache中,所以需要向daemon發(fā)送”lookup”的請求:

這些請求會被放入一個pending queue中,等待daemon進(jìn)程的回復(fù),而user進(jìn)程將陷入睡眠:

作為daemon,sshfs進(jìn)程通過讀取”/dev/fuse”設(shè)備文件來獲得數(shù)據(jù),如果pending queue為空,它將陷入阻塞等待:

當(dāng)pending queue上有請求到來時,daemon進(jìn)程將被喚醒并處理這些請求。被處理的請求會被移入processing queue,待daemon進(jìn)程向fuse內(nèi)核模塊做出reply之后,user進(jìn)程將被喚醒,對應(yīng)的request將從processing queue移除。

【第二輪】
接下來就是執(zhí)行”touch”命令時所觸發(fā)的其他系統(tǒng)調(diào)用,如果是之前訪問過的data/metadata,那很可能存在于cache中,再次訪問這部分data/metadata的時候,fuse內(nèi)核模塊就可以自行解決,不需要去用戶空間往返一趟,否則還是需要上報daemon進(jìn)程進(jìn)行處理。
這里 get_fuse_conn() 獲取的是在fuse類型的文件系統(tǒng)被mount時創(chuàng)建的”fuse_conn“結(jié)構(gòu)體實例。作為daemon進(jìn)程和kernel聯(lián)系的紐帶,除非daemon進(jìn)程消亡,或者對應(yīng)的fuse文件系統(tǒng)被卸載,否則該connection將一直存在。

在daemon進(jìn)程這一端,還是類似的操作。需要注意的是區(qū)別 fuse_write/read() 和fuse_dev_write/read() 這兩個系列的函數(shù),前者是user進(jìn)程在訪問fuse文件系統(tǒng)上的文件時的VFS讀寫請求,屬于對常規(guī)文件的操作,而后者是daemon進(jìn)程對”/dev/fuse”這個代表fuse內(nèi)核模塊的設(shè)備的讀寫,目的是為了獲取request和給出reply。

【第三輪】
fuse內(nèi)核模塊和daemon進(jìn)程的最后一輪交互是在代表fuse文件系統(tǒng)的superblock中獲取inode號,并填寫這個metadata的相關(guān)信息。

硬幣的兩面
不難發(fā)現(xiàn),在fuse文件系統(tǒng)中,即便執(zhí)行一個相對簡單的”touch”操作,所涉及的用戶態(tài)和內(nèi)核態(tài)的切換都是比較頻繁的,并且還伴隨著多次的數(shù)據(jù)拷貝。相比于傳統(tǒng)的內(nèi)核文件系統(tǒng),它整體的I/O吞吐量更低,而延遲也更大。
那為什么fuse在操作系統(tǒng)支持的文件系統(tǒng)里面依然占據(jù)一席之地呢?說起來,在用戶態(tài)開發(fā)是有很多優(yōu)勢的。一是便于調(diào)試,特別適合做一個新型文件系統(tǒng)prototype的快速驗證,因此在學(xué)術(shù)研究領(lǐng)域頗受青睞。在內(nèi)核里面,你只能用c語言吧,到了用戶態(tài),就沒那么多限制了,各種函數(shù)庫,各種編程語言,都可以上。
二是內(nèi)核的bug往往一言不合就導(dǎo)致整個系統(tǒng)crash(在虛擬化的應(yīng)用中更為嚴(yán)重,因為宿主機的crash會導(dǎo)致其上面運行的所有虛擬機crash),而用戶態(tài)的bug所造成的影響相對有限一些。
所以,硬幣的正面是便于開發(fā),不過到底有多方便,這畢竟是一種主觀的感受,而反面則是性能的影響,這可是能夠用客觀的實驗數(shù)據(jù)來驗證的。那應(yīng)該用什么方法才能相對準(zhǔn)確地衡量fuse所帶來的損耗呢?
還是用前面用過的這個fuse-sshfs,不過這里我們不再使用遠(yuǎn)端掛載,而是采用本地掛載的方式(假設(shè)本機的”dir-src”目錄位于ext4文件系統(tǒng)):
sshfs localhost:
當(dāng)daemon進(jìn)程收到請求后,它需要再次進(jìn)入內(nèi)核,去訪問ext4的內(nèi)核模塊(這種文件系統(tǒng)模式被稱為”stackable”的):

以user進(jìn)程向fuse文件系統(tǒng)發(fā)出 write() 請求為例,右邊紅框部分是一次原生的ext4調(diào)用路徑,而左邊多出來的就是因為引入fuse后增加的路徑:

根據(jù)這篇文檔給出的數(shù)據(jù),在這一系統(tǒng)調(diào)用中使用到的”getxattr”所形成的request,需要2倍的”user-kernel”交互量。對于順序?qū)?/strong>,相比起原生的ext4文件系統(tǒng),I/O吞吐量降低27%,隨機寫則降低44%。
不過,在fuse文件系統(tǒng)誕生的這么多年里,大家還是為它想出了很多的優(yōu)化舉措。比如,順序讀寫的時候,可以設(shè)計為向daemon進(jìn)程批量發(fā)送request的形式(但隨機讀寫不適合)。
還有就是使用splicing這種zero-copy技術(shù),由Linux內(nèi)核提供的splicing機制允許用戶空間在轉(zhuǎn)移兩個內(nèi)核的內(nèi)存buffer的數(shù)據(jù)時,不需要拷貝,因此尤其適合stackable模式下,從fuse內(nèi)核模塊直接向ext4內(nèi)核模塊傳遞數(shù)據(jù)(但splicing通常用于超過4K的請求,小數(shù)據(jù)量的讀寫用不上)。
經(jīng)過這些努力,fuse文件系統(tǒng)的性能可以達(dá)到什么樣的一種程度呢?根據(jù)這篇列出的測試結(jié)果,相比起原生的ext4,在最理想的情況下,fuse的性能損耗可以控制到5%以內(nèi),但最差的情況則是83%。同時,其對CPU的資源占用也增加了31%。
從android v4.4到v7.0之間存在的sdcard daemon,到最近幾年的ceph和GlusterFS,都曾經(jīng)采用過或正在采用基于FUSE的實現(xiàn)。FUSE在network filesystem和虛擬化應(yīng)用中都展現(xiàn)了自己的用武之地,它的出現(xiàn)和發(fā)展,并不是要取代在內(nèi)核態(tài)實現(xiàn)的文件系統(tǒng),而是作為一個有益的補充(理論上,F(xiàn)USE還可以用于實現(xiàn)根文件系統(tǒng),但是不建議這么做,”can do”和”should do”是兩回事)。
相關(guān)推薦:《這篇》