linux文件系統基礎--VFS統一文件模型中的4大核心數據結構

概念

VFS(Virtual Filesystem Switch)稱爲虛擬文件系統或虛擬文件系統轉換,是一個內核軟件層,在具體的文件系統之上抽象的一層,用來處理與Posix文件系統相關的所有調用,表現爲能夠給各種文件系統提供一個通用的接口,使上層的應用程序能夠使用通用的接口訪問不同文件系統,同時也爲不同文件系統的通信提供了媒介。
架構
VFS在整個Linux系統中的架構視圖如下:

Linux系統的User使用GLIBC(POSIX標準、GUN C運行時庫)作爲應用程序的運行時庫,然後通過操作系統,將其轉換爲系統調用SCI(system-call interface),SCI是操作系統內核定義的系統調用接口,這層抽象允許用戶程序的I/O操作轉換爲內核的接口調用。VFS提供了一個抽象層,將POSIX API接口與不同存儲設備的具體接口實現進行了分離,使得底層的文件系統類型、設備類型對上層應用程序透明。

接口適配示例

        用戶寫入一個文件,使用POSIX標準的write接口,會被操作系統接管,轉調sys_write這個系統調用(屬於SCI層)。然後VFS層接受到這個調用,通過自身抽象的模型,轉換爲對給定文件系統、給定設備的操作,這一關鍵性的步驟是VFS的核心,需要有統一的模型,使得對任意支持的文件系統都能實現系統的功能。這就是VFS提供的統一的文件模型(common file model),底層具體的文件系統負責具體實現這種文件模型,負責完成POSIX API的功能,並最終實現對物理存儲設備的操作。


VFS這一層建模和抽象是有必要的,如果放在SCI層會導致操作系統的系統調用的功能過於複雜,易出bug。那麼就只能讓底層文件系統都遵循統一實現,這對於已經出現的各種存儲設備來說天然就有不同的特性,也是無法實現的。因此VFS這樣一層抽象是有其必要性的。

跨設備/文件系統示例

VFS爲不同設備或文件系統間的訪問提供了媒介,下面的示意圖和代碼中,用戶通過cp命令進行文件的拷貝,對用戶來說是不用關心底層是否跨越文件系統和設備的,具體都通過VFS抽象層實現對不同文件系統的讀寫操作。

VFS的抽象接口

上述示例中提到VFS也有自己的文件模型,用來支持操作系統的系統調用。下面是VFS抽象模型支持的所有Linux系統調用:

  • 文件系統相關:mount, umount, umount2, sysfs,  statfs,  fstatfs,  fstatfs64, ustat
  • 目錄相關:chroot,pivot_root,chdir,fchdir,getcwd,mkdir,rmdir,getdents,getdents64,readdir,link,unlink,rename,lookup_dcookie
  • 鏈接相關:readlink,symlink
  • 文件相關:chown, fchown,lchown,chown16,fchown16,lchown16,hmod,fchmod,utime,stat,fstat,lstat,acess,oldstat,oldfstat,oldlstat,stat64,lstat64,lstat64,open,close,creat,umask,dup,dup2,fcntl, fcntl64,select,poll,truncate,ftruncate,truncate64,ftruncate64,lseek,llseek,read,write,readv,writev,sendfile,sendfile64,readahead

Linux系統VFS支持的文件系統

  • Disk-based 文件系統:Ext2, ext3, ReiserFS,Sysv, UFS, MINIX, VxFS,VFAT, NTFS,ISO9660 CD-ROM, UDF DVD,HPFS, HFS, AFFS, ADFS,
  • Network 文件系統:NFS, Coda, AFS, CIFS, NCP
  • 特殊文件系統:/proc,/tmpfs等

統一文件模型(common file model)

VFS爲了提供對不同底層文件系統的統一接口,需要有一個高度的抽象和建模,這就是VFS的核心設計——統一文件模型。目前的Linux系統的VFS都是源於Unix家族,因此這裏所說的VFS對所有Unix家族的系統都適用。Unix家族的VFS的文件模型定義了四種對象,這四種對象構建起了統一文件模型。

  • superblock:存儲文件系統基本的元數據。如文件系統類型、大小、狀態,以及其他元數據相關的信息(元元數據)
  • index node(inode):保存一個文件相關的元數據。包括文件的所有者(用戶、組)、訪問時間、文件類型等,但不包括這個文件的名稱。文件和目錄均有具體的inode對應
  • directory entry(dentry):保存了文件(目錄)名稱和具體的inode的對應關係,用來粘合二者,同時可以實現目錄與其包含的文件之間的映射關係。另外也作爲緩存的對象,緩存最近最常訪問的文件或目錄,提示系統性能
  • file:一組邏輯上相關聯的數據,被一個進程打開並關聯使用

