基於FUSE框架的文件系統-課程設計

一、選題背景

FUSE(用戶空間文件系統)是這樣一個框架:它使得FUSE用戶在用戶態下編寫文件系統成爲可能,而不必和內核打交道。
FUSE由三個部分組成:linux內核模塊、FUSE庫 以及mount 工具。用戶關心的只是FUSE庫和mount工具,內核模塊僅僅提供kernel的接入口,給了文件系統一個框架,而文件系統本身的主要實現代示位於用戶空間中。FUSE庫給用戶提供了編程的接口,而mount工具則用於掛在用戶編寫的文件系統。
FUSE起初是爲了研究AVFS(A Virtual Filesystem)而設計的,而現在已經成爲 SourceForge的一個獨立項目,目前適用的平臺有Linux, FreeBSD, NetBSD, OpenSolaris和 Mac OS X。
官方的linux kernel版本到 2.6.14 才添加了FUSE模塊,因此 2.4 的內核模塊下,用戶如果要在FUSE中創建一個文件系統,需要先安裝一個FUSE內核模塊,然後使用 FUSE庫和API來創建。
解決的問題是,通過fuse提供的接口,創建一個u_fs 文件系統。

二、方案論證(設計理念)

安裝fuse 2.7.0文件系統框架。FUSE使用fuse_operations來給用戶提供編程結構,讓用戶通過註冊自己編寫的函數到該結構體來實現自己的文件系統。模塊化開發,簡化了編碼。
完成的功能有:
以某文件作爲我們磁盤,組織磁盤的文件管理,其中包括Super block、Bitmap block、Data block。採用位圖方式記錄磁盤塊使用情況。
其結構如下

Super block1 block)
Bitmap block
(1280 blocks)
Data block
(all the rest blocks)

需要實現接口函數:

getattr
readdir
mkdir
rmdir
mknod
write
read
unlink
open
flush
truncate

編寫Makefile文件,簡化編譯步驟。
運行環境:Linux系統(Ubuntu14.04)、eclipse、fuse2.7.0

三、過程論述

在Linux系統中安裝fuse 2.7.0文件系統框架。

進入fuse 2.7.0文件夾,輸入如下命令即可:
./configure --disable-kernel-module
make
make install

創建一個二進制文件作爲磁盤

此處用某5M的文件,重命名爲newdisk,作爲該文件系統的磁盤。

初始化磁盤:

由於文件原先有內容,故先將其內容全清空,以防止干擾文件系統運行。
根據Super block、Bitmap block、Data block的結構,進行初始化磁盤。
其中一個block的結構爲:
struct u_fs_disk_block { // 512 bytes
int size; //how many bytes are bveing used in this block
//The next idsk block,if needed This is the next pointer in the linked allocation list
long nNextBlock;
//And all the rest of the space in the block can be used for actual data sotorage.
char data[MAX_DATA_IN_BLOCK];
};
Super block: 結構如下
struct super_block { //24 bytes
long fs_size; //sizes of file system, in blocks
long first_blk; //first block of root directory
long bitmap; //size of bitmap,in blocks
};

首先獲取磁盤大小,從而賦值fs_size,由於super block佔1 block,bitmap block 佔1280 blocks,故賦值first_blk、bitmap分別爲12811280.
將該結構寫入磁盤,即super block初始化完畢。

Bitmap block:
此處記錄整個磁盤的佔用情況。由於super block、bitmap block均已佔用,且data block中爲root directory預定了一個block,故bitmap需要將這1+1280+1個block置1(即已佔用),此處bitmap採用的是位圖記錄算法,故需將bitmap block中的前1282個bit置爲1,其餘置爲0。即可初始化bitmap block完成。

Data block:
  此處存放文件或者文件夾的屬性以及內容。文件/文件夾的結構如下:
struct u_fs_file_directory { //64 bytes
char fname[MAX_FILENAME + 1]; //filename(plus space for nul)
char fext[MAX_EXTENSION + 1]; //extension(plus space for nul)
long fsize;                 //file size
long nStartBlock;           //where the first block is on disk
int flag;  //indicate type of file. 0:for unused.1:for file. 2:for directory
};
在data block的第一個block中需要初始化root directory的屬性信息。開始root directory沒有子文件,故size=0,nNextblock=-1 data爲空。寫入相應磁盤位置,即可完成data block的初始化。

各接口的功能實現:

getattr:

    實現的函數聲明如下:
static int ufs_getattr(const char *path, struct stat *stbuf);
其操作是通過路徑path,找到所處文件的屬性賦值給stbuf。
具體實現步驟:

**一、獲取path父目錄的屬性(存放於u_fs_file_directory中):**
1. 通過path獲取父目錄的路徑。
2. 讀取super block,從而找到data block的起始位置(或直接定位爲第1282 block處)。
3. 讀取root directory所處塊(即第1282塊)的內容(存放於u_fs_disk_block 結構中)。該block中存放的都是u_fs_file_direcotry。依次讀取該block的文件屬性,通過文件的屬性判斷文件夾名稱等於父目錄的名稱。如果確定爲該父目錄,則獲取父目錄的屬性。

