本文將對Linux下的VFS作一個簡單介紹,主要包括VFS裏面的一些概念,以及文件系統是如何與VFS交互的。html
本文所涉及的代碼摘自Linux-4.4.0-59node
VFS的全稱爲virtual File System(虛擬文件系統),能夠把它理解爲Linux下的文件系統平臺。linux
對應用層來講,只要和VFS打交道就能夠了,VFS對外提供了read,write等接口,應用層程序不須要關心底層具體的的文件系統是怎麼實現的。git
對具體的文件系統來講,VFS就是一個框架,提供了文件系統通用的一些數據結構和函數,文件系統只須要提供VFS所要求的數據和實現所要求的函數就能夠了,就像是在VFS上開發一個插件同樣。github
下面這張圖展現了文件系統在系統中的位置編程
+---------------------------------------------------------------+ | +---------------------+ | | | User Applications | | | +---------------------+ | | | ↓ User Space | | | +---------------+ | | | | GNU C Library | | | | +---------------+ | |...................|.........|.................................| | ↓ ↓ | | +-------------------------+ | | | System Call Interface | | | +-------------------------+ | | ↓ | | +-----------+ +-------------------------+ +---------------+ | | |Inode Cache|--| Virtual File System |--|Directory Cache| | | +-----------+ +-------------------------+ +---------------+ | | ↓ | | +-------------------------+ | | | Individual File System | | | +-------------------------+ | | ↓ | | +----------------+ | | | Buffer Cache | Kernel Space | | +----------------+ | | ↓ | | +----------------+ | | | Device Driver | | | +----------------+ | +---------------------------------------------------------------+
應用層程序(User Applications)能夠經過libc(GNU C Library),或者直接經過內核提供的系統調用(System Call Interface )來訪問文件緩存
在內核裏面,相應的系統調用裏面調用的其實就是VFS提供的函數數據結構
VFS根據文件路徑找到相應的掛載點,就獲得了具體的文件系統信息,而後調用具體文件系統的相應函數。(Inode Cache和Directory Cache是VFS中的一部分,用來加快inode和directory的訪問)框架
具體的文件系統根據本身對磁盤(多是別的介質,本篇統一以磁盤爲例)上數據的組織方式,操做相應的數據。(Buffer Cache是內核中塊設備的緩存)函數
下面這張圖簡要的說明了VFS裏面各類object之間的關係
. . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Process A . . . . . . fd . . Kernel . . File System . . +---+ . . +--------------+ . . . . stdin | 0 |-------------->| File Object1 |--------+ . . . . +---+ . . +--------------+ | . . . . stdout | 1 |-------+ . +--------------+ | . . . . +---+ . +------>| File Object2 |------+ | . . . . stderr | 2 |-------+ . +--------------+ | | . . +------------+ . . +---+ . . +--------------+ | | . +------->|Inode Object|--+ +-----------------+ . . | 3 |-------------->| File Object3 |----+ | | DEntry Cache . | . +------------+ | +->|Superblock Object| . . +---+ . . +--------------+ | | | +-------------+ . | . +------------+ | | +-----------------+ . . |...| . . +--+-+-+->|DEntry Object|------+ +---->|Inode Object|--+ | | . . +---+ . . | | | +-------------+ . | . +------------+ +--+ | . . . . . . .. . . . . . | | +--->|DEntry Object|---------+ . +------------+ | | ↓ . . | | +-------------+ . .+->|Inode Object|--+ | +-----------------+ . . | +----->|DEntry Object|------------+ +------------+ | | | | . . . . . . .. . . . . . | | +-------------+ . . +------------+ | +->| Disk | . . Process B . . | | +-->|DEntry Object|-------------->|Inode Object|--+ | | . . fd . . +--------------+ | | | +-------------+ . . +------------+ +-----------------+ . . +---+ . +------>| File Object4 |-+ | | | ..... | . . . . stdin | 0 |-------+ . +--------------+ | | +-------------+ . . . . +---+ . . +--------------+ | | . . . . stdout | 1 |-------------->| File Object5 |----+ | . . . . +---+ . . +--------------+ | | . . . . stderr | 2 |-------+ . +--------------+ | | . . . . +---+ . +------>| File Object6 |----+ | . . . . | 3 |---+ . . +--------------+ | . . . . +---+ | . . +--------------+ | . . . . |...| +---------->| File Object7 |-------+ . . . . +---+ . . +--------------+ . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
在進程的眼裏,看到的是fd列表,列表裏面存放的是指向file object的指針,因此一樣的fd在不一樣的進程中可能會指向不一樣的file object
file object是內核中的對象,表明了一個被進程打開的文件,和具體的進程相關聯
這裏是它的數據結構(只摘取部分)
//linux/fs.h struct file { mode_t f_mode; /*rwx權限及文件類型,來自inode object*/ struct path f_path; /*文件的路徑,由dentry object組成*/ struct inode *f_inode; /*指向的inode*/ const struct file_operations *f_op; /*和文件相關的函數,好比read,write等,來自inode object*/ loff_t f_pos; /*當前文件訪問到的位置*/ } //linux/path.h struct path { struct vfsmount *mnt; /*所屬的掛載點信息*/ struct dentry *dentry; /*指向的dentry object*/ };
當應用程序調用open函數的時候,VFS就會建立相應的file object,除了f_pos是進程私有的數據外,其餘的數據都來自於inode和dentry,和全部進程共享,不一樣進程的file object能夠指向同一個dentry和inode
DEntry Object由VFS維護,全部文件系統共享,不和具體的進程關聯,每一個DEntry Object都指向一個Inode object,在加載inode時根據inode自動生成。
當調用open函數打開一個文件時,內核會第一時間到dentry cache裏面根據path來找相應的dentry,找到了就直接構造file object並返回,若是沒找到的話,就會根據找到的最近的目錄一級一級的往下加載,直到找到相應的文件。(加載的過程就是從磁盤加載inode的過程,全部被加載的inode都會生成相應的dentry而後緩存起來)
dentry chache的做用就是用來加快根據path找到相應文件的速度,它裏面有本身設計的便於快速查找的數據結構,dentry只存在於內存中,不會被存儲在磁盤上,因爲內存有限,因此並非磁盤上全部inode所對應的dentry都會被緩存起來,VFS有本身的緩存策略。
這裏是它的數據結構(只摘取部分)
//linux/dcache.h struct dentry { struct dentry *d_parent; /* 父目錄 */ struct qstr d_name; /* 名字 */ struct inode *d_inode; /* 關聯的inode */ */ struct list_head d_child; /* 父目錄中的子目錄和文件 */ struct list_head d_subdirs; /* 當前目錄中的子目錄和文件 */ };
注意:dentry雖然名字叫directory entry,實際上它對應的inode也能夠是普通文件
inode的結構體由VFS定義,表明了磁盤上的一個文件、目錄、連接或者其它類型的文件。
inode裏面包含的數據存放在磁盤上,由具體的文件系統進行組織,當須要訪問一個inode時,會由文件系統從磁盤上加載相應的數據並構造好相應的inode
一個inode可能被多個dentry所關聯,好比多個hard links指向同一個文件的狀況
這裏是它的數據結構(只摘取部分)
//linux/fs.h struct inode { umode_t i_mode; /*rwx權限及文件類型*/ kuid_t i_uid; /*user id*/ kgid_t i_gid; /*group id*/ const struct inode_operations *i_op; /*inode相關的操做函數,如創create,mkdir,lookup,rename等,請參考fs.h裏的定義*/ struct super_block *i_sb; unsigned long i_ino; /*inode的編號*/ loff_t i_size; /*文件大小*/ struct timespec i_atime; /*最後訪問時間*/ struct timespec i_mtime; /*文件內容最後修改時間*/ struct timespec i_ctime; /*文件元數據最後修改時間(包括文件名稱)*/ const struct file_operations *i_fop; /* 文件操做函數,包括open,llseek,read,write等,請參考fs.h裏的定義 */ void *i_private; /* 文件系統的私有數據,通常能夠從這裏面知道文件對應的數據在哪 */ };
它的結構體由VFS定義,但裏面的數據由具體的文件系統填充,每一個superblock表明了一個具體的分區。
每一個磁盤分區上都有一份superblock,裏面包含了當前磁盤分區的信息,如文件系統類型、剩餘空間等。
因爲superblock很是重要,因此通常文件系統都會在磁盤上存儲多份,防止數據損壞致使整個分區沒法讀取。
這裏是它的數據結構(只摘取部分)
//linux/fs.h struct super_block { unsigned long s_blocksize; /* block的大小,常見文件系統通常都是4K */ loff_t s_maxbytes; /* 支持的最大文件大小 */ struct file_system_type *s_type; /* 文件系統系統類型 */ struct dentry *s_root; /* 分區內文件樹的根節點 */ struct list_head s_mounts; /* 一個分區能夠mount到多個地方,這裏是mount的相關信息*/ struct block_device *s_bdev; /* 對應的物理設備信息 */ u8 s_uuid[16]; /* 分區的UUID */ void *s_fs_info; /* 文件系統的私有數據*/ };
文件系統主要負責管理磁盤上的空間,磁盤上至少要包含三部分數據: superblock,inodes和數據塊。
首先得有一個建立文件系統的工具(如ext2文件系統的mke2fs),用來將磁盤分區格式化成想要的格式,主要是初始化superblock和root inode。
寫一個內核模塊,在裏面註冊本身的文件系統,而且初始化mount函數
當用戶在應用層調用mount命令時,VFS就會根據指定的文件系統類型找到咱們寫的內核模塊,而且調用裏面的mount函數
在mount函數裏面讀取磁盤上的superblock和root inode
初始化root inode的inode_operations和file_operations,而後返回給VFS
這樣VFS就能根據root inode裏提供的函數一級一級的往下找到path對應文件的inode
讀取inode所指向的數據塊(一個或者多個),根據文件的類型,解析數據塊的內容。若是文件類型是普通文件,那麼數據塊裏面就是文件的內容;若是文件類型是目錄,那麼數據塊裏面存儲的就是目錄下面全部子目錄和文件的名稱及它們對應的inode號;若是文件類型是軟連接,那麼數據塊裏面存儲的就是連接到的文件路徑。
總的來講,實現文件系統就是怎麼在磁盤上組織文件,而後實現VFS所要求的superblock,inode以及inode_operations和file_operations。
本篇只是介紹了VFS裏面的基本概念,沒有深刻細節(由於我也沒有相關經驗),但願對你們理解文件系統有所幫助。
要實現一個真正的文件系統除了須要瞭解VFS外,還須要不少內核編程及磁盤操做的知識,若是感興趣,能夠參考ext2文件系統的實現,相對簡單且代碼少(不到1萬行)。