統一文件模型是一個標準,各種具體文件系統的實現必須以此模型定義的各種概念來實現。

Superblock

靜態(磁盤上):superblock保存了一個文件系統的最基礎的元信息,一般都保存在底層存儲設備的開頭;動態(內存中,一般是xxx_super_block_info):掛載之後會讀取文件系統的superblock並常駐內存,部分字段是動態創建時設置的。superblock的具體定義見linux/include/fs/fs.h,下圖展示了內存中維護的superblock:

由於Linux系統支持同時掛載多個文件系統,因此s_list字段用於在內存中構建superblock鏈表來支持掛載多個文件系統。s_root字段標識該文件系統的根目錄,s_bdev標識該文件系統所在的設備信息。其中最重要的字段是s_op,這個指針指向該文件系統所支持的各種操作的結構體,稱爲“super_operations”,具體定義如下:

struct super_operations {
    struct inode *(*alloc_inode)(struct super_block *sb);
    void (*destroy_inode)(struct inode *);
    void (*dirty_inode) (struct inode *);
    int (*write_inode) (struct inode *, int);
    void (*drop_inode) (struct inode *);
    void (*delete_inode) (struct inode *);
    void (*put_super) (struct super_block *);
    void (*write_super) (struct super_block *);
    int (*sync_fs)(struct super_block *sb, int wait);
    int (*freeze_fs) (struct super_block *);
    int (*unfreeze_fs) (struct super_block *);
    int (*statfs) (struct dentry *, struct kstatfs *);
    int (*remount_fs) (struct super_block *, int *, char *);
    void (*clear_inode) (struct inode *);
    void (*umount_begin) (struct super_block *);
    int (*show_options)(struct seq_file *, struct vfsmount *);
    int (*show_stats)(struct seq_file *, struct vfsmount *);
    ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
    ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
    int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
};

這個結構體的每個成員都一個函數指針,用來代表這個操作具體應該執行的底層操作。例如需要寫入數據時,VFS會通過superblock中的s_op字段最終去調用write_super來執行文件系統的具體操作:sb->s_op->write_super(sb)。所有這些調用都由VFS完成,向上對接了操作系統的sys_write系統調用,向下轉交到具體文件系統的底層操作。

每一個文件系統使用前必須進行掛載(mount),superblock包含的s_type字段定義了這個文件系統的類型。通過系統調用register_filesystem()、unregister_filesystem()可以實現對具體文件系統的掛載和卸載,它們都只有一個參數,就是文件系統類型“ file_system_type”。本質上就是告訴操作系統掛載的文件系統信息。對於2.6.18之後的內核版本,該結構體定義如下:

struct file_system_type {
    const char *name; //文件系統類型名稱
    int fs_flags;
    struct super_block *(*get_sb)(struct file_system_type *,
    int, char *, void *, struct vfsmount *); //掛載文件系統時由kernel調用,用於創建內存中的superblock
    void (*kill_sb) (struct super_block *); //卸載文件系統是由kernel調用,用於移除內存中的    superblock
    struct module *owner;
    struct file_system_type *next;
    struct list_head fs_supers;
    struct lock_class_key s_lock_key;
    struct lock_class_key s_umount_key;
};

另外,get_sb的最後一個參數是vfsmount類型,這是系統用來記錄掛載信息的數據結構。它保存了掛載點、設備、掛載選項等信息。對於每個一個打開的進程來說,都會在其內核部分維護兩個數據結構:fs_struct和file;分別用來描述關聯的文件系統信息和打開的文件信息。

Index node

靜態:創建文件系統時生成inode,保存在具體存儲設備上,記錄了文件系統的元信息;動態:VFS在內存中使用inode數據結構,來管理文件系統的文件對象,記錄了文件對象的詳細信息,部分字段與關聯的文件對象有關,會動態創建。具體信息如下:

i_dentry字段指定當前inode標識的文件對象的名稱,也就是dentry,是一個鏈表的,因爲可能由多個dentry都指向這個inode(硬鏈接)。然後除了文件的一些權限信息、訪問時間、大小等信息之外,最重要的就是記錄了inode和file對象所提供的操作,分別是i_fop和i_op。其中inode支持的操作示例如下:

struct inode_operations {
    int (*create) (struct inode *, struct dentry *, int);
    struct dentry * (*lookup) (struct inode *, struct dentry *);
    int (*link) (struct dentry *, struct inode *, struct dentry *);
    int (*unlink) (struct inode *, struct dentry *);
    int (*symlink) (struct inode *, struct dentry *, const char *);
    int (*mkdir) (struct inode *, struct dentry *, int);
    int (*rmdir) (struct inode *, struct dentry *);
    int (*mknod) (struct inode *, struct dentry *, int, dev_t);
    int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *);
    int (*readlink) (struct dentry *, char *,int);
    int (*follow_link) (struct dentry *, struct nameidata *);
    void (*truncate) (struct inode *);
    int (*permission) (struct inode *, int);
    int (*setattr) (struct dentry *, struct iattr *);
    int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
    int (*setxattr) (struct dentry *, const char *, const void *, size_t, int);
    ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
    ssize_t (*listxattr) (struct dentry *, char *, size_t);
    int (*removexattr) (struct dentry *, const char *);
};

