用戶態文件系統fuse學習

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


發佈了45 篇原創文章 · 獲贊 17 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章