**二、 通過父目錄屬性,獲取path對應的文件屬性:**
1. 通過父目錄屬性,獲取父目錄的存放子文件屬性的第一塊block的位置。
2. 讀取上述的block內容,循環賦值u_fs_file_direcotry,查看其文件名是否爲path所指文件名。如果不是,則繼續查找block的後續塊。
3. 如果找到path,則賦值文件屬性u_fs_file_direcotry。

**三、根據path對應文件的類型,進行stat的賦值(如權限、文件大小等),並返回給fuse框架。**

readdir:

    實現的函數聲明如下:
static int ufs_readdir(const char*path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info*fi)
其操作是通過路徑path,找到其全部子文件/子文件夾,將其名稱通過filler函數填充進buf中。

具體實現步驟:
1. 獲取path獲取該文件夾的屬性(操作類似getattr函數)
2. 每一個文件夾都有. 和 .. 子文件夾,將其添入buf
3. 通過path文件夾的屬性,獲取子文件/文件夾的存放位置
4. 循環賦值,獲取子文件/文件夾的屬性,並將其名稱通過filler函數添加進buf中
5. 如果path的子文件/文件夾屬性存放大小不止一塊,則循環獲取後續塊,執行步驟3,直到u_fs_disk_block-> nStartBlock爲-1

mkdir:

    實現的函數聲明如下:
static int ufs_mkdir(const char *path, mode_t mode);
其操作是通過路徑path,增加新的文件夾:

具體實現操作:
1. 通過path,找到其父目錄名稱
2. 通過父目錄名稱,找到父目錄的屬性
3. 通過父目錄的屬性,找到父目錄的子文件/文件夾的存放塊的其實位置。
4. 在存放塊中,順序查找空閒位置,以添入path新文件屬性
5. 如果當前塊已滿。則繼續賦值後續塊,繼續執行步驟4,如果沒有後續塊,則尋找一塊新的block(空閒塊)作爲後續塊,寫入path新文件的屬性。
6. 修改父目錄的屬性信息(如子文件/文件夾大小),並寫回磁盤。

尋找n塊連續空閒塊步驟:
1. 讀入bitmap_block塊,從其1282bit開始向後查找(因爲磁盤的前1282塊都已經佔用了)
2. 尋找連續n個bit都是0的位置,並保存查找到的最大連續空閒塊max_num。
3. 如果max_num等於n,則查找成功並返回。否則繼續查找。
4. 如果有後續塊,則繼續賦值查找,執行b步驟,如果沒有後續塊,且尚未找到n塊連續塊,則返回找到的最大空閒塊max_num

rmdir:

    實現的函數聲明如下:
static int ufs_rmdir(const char *path);
其操作是將path路徑對應的空文件夾刪除