inode在內存中創建後會有inode cache進行緩存,並執行延遲的write back策略保存到底層存儲設備。

Directory entry

dentry是用來記錄具體的文件名與對應的inode間的對應關係的,同時可以用來實現硬鏈接、緩存、多級目錄等樹狀文件系統的特性。VFS的dentry設計上就是爲了實現整個文件系統樹狀層次結構的,每個文件系統擁有一個沒有父dentry的根目錄(root dentry),這個dentry會被superblock引用,用來作爲進行樹形結構的查找入口。其餘的所有dentry都是有唯一的父dentry,並可以由若干個孩子dentry。示例:對於一個文件"/home/user/a”,會存在“/”、“home”、“user”、“a”四個dentry,依次構成父子關係,每個dentry也都有一個inode與之關聯,存儲了具體的數據。

dentry沒有在磁盤等底層持久化存儲設備上存儲,是一個動態創建的內存數據結構,主要是爲了構建出樹狀組織結構而設計,用來進行文件、目錄的查找。dentry創建之後會被操作系統進行緩存,目的是爲了提升對文件系統進行操作的性能。dentry的結構如下示意,具體定義於“<linux/dcache.h>”。

其中最重要的有兩個字段,一個是d_inode指針指向了當前dentry關聯的inode。另一個就是d_op字段,指向了一系列dentry支持的操作的集合。典型的dentry支持的操作集合如下:

struct dentry_operations {
    int (*d_revalidate)(struct dentry *, int);
    int (*d_hash) (struct dentry *, struct qstr *);
    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
    int (*d_delete)(struct dentry *);
    void (*d_release)(struct dentry *);
    void (*d_iput)(struct dentry *, struct inode *);
};

dentry在需要使用時動態創建,並會被緩存。每個dentry有三種狀態:

  • used:與一個inode關聯,正處於被VFS使用的狀態,不能被損壞和丟棄
  • unused:與inode關聯,但處於被緩存狀態,沒有被VFS使用
  • negative:沒有與具體的inode關聯(相當於是一個無效的路徑)

dentry由於會被動態創建,爲了提升系統性能,設計了一個dentry cache進行緩存,包括三個部分:

  • used dentries 鏈表:記錄每個正在使用的dentry,將其關聯的inode的i_dentry字段指向的dentry鏈表連接起來形成一個dentry鏈表的鏈表
  • LRU雙向環鏈表:用於維護unused和negative狀態的dentry對象,從頭部插入,離頭部越近就是最近訪問過的。當需要刪除dentry時,從隊列尾部刪除最舊的dentry
  • hash table和hash function:用來快速查詢一個給定的路徑到dentry對象

File

文件對象是打開一個具體文件之後創建的一個內存數據結構,與具體的進程和用戶相聯繫。一個文件對象包括的內容就是編程語言支持設置的各種文件打開的flag、mode,文件名稱、當前的偏移等,其中非常重要的一個字段就是f_op,指向了當前文件所支持的操作集合。

struct file {
    struct dentry *f_dentry;
    struct vfsmount *f_vfsmnt;
    struct file_operations *f_op;
    mode_t f_mode;
    loff_t f_pos;
    struct fown_struct f_owner;
    unsigned int f_uid, f_gid;
    unsigned long f_version;
    ...
}

基本的操作集合如下,這也是使用應用程序可感知到的一系列接口。

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, char *, size_t, loff_t);
    ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
    ssize_t (*aio_write) (struct kiocb *, const char *, size_t, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
    ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
    ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

VFS抽象組件間的關係

從用戶角度來看VFS的時候,可以通過下圖很容易的理解各個抽象組件間的協作關係:

每個打開的文件對用戶來說有一個文件描述符,也就是VFS抽象的file object。file object指向一個dentry,dentry指向新的dentry或一個inode,inode最終代表了一個具體存儲設備上的數據。
參考文獻:

1、http://learnlinuxconcepts.blogspot.com/2014/10/the-virtual-filesystem.html

2、https://www.ibm.com/developerworks/library/l-virtual-filesystem-switch/

3、http://www.tldp.org/LDP/khg/HyperNews/get/fs/vfstour.html

4、https://www.win.tue.nl/~aeb/linux/lk/lk-8.html

5、https://www.kernel.org/doc/Documentation/filesystems/vfs.txt
————————————————
版權聲明:本文爲CSDN博主「OshynSong」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u010487568/article/details/79606141

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