FUSE概述
FUSE(用戶態文件系統)是一個實現在用戶空間的文件系統框架,通過FUSE內核模塊的支持,使用者只需要根據fuse提供的接口實現具體的文件操作就可以實現一個文件系統。
在fuse出現以前,Linux中的文件系統都是完全實現在內核態,編寫一個特定功能的文件系統,不管是代碼編寫還是調試都不太方便,就算是僅僅在現有傳統文件系統上添加一個小小的功能,因爲是在內核中實現仍需要做很大的工作量。在用戶態文件系統FUSE出現後(2.6內核以後都支持fuse),就會大大的減少工作量,也會很方便的進行調試。編寫FUSE文件系統時,只需要內核加載了fuse內核模塊即可,不需要重新編譯內核。
FUSE 特點
- 用戶空間文件系統——類Unix OS的框架
- 允許非超戶在用戶空間開發文件系統
- 內核的API接口,使用fs-type操作
- 支持多種編程語言( c、c++、perl、java 等)
- 普通用戶也可以掛載FUSE
- 不用重新編譯內核
FUSE組成
fuse主要由三部分組成:FUSE內核模塊、用戶空間庫libfuse以及掛載工具fusermount。
- fuse內核模塊:實現了和VFS的對接,實現了一個能被用戶空間進程打開的設備,當VFS發來文件操作請求之後,將請求轉化爲特定格式,並通過設備傳遞給用戶空間進程,用戶空間進程在處理完請求後,將結果返回給fuse內核模塊,內核模塊再將其還原爲Linux kernel需要的格式,並返回給VFS;
- fuse庫libfuse:負責和內核空間通信,接收來自/dev/fuse的請求,並將其轉化爲一系列的函數調用,將結果寫回到/dev/fuse;提供的函數可以對fuse文件系統進行掛載卸載、從linux內核讀取請求以及發送響應到內核。libfuse提供了兩個APIs:一個“high-level”同步API 和一個“low-level” 異步API 。這兩種API 都從內核接收請求傳遞到主程序(fuse_main函數),主程序使用相應的回調函數進行處理。當使用high-level API時,回調函數使用文件名(file names)和路徑(paths)工作,而不是索引節點inodes,回調函數返回時也就是一個請求處理的完成。使用low-level API 時,回調函數必須使用索引節點inode工作,響應發送必須顯示的使用一套單獨的API函數。
- 掛載工具:實現對用戶態文件系統的掛載
FUSE主要代碼文件
in kernel:
- kernel/inode.c —> 主要完成fuse文件驅動模塊的註冊,提供對supper block的維護函數以及其它(驅動的組織開始文件)
- kernel/dev.c —> fuse 的(虛擬)設備驅動
- kernel/control.c —> 提供對於dentry的維護及其它
- kernel/dir.c —> 主要提供對於目錄inode索引節點的維護
- kernel/file.c —> 主要提供對於文件inode索引節點的維護
in userspace:
- lib/helper.c —> “fuse_main()”調用的主入口
- lib/fuse_kern_chan.c—>主要實現fuse應用層訪問(讀寫)fuse driver的功能
- lib/fuse_mt.c —> fuse 的mount管理
- lib/fuse.c —> lib庫主框架文件,實現了主要框架及對”用戶實現的文件系統操作代碼”的封裝
- lib/fuse_lowlevel.c –> 實現比較底層的函數封裝,供fuse.c等使用
- lib/fuse_loop.c —> fuse lib循環監視”fuse driver”的通信緩存
- lib/fuse_loop_mt.c —> 同fuse_loop.c
- lib/fuse_session.c —> fuse會話管理
Fuse是怎麼工作的(fuse-2.9)?
1. fuse庫
- 1.在用戶態程序調用fuse_main() (lib/helper.c)時,先調用fuse_setup_common()該函數先解析用戶態程序傳遞過來的參數,然後調用fuse_mount_common()(該函數是fuse_kern_mount()函數的封裝,lib/mount.c)。
fuse_main()是一個宏定義(include/fuse.h),如下:
#define fuse_main(argc, argv, op, user_data) \
fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)
- 2.fuse_kern_mount()函數中調用fuse_mount_fusermount()使用socketpair()創建一個UNIX域套接字,然後使用創建子進程執行fusermount程序,將FUSE_COMMFD_ENV環境變量中套接字的一端傳遞給它。
- 3.fusermount(util/fusermount.c)確保fuse 模塊已經被加載,然後打開/dev/fuse並通過一個UNIX套接字發送文件處理句柄。父進程等待子進程執行完畢回收,然後返回fuse_mount_fusermount()函數。
- 4.fuse_kern_mount()通過/dev/fuse返回文件句柄給fuse_kern_chan_new()負責處理內核數據,然後返回到fuse_mount_common()函數。
- 5.fuse_setup_common()函數調用fuse_new_common(lib/fuse.c),fuse_new_common()函數分配fuse數據結構,存儲並維護一個文件系統數據鏡像緩存cached,返回到fuse_main()。
- 6.最後,fuse_main()調用fuse_loop(lib/fuse.c)或者fuse_loop_mt()(lib/fuse_mt.c),這兩個函數都可以從設備/dev/fuse讀取文件系統調用,調用fuse_main()之前調用存儲在fuse_operations結構體中的用戶態函數。這些調用的結果回寫到/dev/fuse設備(這個設備可以轉發給系統調用)。
2.內核模塊
內核模塊由2個部分組成:
第一個是proc文件系統組件(在kernel/dev.c中);
第二個是文件系統調用(kernel/file.c、kernel/inode.c、kernel/dir.c)。
kernel/file.c、kernel/inode.c、kernel/dir.c中的所有系統調用要麼調用request_send(),要麼調用request_send_noreply()或者request_send_nonblock()。大部分都是調用request_send()函數,它添加請求到“list of request”結構體(fc->pending),然後等待一個響應。request_send_noreply()和request_send_nonblock()與request_send()函數相似,除了是非阻塞的和不響應一個回覆。
kernel/dev.c中的proc文件系統組件響應文件IO請求,fuse_dev_read()處理文件讀,並從請求列表結構體(list of requests)返回命令到調用程序。fuse_dev_write()處理文件寫, 完成數據寫並放入req->out結構體(它能返回系統調用通過請求列表結構體和request_send()函數),
用戶進程和操作系統進行交互(read文件爲例):
該fuse文件系統掛載在現有ext4文件系統之上.
1.一個用戶進程發出read文件請求;
2.該請求被轉換爲一個內核系統調用,內核VFS層調用fuse文件系統內核模塊;
3.fuse 內核模塊通過/dev/fuse,將read請求傳遞到fuse 用戶態進程;
4.fuse daemon根據用戶實現的read接口,產生新的系統調用,最終調用ext4文件系統的read操作函數,從存儲介質中提取讀操作要求的數據(page cache中有,直接從其中獲取,否則讀磁盤);
5.內核將數據返回給fuse文件系統;
6.用戶級文件系統再次調用內核操作,把數據返回給用戶進程;
7.內核將數據傳給用戶進程完成操作。
庫函數fuse_main()具體處理流程:
1.先打開設備文件/dev/fuse;
2.然後掛載FUSE文件系統;
3.產生FUSE文件系統指針;
4.初始化FUSE文件系統的操作函數集:
5.初始化信號處理函數集;
6.進入等待循環:
從設備文件/dev/fuse中讀取來自內核模塊的請求;
運行相應的操作函數,並獲取返回結果;
將返回給內核的應答結果寫入設備文件/dev/fuse中;
注意
fuse本質上(數據處理時)是處於現有文件系統之上的(具體實現是和現有文件系統處於同一個層次的),fuse不參與底層磁盤數據的存取,只負責處理對讀取和寫入的數據在邏輯上的操作而已。
安裝使用fuse
軟件包下載https://github.com/libfuse/libfuse/releases:
下載軟件包,解壓後,編譯安裝。
./configure
make
make install
解壓後的目錄中有名爲example的目錄,其中有fuse自帶的幾種fuse用戶態實現例子,可以運行其進行測試。
fuse安裝完後,該目錄中的文件也已經編譯成功,只需運行即可(目錄dir爲掛載點,掛載成功後,在該目錄中對文件的操作就會調用fuse自己實現的操作函數):
//example/hello.c 部分代碼,主要實現文件系統的取文件屬性、打開目錄、讀文件、打開文件
static struct fuse_operations hello_oper = { //文件操作函數,爲回調函數
.getattr = hello_getattr,
.readdir = hello_readdir,
.open = hello_open,
.read = hello_read,
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &hello_oper, NULL); //主函數,會調用文件操作結構體
}
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ mkdir dir //創建掛載點
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ ./hello dir //將hello fuse文件系統掛載在dir目錄上
根據目前實現的四個功能進行測試如下:
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ ls
hello
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ cat hello
Hello World!
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ ls -l hello
-r--r--r-- 1 root root 13 12月 31 1969 hello
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ rm -fr hello
rm: cannot remove ‘hello’: Function not implemented
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ touch test
touch: cannot touch ‘test’: Function not implemented
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ mkdir test
mkdir: cannot create directory ‘test’: Function not implemented
由測試可以發現在dir目錄中只能對文件進行實現的四個功能的操作,其他的操作都無法完成,會提示用戶函數沒有實現。 ty@ubuntu:~/program/fuse/fuse-2.9.6/example/$ dirfusermount
-u dir //卸載fuse文件系統dir