天天看點

深度解析linux的檔案系統背景:Unix的檔案系統:VFS介紹:

背景:

虛拟檔案系統(有時也稱作虛拟檔案交換,更常見的是簡稱VFS)作為核心子系統,為使用者空間程式提供了檔案和檔案系統相關的接口。系統中所有檔案系統不但依賴VFS共存,而且也依靠VFS系統協同工作。通過虛拟檔案系統,程式可以利用标準的Uinx系統調用對不同的檔案系統,甚至不同媒體上的檔案系統進行讀寫操作。

深度解析linux的檔案系統背景:Unix的檔案系統:VFS介紹:

實作:

VFS執行的動作:使用cp(1)指令從ext3檔案系統格式的硬碟拷貝資料到ext2檔案系統格式的可移動磁盤上。兩種不同的檔案系統,兩種不同的媒體,連接配接到同一個 VFS 上。

Unix的檔案系統:

概述:

1.Unix使用了四種和檔案系統相關的傳統抽象概念:檔案、目錄項、索引節點和安裝點( mount point)。

從本質上講檔案系統是特殊的資料分層存儲結構,它包含檔案、目錄和相關的控制資訊。檔案系統的通用操作包含建立、删除和安裝等。

2.在 Unix中,檔案系統被安裝在一個特定的安裝點上,該安裝點在全局層次結構中被稱作命名空間,所有的已安裝檔案系統都作為根檔案系統樹的枝葉出現在系統中。

3.檔案:檔案其實可以做一個有序位元組串,位元組串中第一個位元組是檔案的頭,最後一個位元組是檔案的尾。每一個檔案為了便于系統和使用者識别,都被配置設定了一個便于了解的名字。典型的檔案操作有讀、寫、建立和删除等。簡單的面向位元組流抽象的Unix檔案則以簡單性和相當的靈活性為代價。

4.目錄:檔案通過目錄組織起來。檔案目錄好比一個檔案夾,用來容納相關檔案。因為目錄也可以包含其他目錄,即子目錄,是以目錄可以層層嵌套,形成檔案路徑。路徑中的每一部分都被稱作目錄條目。“/home/wolfman/butter”是檔案路徑的一個例子——根目錄/,目錄home,wolfman和檔案 butter都是目錄條目,它們統稱為目錄項。在Unix中,目錄屬于普通檔案,它列出包含在其中的所有檔案。由于VFS把目錄當作檔案對待,是以可以對目錄執行和檔案相同的操作。

5.檔案相關資訊:Unix系統将檔案的相關資訊和檔案本身這兩個概念加以區分,例如通路控制權限、大小、擁有者、建立時間等資訊。檔案相關資訊,有時被稱作檔案的中繼資料(也就是說,檔案的相關資料),被存儲在一個單獨的資料結構中,該結構被稱為索引節點( inode),它其實是index node的縮寫,不過近來術語“inode”使用得更為普遍一些。

所有這些資訊都和檔案系統的控制資訊密切相關,檔案系統的控制資訊存儲在超級塊中,超級塊是一種包含檔案系統資訊的資料結構。有時,把這些收集起來的資訊稱為檔案系統資料元,它集單獨檔案資訊和檔案系統的資訊于一身。

VFS介紹:

VFS使得使用者可以直接使用open()、read)和write()這樣的系統調用而無須考慮具體檔案系統和實際實體媒體。現在聽起來這并沒什麼新奇的(我們早就認為這是理所當然的),但是,使得這些通用的系統調用可以跨越各種檔案系統和不同媒體執行,絕非是微不足道的成績。更了不起的是,系統調用可以在這些不同的檔案系統和媒體之間執行——我們可以使用标準的系統調用從一個檔案系統拷貝或移動資料到另一個檔案系統。

