綱要:
0. 簡介
1. FUSE的下載, 安裝, 參考資料來源
2. 帶FUSE的程序的總體分析以及編譯方法
3. 遇到的問題, 解決方案和注意事項
簡介
FUSE,,全稱Filesystem in Userspace。從名字上看,並不怎么容易理解,其中有一個意義模糊的詞Userspace。我以為,此處的User,是相對於kernel而言的。對於一個傳統的文件系統,往往是從內核的層面上對一個文件系統進行支持,比如一些和文件操作相關的系統調用(例如mkdir,open等)都是在內核的層次上實現,從而完成對文件的相關操作。一方面,內核態的代碼難以調試,開發效率低;另一方面,如果開發一個文件系統就需要程序員對於操作系統的內核了然於胸,導致開發成本進一步提高。而FUSE這個庫,把內核關於文件的操作封裝起來,轉化成一組相對簡單的API供開發人員直接使用。這樣,程序員不必深入了解操作系統內核就可以開發文件系統了。
下載安裝
FUSE是sourceforge上的一個開源項目,可以免費的下載源代碼並使用FUSE進行二次開發。
在sourceforge的FUSE頁面(http://fuse.sourceforge.net/ )可以獲取到FUSE的源碼壓縮包。將壓縮包解壓縮,里面主要有這些東西:doc文件夾,里面有一個kernel.txt,對整個FUSE進行了總體上的介紹;example文件夾,里面包含了幾個例子,其中對初學者比較有參考價值的,一個是hello,一個是fusexmp,在下文中會進一步介紹;include文件夾,包含了FUSE的頭文件,其中比較重要的是fuse.h,一方面使用FUSE開發文件系統時只要包含這個頭文件就可以完成大部分工作,另一方面這個文件中聲明了FUSE最核心的API,並且在注釋中有一些說明,起到了文檔的效果;lib文件夾,包含了FUSE的實現,如果僅僅是使用FUSE開發而不是研究FUSE的源碼的話這里就可以略過了;FAQ文件,里面包含了一些常見的問題和解決方案;README里面包含了一些介紹信息;Filesystems包含了一些用FUSE實現的文件系統的網站鏈接。學習使用FUSE,主要的參考資料便是上面的這些內容。
安裝的話,configure + make + make install即可,和在linux上安裝一般的程序沒什么區別。
在FUSE主頁上就有關於安裝的簡要介紹,另外在安裝包中有一個名字為INSTALL的文本文件,里面詳細的介紹了安裝過程,這里不再贅述了。
如何使用
首先,從FUSE提供的例子入手。經過了make之后, FUSE自帶的例子都編譯出了可執行程序。比如hello這個例子,在fuse/example/目錄下新建目錄tmp/然后運行 ./hello tmp/ 這樣就將這個hello文件系統掛載到tmp/這個目錄上了。打開這個目錄(在終端中使用ls命令或者使用圖形化的文件管理工具都可以),可以看到tmp目錄下有一個hello文件,用文本編輯器打開這個文件,可以看到里面的字符串“helloworld”。由於這僅僅是一個最簡單的示例性的文件系統,有些基本功能並不完備,比如hello文件中的內容不能被修改(修改了也沒有辦法保存,因為沒有實現相應的操作。實際上,hello這個文件中的內容是在源碼中直接寫進去的)。如果要嘗試更多的操作,可以使用example中fusexmp這個例子。這個例子是把根目錄掛載到掛載點上,里面的操作也提供的比較完備。
下面簡要分析下hello這個例子的源碼。源碼在hello.c這個文件中,里面只有不到100行C源碼。
整體看起來是這樣的:最開始處包含了fuse.h這個頭文件,然后定義了一些函數。之后定義了一個fuse_operations結構,雖然此處的語法有些晦澀,但是也不難猜出fuse_operations的成員是一些函數指針,然后使用上面實現的函數對這些函數指針進行賦值。最后就是main函數,main函數把所有的工作都交給了fuse_main進行處理了。
那么主要的核心就集中在fuse_operations結構上了。打開fuse.h文件,可以看到fuse_operations是長成這個樣子的:里面有很多函數指針。每一個函數指針都對應着一個和文件系統相關的基本操作。程序員為這些接口提供能夠操作自己文件系統的具體實現,從而將自己的文件系統和操作系統對接到一起。舉個例子,比如在終端中使用ls命令列出目錄中的文件,那么如果路徑指向的是操作系統自身文件系統中的目錄,那么實際上調用的是系統中原有的系統調用readdir;如果路徑指向的是程序員自己的文件系統的目錄,實際上調用的就是fuse_operations結構中的readdir函數指針指向的函數。每個函數指針的實現規格(輸入,輸出,注意事項)雖然在注釋中有所體現,但是過於簡要,很多地方還是需要對操作系統有一定的熟悉程度才會容易理解一些。
總體來看,使用FUSE開發文件系統,是這樣的一個過程:定義一個fuse_operations結構,為這個結構的成員提供符合自己需求的實現即可。
遇到的問題,解決方案和注意事項
使用FUSE的程序的編譯
通過configure生成的makefile文件,直接make即可將FUSE中所有的example都進行編譯。但是如果要單獨編譯其中的某個例子,或者編譯自己寫好的文件系統,按照文檔的說法是這樣的:
Gcc -Wall `pkg-config fuse --cflags --libs` hello.c -o hello
注意“ ` ”是鍵盤左上角,數字1左邊的那個按鍵,而不是單引號。但實際上使用這個命令編譯時會報鏈接錯誤:“fuse_main_real()函數找不到實現……”之類的。這是一個很典型的鏈接錯誤,說明編譯器只看到了函數的聲明而沒有找到這個函數的實現。實際上這個函數的實現在helper.c中,並且被編譯到了相應的鏈接庫中。究其原因,其實是gcc的命令行參數傳入的順序問題。`pkg-config fuse --cflags --libs`這個東西,可以把它理解成一個在fuse安裝時定義的變量,可以在終端中輸入echo `pkg-config fuse --cflags --libs`即可看到這個變量展開之后所表示的gcc的命令行參數,應該是這種形式“-D_FILE_OFFSET_BITS64 -I/usr/includefuse -pthread -lfuse -lrt=”。其中包含了一個-lfuse命令,它的作用是鏈接fuse相應的鏈接庫,也就是告訴編譯器去哪里能找到fuse_main_real()的實現。事實上gcc要求鏈接庫的命令行參數要放在源代碼文件之后,所以將上面的命令改成:
Gcc -Wall hello.c -o hello `pkg-config fuse --cflags --libs`
即可順利通過編譯。關於gcc的參數說明,具體詳情參見gcc的官方文檔,此處不再引用。
使用g++編譯FUSE程序
FUSE除了提供C語言的API,還為多種語言提供了編程接口,比如Java,C#,Python等(詳見FUSE主頁上language binding)。對於C++來說,FUSE也提供了C++風格的API,但實際上由於C和C++的兼容性,C++也可以直接使用C的API,但是需要做出少許的修改。例如像結構體的指定初始化這種C99標准中的語法(也就是hello.c中fuse_operation結構體函數指針成員賦值的晦澀語法),g++是不能編譯通過的。將其修改成在全局定義一個fuse_operation對象,然后在main函數中對這個fuse_operation對象的成員依次賦值即可。其余的命令行參數都和gcc相同了。
FUSE的一個非常有用的命令行參數-d
通過實現一些自定制的函數和FUSE提供的API對接起來從而可以完成整個文件系統的實現。那么怎樣知道FUSE的某個API什么時候會被調用呢?或者說實現了一個API之后怎樣測試它是否正確?這里就用到了FUSE的一個命令行參數-d。在使用FUSE程序進行掛載的時候,同時使用-d參數,這樣在對自己的文件系統進行操作的時候就會在終端中打印一些調試信息。此時最好使用終端通過命令行的方式操作文件系統,如果使用圖形化文件管理工具的話,可能一個操作就會打印幾十甚至上百條調試信息,分析起來會很麻煩。調試信息中主要包括這樣的信息:一個當前操作的序號,操作的名稱(雖然和提供的API並不是一一對應,但大部分是和API具有相同名字),輸入輸出的數據量的大小以及該操作失敗時的錯誤返回碼。關於這些調戲信息的具體規格,並沒有找到相關的詳細說明,所幸大部分信息都不難理解。
FUSE程序的調試
關於FUSE的調試確實沒有想到什么很好的方法。由於FUSE程序的特殊性質以及自身缺少系統級別的編程經驗,沒用成功的使用gdb對FUSE的代碼直接進行跟蹤。FUSE的官網上也推薦了一些系統調試工具,由於精力有限也沒有一一嘗試。一個簡潔有效的方法是通過使用cerr打印一些調試信息,並且配合assert進行定位。另一方面,對自己的文件系統的接口部分進行充分的單元測試,這樣可以大大降低和FUSE對接之后的調試成本。另外在當FUSE的接口只完成一部分的時候,對這些接口單獨測試可能會出現一些很莫名奇妙的bug,比如我就遇到了這樣的問題:實現了getattr和readdir命令后,在終端中使用ls命令,打印的文件和目錄名有時候會多出一些多余的符號,有時候又會報“IO錯誤”。幸運的是,當我對FUSE的其他API進一步完善之后,這個問題神奇的自動消失了。好吧,首先要承認帶着bug還繼續添加代碼是一件非常糟糕的事情,但是FUSE的所有API是一個整體,而終端在執行命令的過程中究竟在后面都做了什么事情,我們也不得而知。總之,我要說明的是,如果使用FUSE的過程中出現了一些和預期不符的東西,首先排除它不是由文件系統自身帶來的,然后再確定對FUSE的使用方式是否恰當,然后確定測試思路是否正確。