具體實現步驟:
1. 獲取path獲取該文件夾父文件夾的屬性
2. 獲取path的文件夾的屬性
3. 通過文件夾屬性,判斷是否爲空文件夾。如果非空則返回錯誤
4. 初始化該path文件夾存放子文件夾/文件的磁盤塊block。並將其釋放(即在bitmap_block中相應位修改成0)。
5. 初始化path文件夾屬性的存放位置(如果清空該位置後可空出一個磁盤塊,則回收磁盤塊。
6. 修改父文件夾的相應屬性信息並寫回磁盤。

mknod:

    實現的函數聲明如下:
static int ufs_mknod(const char *path, mode_t mode, dev_t rdev);

具體實現步驟:
1. 與mkdir函數類似,只是u_fs_file_directory->flag應該爲1(表示文件)
2. 創建好文件後需要尋找新的磁盤塊,用來存放該文件內容(與mkdir類似,只是mkdir中新的磁盤塊是用來存放子文件/文件夾屬性)

write:

    實現的函數聲明如下:
static int ufs_write(const char *path, const char *buf, size_t size,
       off_t offset, struct fuse_file_info *fi);
其操作是將buf裏大小爲size的內容,從path指定文件的內容的起始塊後的第offset字節寫入。

具體實現步驟:
1. 獲取path對應文件的屬性
2. 通過offset得知需要跳過文件內容的m個block後開始寫
3. 順序查找文件內容的第m塊磁盤,通過size得知一共需要寫多少塊磁盤
4. 若第m塊磁盤不夠寫,則繼續將剩餘buf內容寫入後續塊。如果全部後續塊也不夠寫,則按剩餘內容申請相應的空閒塊,繼續寫(如果申請空閒塊不夠,則繼續申請,直至寫完buf內容或寫滿磁盤爲止)
5. 修改path對應文件的屬性並寫回磁盤。

read:

 實現的函數聲明如下:
static int ufs_read(const char *path, char *buf, size_t size, off_t offset,struct fuse_file_info *fi)

具體實現步驟:
1. 通過path找到文件屬性。
2. 通過文件屬性找到文件內容的起始磁盤塊。
3. 按offset得出跳過磁盤塊的數目,並獲取最終需要寫的磁盤塊位置。
4. 從指定磁盤開始讀取size內容並賦值給buf(如果跨磁盤塊,則繼續讀後續磁盤塊,直至讀完size長度,或讀完整個文件)

unlink:

    實現的函數聲明如下:
static int ufs_unlink(const char *path);
其操作是是將path對應的文件刪除。

具體實現步驟:
1. 獲取path獲取該文件夾父文件夾的屬性
2. 獲取path的文件的屬性
3. 遍歷path文件內容的磁盤塊,將磁盤塊都初始化,並釋放。
4. 初始化path文件屬性的存放位置(如果清空該位置後可空出磁盤塊,則回收該磁盤塊)
5. 修改父文件夾的相應屬性信息並寫回磁盤。

open:

    實現的函數聲明如下:
static int ufs_open(const char*path, struct fuse_file_info *fi);
此處不用實現其操作,將其返回讓fuse操作即可。

flush:

    實現的函數聲明如下:
static int ufs_flush(const char*path, struct fuse_file_info *fi)
此處不用實現其操作,將其返回讓fuse操作即可。

truncate:

    實現的函數聲明如下:
static int ufs_truncate(const char *path, off_t size);
此處不用實現其操作,將其返回讓fuse操作即可。

編寫makefile

    略。
    大家各回各家,各找各媽。(去搜索下如何編寫makefile吧)

三、結果分析

掛載文件系統:
先準備好一個5M文件,命名爲~/code/homework/newdisk
查看當前文件:
這裏寫圖片描述

編譯文件 make
這裏寫圖片描述
此處可能有warning,可忽略。

初始化磁盤(5M磁盤)
直接運行init_sb_bitmap_data_blocks程序
這裏寫圖片描述

創建新文件夾(創建掛載點),並掛載文件系統:
這裏寫圖片描述

由於開啓調試模式,故另開終端進行測試:
測試內容有

執行命令                                          調用的接口
echo "hello world " > file                       mknod  、write
cat file                                         openread
mkdir dir                                        mkdir
ls -al                                           readdir、getattr
unlink file                                      unlink
rmdir dir                                        rmdir
fusermount –u /tmp/fuse                          卸載掛載點

這裏寫圖片描述

重新掛載文件系統到另外一個文件夾(/tmp/fuse_tmp)中,發現文件依舊在。即文件系統可用。
這裏寫圖片描述

四、課程設計總結

這次課程設計採用fuse文件系統框架,並在Linux系統中進行編碼調試,難度比以前課程設計的要大。
爲了研究fuse的代碼的實現方式,在網上查找相應資料,且調出fuse源代碼研究其原理。通過example文件夾中的幾個例子,一點點修改各個功能。期間詢問了師兄關於某些功能的代碼編寫,也仿寫了其他一些接口代碼,經過兩個星期的努力,終於做出了該文件系統。
整個課程設計過程中,收穫最大的就是Linux的文件系統實現代碼流程,以及C語言的編碼框架及結構。此文件系統採用位圖方式記錄磁盤塊佔用情況,在查詢空閒磁盤塊中,採用了最先符合方式進行分配磁盤,其中可以進一步優化:如採用最差匹配、最優匹配磁盤等方式,或修改磁盤塊的大小以來減少碎片等等。
在整個功能設計中,代碼尚存冗餘,在調用某些功能函數時候,存在部分調用做了些無謂的工作,從而使整個運行速度有所減緩。再次,此文件系統沒有碎片管理、整合功能,在多次創建修改文件/文件夾後將會導致該文件系統操作緩慢。
此次課程設計,我還加深了Linux中編譯操作、Makefile文件的編寫,在Linux系統中進行開發其他C/C++項目或測試時,感覺異常舒服及流暢。

五、參考文獻

[1]Andrew S.Tanenbaum(著),陳向羣,馬洪兵(譯) 現代操作系統(原書第三版) [M]. 北京:機械工業出版社.2009.7
[2] 鳥哥 著;王世江 改編 Linux私房菜[M].北京:人民郵電出版社. 2014.6
[3] W. Richard Stevens,Stephen A. Rago 著;戚正偉,張亞英,尤晉元 譯 Unix環境高級編程 [M].北京:人民郵電出版社. 2014.6
[4] 侯捷 著;STL 源碼剖析 [M].北京:華中科技大學出版社. 2007.6
[5] UC技術博客 FUSE源碼剖析[EB/0L]
http://tech.uc.cn/?p=1597
[6] FUSE(Filesystem in Userspace)簡介和使用[EB/0L]
http://blog.csdn.net/jiangandhe/article/details/5739391

GitHub代碼

https://github.com/luonango/linux_fuse_fs

ps:對師弟師妹的話:
好好看代碼,不要只複製粘貼(百害而無一益)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章