老式的作業系統(比如DOS〉是無力完成上述工作的,任何對非本地檔案系統的通路都必須依靠特殊工具才能完成。正是由于現代作業系統引入抽象層,比如Linux,通過虛拟接口通路檔案系統,才使得這種協作性和泛型存取成為可能。

檔案系統抽象層:

VFS抽象層之是以能銜接各種各樣的檔案系統,是因為它定義了所有檔案系統都支援的、基本的、概念上的接口和資料結構。同時實際檔案系統也将自身的諸如“如何打開檔案”,“目錄是什麼”等概念在形式上與VFS的定義保持一緻。因為實際檔案系統的代碼在統一的接口和資料結構下隐藏了具體的實作細節,是以在VFS層和核心的其他部分看來,所有檔案系統都是相同的,它們都支援像檔案和目錄這樣的概念,同時也支援像建立檔案和删除檔案這樣的操作。

核心通過抽象層能夠友善、簡單地支援各種類型的檔案系統。實際檔案系統通過程式設計提供VFS所期望的抽象接口和資料結構,這樣,核心就可以毫不費力地和任何檔案系統協同工作,并這樣提供給使用者空間的接口,也可以和任何檔案系統無绛地連接配接在一起,完成實際工作。

一個簡單的使用者空間操作:

ret=write(fd,buf,len)
           

該系統調用将buf指針指向的長度為len位元組的資料寫入檔案描述符fd對應的檔案的目前位置。

這個系統調用首先被一個通用系統調用sys_write()處理,sys_write()函數要找到fd所在的檔案系統實際給出的是哪個寫操作,然後再執行該操作。實際檔案系統的寫方法是檔案系統實作的一部分,資料最終通過該操作寫入媒體(或執行這個檔案系統想要完成的寫動作)。

下圖描述了從使用者空間的write()調用到資料被寫入磁盤媒體的整個流程。

一方面,系統調用是通用VFS接口,提供給使用者空間的前端﹔

另一方面,系統調用是具體檔案系統的後端,處理實作細節。

深度解析linux的檔案系統背景:Unix的檔案系統:VFS介紹:

VFS的對象

VFS其實采用的是面向對象的設計思路,使用一組資料結構來代表通用檔案對象。這些資料結構類似于對象。因為核心純粹使用C代碼實作,沒有直接利用面向對象的語言,是以核心中的資料結構都使用C語言的結構體實作,而這些結構體包含資料的同時也包含操作這些資料的函數指針,其中的操作函數由具體檔案系統實作。

VFS中有四個主要的對象類型,它們分别是:

超級塊對象:它代表一個具體的已安裝檔案系統。

索引節點對象:它代表一個具體檔案。

目錄項對象,它代表一個目錄項,是路徑的一個組成部分。

檔案對象,它代表由程序打開的檔案。

每個主要對象中都包含一個操作對象,這些操作對象描述了核心針對主要對象可以使用的方法:

super_operations對象,其中包括核心針對特定檔案系統所能調用的方法,比如 write_inode()和sync_fs()等方法。

inode_operations對象,其中包括核心針對特定檔案所能調用的方法,比如create()和link)等方法。

dentry_operations對象,其中包括核心針對特定目錄所能調用的方法,比如d_compare()和d_delete(等方法。

file_operations對象,其中包括程序針對已打開檔案所能調用的方法,比如read()和write(等方法。

超級塊對象

各種檔案系統都必須實作超級塊對象,該對象用于存儲特定檔案系統的資訊,通常對應于存放在磁盤特定扇區中的檔案系統超級塊或檔案系統控制塊(是以稱為超級塊對象)。對于并非基于磁盤的檔案系統(如基于記憶體的檔案系統,比如sysfs),它們會在使用現場建立超級塊并将其儲存到記憶體中。

引用于:/usr/src/kernels/3.10.0-1160.el7.x86_64/include/linux/fs.h

struct super_block {
        struct list_head        s_list;         /* Keep this first */
        dev_t                   s_dev;          /* search index; _not_ kdev_t */
        unsigned char           s_blocksize_bits;
        unsigned long           s_blocksize;
        loff_t                  s_maxbytes;     /* Max file size */
        struct file_system_type *s_type;
        const struct super_operations   *s_op;
        const struct dquot_operations   *dq_op;
        const struct quotactl_ops       *s_qcop;
        const struct export_operations *s_export_op;
        unsigned long           s_flags;
        unsigned long           s_magic;
        struct dentry           *s_root;
        struct rw_semaphore     s_umount;
        int                     s_count;
        atomic_t                s_active;
#ifdef CONFIG_SECURITY
        void                    *s_security;
#endif
        const struct xattr_handler **s_xattr;

        struct list_head        s_inodes;       /* all inodes */
       struct hlist_bl_head    s_anon;         /* anonymous dentries for (nfs) exporting */
#ifdef __GENKSYMS__
#ifdef CONFIG_SMP
        struct list_head __percpu *s_files;
#else
        struct list_head        s_files;
#endif
#else
#ifdef CONFIG_SMP
        struct list_head __percpu *s_files_deprecated;
#else
        struct list_head        s_files_deprecated;
#endif
#endif
        struct list_head        s_mounts;       /* list of mounts; _not_ for fs use */
        /* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */
        struct list_head        s_dentry_lru;   /* unused dentry lru */
        RH_KABI_REPLACE_UNSAFE(
                        int     s_nr_dentry_unused,
                        long    s_nr_dentry_unused)     /* # of dentry on lru */
                                                              1401,2-9      41%
...
}
           

建立、管理和撤銷超級塊對象的代碼位于檔案 fs/super.c中。超級塊對象通過alloc_super()函數建立并初始化。在檔案系統安裝時,檔案系統會調用該函數以便從磁盤讀取檔案系統超級塊,并且将其資訊填充到記憶體中的超級塊對象中。

超級塊的操作函數

超級塊對象中最重要的一個域是s_op,它指向超級塊的操作函數表。超級塊操作函數表由iper operations結構休表示,定義在檔案<linux/fs.h>中,其形式如下;

struct super_operations {
        struct inode *(*alloc_inode)(struct super_block *sb);
        void (*destroy_inode)(struct inode *);

        void (*dirty_inode) (struct inode *, int flags);
        int (*write_inode) (struct inode *, struct writeback_control *wbc);
        int (*drop_inode) (struct inode *);
        void (*evict_inode) (struct inode *);
        void (*put_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 (*umount_begin) (struct super_block *);

        int (*show_options)(struct seq_file *, struct dentry *);
        int (*show_devname)(struct seq_file *, struct dentry *);
        int (*show_path)(struct seq_file *, struct dentry *);
        int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
        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);
#endif
        int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
        int (*nr_cached_objects)(struct super_block *);
        void (*free_cached_objects)(struct super_block *, int);
        RH_KABI_EXTEND(struct list_head *(*inode_to_wblist)(struct inode *))
        RH_KABI_EXTEND(struct inode *(*wblist_to_inode)(struct list_head *))
};
           

該結構體中的每一項都是一個指向超級塊操作函數的指針,超級塊操作函數執行檔案系統和索引節點的低層操作。

當檔案系統需要對其超級塊執行操作的時候,首先要在超級塊的對象中尋找需要的操作方法。

sb->s_op->write_super(sb);
/*
在這個調用中,sb是指向檔案系統超級塊的指針,沿着該指針進入超級塊操作函數表s_op,并從表中取得希望得到的write_super()函數,該函數執行寫入超級塊的實際操作。注意,盡管write_super)方法來自超級塊,但是在調用時,還是要把超級塊作為參數傳遞給它,這是因為C缺少對面向對象的支援
*/
           

由于在C語言中無法直接得到操作函數的父對象,是以必須将父對象以參數形式傳給操作函數。

下面給出super_operation中,超級塊操作函數的用法

struct inode *alloc_inode (struct super_block *sb)
           

在給定的超級塊下建立和初始化一個新的索引節點對象。

void destroy_inode (struct inode *inode)
           

釋放給定的索引節點

void dirty_inode(struct inode *inode)
           

VFS在索引節點髒(被修改)時會調用此函數。日志檔案系統(如ext3和ext4)執行該函數進行日志更新。

void write _inode (struct inode *inode ,int wait)
           

用于将給定的索引節點寫入磁盤。wait參數指明寫操作是否需要同步

void drop_inode(struct inode *inode)
           

在最後一個指向索引節點的引用被釋放後,VFS會調用該函數。VFS隻需要簡單地删除這個索引節點後,普通Unix檔案系統就不會定義這個函數了。

void de1ete_inode(struct inode *inode)
           

用于從磁盤上删除給定的索引節點。

void put_super(struct super_block *sb)
           

在解除安裝檔案系統時由VFS 調用,用來釋放超級塊。調用者必須一直持有s_lock鎖

oid write_super(struct super_block *sb)
           

用給定的超級塊更新磁盤上的超級塊。VFS通過該函數對記憶體中的超級塊和磁盤中的超級塊進行同步。調用者必須一直持有s_lock鎖。

int sync_fs (struct super_block *sb, int wait)
           

使檔案系統的資料元與磁盤上的檔案系統同步。wait參數指定操作是否同步。

索引節點對象

索引節點對象包含了核心在操作檔案或目錄時需要的全部資訊。

對于Unix風格的檔案系統來說

這些資訊可以從磁盤索引節點直接讀入。

如果一個檔案系統沒有索引節點,那麼,不管這些相關資訊在磁盤上是怎麼存放的,檔案系統都必須從中提取這些資訊。

沒有索引節點的檔案系統通常将檔案的描述資訊作為檔案的一部分來存放。

這些檔案系統與Unix風格的檔案系統不同,沒有将資料與控制資訊分開存放。有些現代檔案系統使用資料庫來存儲檔案的資料。不管哪種情況、采用哪種方式,索引節點對象必須在記憶體中建立,以便于檔案系統使用。

引自:/usr/src/kernels/3.10.0-1160.el7.x86_64/include/linux/fs.h

struct inode {
        umode_t                 i_mode;
        unsigned short          i_opflags;
        kuid_t                  i_uid;
        kgid_t                  i_gid;
        unsigned int            i_flags;

#ifdef CONFIG_FS_POSIX_ACL
        struct posix_acl        *i_acl;
        struct posix_acl        *i_default_acl;
#endif

        const struct inode_operations   *i_op;
        struct super_block      *i_sb;
        struct address_space    *i_mapping;

#ifdef CONFIG_SECURITY
        void                    *i_security;
#endif
...
}
           

一個索引節點代表檔案系統中(但是索引節點僅當檔案被通路時,才在記憶體中建立〉的一個檔案,它也可以是裝置或管道這樣的特殊檔案。是以索引節點結構體中有一些和特殊檔案相關的項,比如i_pipe項就指向一個代表有名管道的資料結構,i_bdev指向塊裝置結構體,i_cdev指向字元裝置結構體。這三個指針被存放在一個公用體中,因為一個給定的索引節點每次隻能表示三者之一(或三者均不)。

索引節點操作

struct inode_operations {
        struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
        void * (*follow_link) (struct dentry *, struct nameidata *);
        int (*permission) (struct inode *, int);
        struct posix_acl * (*get_acl)(struct inode *, int);

        int (*readlink) (struct dentry *, char __user *,int);
        void (*put_link) (struct dentry *, struct nameidata *, void *);

        int (*create) (struct inode *,struct dentry *, umode_t, bool);
        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 *,umode_t);
        int (*rmdir) (struct inode *,struct dentry *);
        int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
        int (*rename) (struct inode *, struct dentry *,
                        struct inode *, struct dentry *);
        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);
...
}
           

下面這些接口由各種函數組成,在給定的節點上,可能由VFS執行這些函數,也可能由具體的檔案系統執行:

int create(struct inode *dir,struct dentry *dentry,int mode)
           

VFS通過系統調用create()和open()來調用該函數,進而為dentry對象建立一個新的索引節點。在建立時使用mode指定的初始模式。

struct dentry * lookup(struct inode *dir,struct dentry *dentry)
           

該函數在特定目錄中尋找索引節點,該索引節點要對應于denrty中給出的檔案名。

int link(struct dentry *old_dentry,struct inode *dir,struct dentry *dentry)
           

該函數被系統調用link()調用,用來建立硬連接配接。硬連接配接名稱由dentry參數指定,連接配接對象是dir目錄中old_dentry目錄項所代表的檔案。

int unlink (struct inode *dir,struct dentry *dentry)
           

該函數被系統調用unlink()調用,從目錄dir中删除由目錄項dentry 指定的索引節點對象。

int symlink (struct inode *dir,struct dentry *dentry.const char symname)
           

該函數被系統調用symlik()調用,建立符号連接配接。該符号連接配接名稱由symname指定,連接配接對象是dir目錄中的dentry目錄項。

int mkdir(struct inode *dir,struct dentry *dentry.int mode)
           

該函數被系統調用mkdir()調用,建立一個新目錄。建立時使用mode指定的初始模式

nt rmdir(struct inode *dir.struct dentry dentry)
           

調用rmdir()調用,删除dir目錄中的dentry目錄項代表的檔案

...

繼續閱讀