天天看點

Linux虛拟檔案系統VFS解決

參考《Linux核心設計與實作》

虛拟檔案系統(VFS)它是linux核心和詳細I/O一個普通的通路接口之間的包裝裝置,通過這層界面,linux核心能夠以同一的方式訪問各種I/O裝置。

虛拟檔案系統本身是linux核心的一部分,是純軟體的東西。并不須要不論什麼硬體的支援。

1. 虛拟檔案系統的作用

虛拟檔案系統(VFS)是linux核心和儲存設備之間的抽象層,主要有下面優點。

- 簡化了應用程式的開發:應用通過統一的系統調用訪問各種存儲媒體

- 簡化了新檔案系統增加核心的過程:新檔案系統僅僅要實作VFS的各個接口就可以,不須要改動核心部分

 2. 虛拟檔案系統的4個主要對象虛拟檔案裡的4個主要對象,具體每一個對象的含義參見例如以下的具體介紹。

 2.1 超級塊

超級塊(super_block)主要存儲檔案系統相關的資訊,這是個針對檔案系統級别的概念。

它一般存儲在磁盤的特定扇區中,可是對于那些基于記憶體的檔案系統(比方proc,sysfs),超級塊是在使用時建立在記憶體中的。

超級塊的定義在:<linux/fs.h>

Linux虛拟檔案系統VFS解決

/* 
 * 超級塊結構中定義的字段許多,
 * 這裡僅僅介紹一些重要的屬性
 */
struct super_block {
    struct list_head    s_list;               /* 指向全部超級塊的連結清單 */
    const struct super_operations    *s_op; /* 超級塊方法 */
    struct dentry        *s_root;           /* 檔案夾挂載點 */
    struct mutex        s_lock;            /* 超級塊信号量 */
    int            s_count;                   /* 超級塊引用計數 */

    struct list_head    s_inodes;           /* inode連結清單 */
    struct mtd_info        *s_mtd;            /* 存儲磁盤資訊 */
    fmode_t            s_mode;                /* 安裝權限 */
};

/*
 * 當中的 s_op 中定義了超級塊的操作方法
 * 這裡僅僅介紹一些相對重要的函數
 */
struct super_operations {
       struct inode *(*alloc_inode)(struct super_block *sb); /* 建立和初始化一個索引節點對象 */
    void (*destroy_inode)(struct inode *);                /* 釋放給定的索引節點 */

       void (*dirty_inode) (struct inode *);                 /* VFS在索引節點被改動時會調用這個函數 */
    int (*write_inode) (struct inode *, int);             /* 将索引節點寫入磁盤。wait表示寫操作是否須要同步 */
    void (*drop_inode) (struct inode *);                  /* 最後一個指向索引節點的引用被删除後,VFS會調用這個函數 */
    void (*delete_inode) (struct inode *);                /* 從磁盤上删除指定的索引節點 */
    void (*put_super) (struct super_block *);             /* 解除安裝檔案系統時由VFS調用,用來釋放超級塊 */
    void (*write_super) (struct super_block *);           /* 用給定的超級塊更新磁盤上的超級塊 */
    int (*sync_fs)(struct super_block *sb, int wait);     /* 使檔案系統中的資料與磁盤上的資料同步 */
    int (*statfs) (struct dentry *, struct kstatfs *);    /* VFS調用該函數擷取檔案系統狀态 */
    int (*remount_fs) (struct super_block *, int *, char *); /* 指定新的安裝選項又一次安裝檔案系統時,VFS會調用該函數 */
    void (*clear_inode) (struct inode *);                 /* VFS調用該函數釋放索引節點,并清空包括相關資料的全部頁面 */
    void (*umount_begin) (struct super_block *);          /* VFS調用該函數中斷安裝操作 */
};      
Linux虛拟檔案系統VFS解決

 2.2 索引節點

索引節點是VFS中的核心概念,它包括核心在操作檔案或檔案夾時須要的所有資訊。

一個索引節點代表檔案系統中的一個檔案(這裡的檔案不僅是指我們平時所覺得的普通的檔案,還包含檔案夾,特殊裝置檔案等等)。

索引節點和超級塊一樣是實際存儲在磁盤上的,當被應用程式訪問到時才會在記憶體中建立。

索引節點定義在:<linux/fs.h>

Linux虛拟檔案系統VFS解決
/* 
 * 索引節點結構中定義的字段許多。
 * 這裡僅僅介紹一些重要的屬性
 */
struct inode {
    struct hlist_node    i_hash;     /* 散清單,用于高速查找inode */
    struct list_head    i_list;        /* 索引節點連結清單 */
    struct list_head    i_sb_list;  /* 超級塊連結清單超級塊  */
    struct list_head    i_dentry;   /* 檔案夾項連結清單 */
    unsigned long        i_ino;      /* 節點号 */
    atomic_t        i_count;        /* 引用計數 */
    unsigned int        i_nlink;    /* 硬連結數 */
    uid_t            i_uid;          /* 使用者id */
    gid_t            i_gid;          /* 使用組id */
    struct timespec        i_atime;    /* 最後訪問時間 */
    struct timespec        i_mtime;    /* 最後改動時間 */
    struct timespec        i_ctime;    /* 最後改變時間 */
    const struct inode_operations    *i_op;  /* 索引節點操作函數 */
    const struct file_operations    *i_fop;    /* 預設的索引節點操作 */
    struct super_block    *i_sb;              /* 相關的超級塊 */
    struct address_space    *i_mapping;     /* 相關的位址映射 */
    struct address_space    i_data;         /* 裝置位址映射 */
    unsigned int        i_flags;            /* 檔案系統标志 */
    void            *i_private;             /* fs 私有指針 */
};

/*
 * 當中的 i_op 中定義了索引節點的操作方法
 * 這裡僅僅介紹一些相對重要的函數
 */
struct inode_operations {
    /* 為dentry對象創造一個新的索引節點 */
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    /* 在特定檔案夾中尋找索引節點,該索引節點要相應于dentry中給出的檔案名稱 */
    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
    /* 建立硬連結 */
    int (*link) (struct dentry *,struct inode *,struct dentry *);
    /* 從一個符号連結查找它指向的索引節點 */
    void * (*follow_link) (struct dentry *, struct nameidata *);
    /* 在 follow_link調用之後,該函數由VFS調用進行清除工作 */
    void (*put_link) (struct dentry *, struct nameidata *, void *);
    /* 該函數由VFS調用,用于改動檔案的大小 */
    void (*truncate) (struct inode *);
};      
Linux虛拟檔案系統VFS解決

 2.3 檔案夾項

和超級塊和索引節點不同,檔案夾項并非實際存在于磁盤上的。

在使用的時候在記憶體中建立檔案夾項對象。事實上通過索引節點已經能夠定位到指定的檔案。

可是索引節點對象的屬性許多。在查找,比較檔案時,直接用索引節點效率不高,是以引入了檔案夾項的概念。

這裡能夠看做又引入了一個抽象層。檔案夾項是對索引節點的抽象。。!

路徑中的每一個部分都是一個檔案夾項,比方路徑: /mnt/cdrom/foo/bar 當中包括5個檔案夾項。/ mnt cdrom foo bar

每一個檔案夾項對象都有3種狀态:被使用,未使用和負狀态

- 被使用:相應一個有效的索引節點。而且該對象由一個或多個使用者

- 未使用:相應一個有效的索引節點。可是VFS目前并沒有使用這個檔案夾項

- 負狀态:沒有相應的有效索引節點(可能索引節點被删除或者路徑不存在了)

檔案夾項的目的就是提高檔案查找,比較的效率。是以訪問過的檔案夾項都會緩存在slab中。

slab中緩存的名稱一般就是 dentry,能夠通過例如以下指令檢視:

[wangyubin@localhost kernel]$ sudo cat /proc/slabinfo | grep dentry
dentry            212545 212625    192   21    1 : tunables    0    0    0 : slabdata  10125  10125      0      

檔案夾項定義在:<linux/dcache.h>

Linux虛拟檔案系統VFS解決
/* 檔案夾項對象結構 */
struct dentry {
    atomic_t d_count;       /* 使用計數 */
    unsigned int d_flags;   /* 檔案夾項辨別 */
    spinlock_t d_lock;        /* 單檔案夾項鎖 */
    int d_mounted;          /* 是否登入點的檔案夾項 */
    struct inode *d_inode;    /* 相關聯的索引節點 */
    struct hlist_node d_hash;    /* 散清單 */
    struct dentry *d_parent;    /* 父檔案夾的檔案夾項對象 */
    struct qstr d_name;         /* 檔案夾項名稱 */
    struct list_head d_lru;        /* 未使用的連結清單 */
    /*
     * d_child and d_rcu can share memory
     */
    union {
        struct list_head d_child;    /* child of parent list */
         struct rcu_head d_rcu;
    } d_u;
    struct list_head d_subdirs;    /* 子檔案夾連結清單 */
    struct list_head d_alias;    /* 索引節點别名連結清單 */
    unsigned long d_time;        /* 重置時間 */
    const struct dentry_operations *d_op; /* 檔案夾項操作相關函數 */
    struct super_block *d_sb;    /* 檔案的超級塊 */
    void *d_fsdata;            /* 檔案系統特有資料 */

    unsigned char d_iname[DNAME_INLINE_LEN_MIN];    /* 短檔案名稱 */
};

/* 檔案夾項相關操作函數 */
struct dentry_operations {
    /* 該函數推斷檔案夾項對象是否有效。VFS準備從dcache中使用一個檔案夾項時會調用這個函數 */
    int (*d_revalidate)(struct dentry *, struct nameidata *);
    /* 為檔案夾項對象生成hash值 */
    int (*d_hash) (struct dentry *, struct qstr *);
    /* 比較 qstr 類型的2個檔案名稱 */
    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
    /* 當檔案夾項對象的 d_count 為0時,VFS調用這個函數 */
    int (*d_delete)(struct dentry *);
    /* 當檔案夾項對象将要被釋放時,VFS調用該函數 */
    void (*d_release)(struct dentry *);
    /* 當檔案夾項對象丢失其索引節點時(也就是磁盤索引節點被删除了)。VFS會調用該函數 */
    void (*d_iput)(struct dentry *, struct inode *);
    char *(*d_dname)(struct dentry *, char *, int);
};      
Linux虛拟檔案系統VFS解決

 2.4 檔案對象

檔案對象表示程序已打開的檔案,從使用者角度來看。我們在代碼中操作的就是一個檔案對象。

檔案對象反過來指向一個檔案夾項對象(檔案夾項反過來指向一個索引節點)

事實上僅僅有檔案夾項對象才表示一個已打開的實際檔案,盡管一個檔案相應的檔案對象不是唯一的,但其相應的索引節點和檔案夾項對象卻是唯一的。

檔案對象的定義在: <linux/fs.h>

Linux虛拟檔案系統VFS解決
/* 
 * 檔案對象結構中定義的字段許多,
 * 這裡僅僅介紹一些重要的屬性
 */
struct file {
    union {
        struct list_head    fu_list;    /* 檔案對象連結清單 */
        struct rcu_head     fu_rcuhead; /* 釋放之後的RCU連結清單 */
    } f_u;
    struct path        f_path;             /* 包括的檔案夾項 */
    const struct file_operations    *f_op; /* 檔案操作函數 */
    atomic_long_t        f_count;        /* 檔案對象引用計數 */
};

/*
 * 當中的 f_op 中定義了檔案對象的操作方法
 * 這裡僅僅介紹一些相對重要的函數
 */
struct file_operations {
    /* 用于更新偏移量指針,由系統調用lleek()調用它 */
    loff_t (*llseek) (struct file *, loff_t, int);
    /* 由系統調用read()調用它 */
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    /* 由系統調用write()調用它 */
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    /* 由系統調用 aio_read() 調用它 */
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    /* 由系統調用 aio_write() 調用它 */
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    /* 将給定檔案映射到指定的位址空間上,由系統調用 mmap 調用它 */
    int (*mmap) (struct file *, struct vm_area_struct *);
    /* 建立一個新的檔案對象,并将它和對應的索引節點對象關聯起來 */
    int (*open) (struct inode *, struct file *);
    /* 當已打開檔案的引用計數降低時,VFS調用該函數 */
    int (*flush) (struct file *, fl_owner_t id);
};      
Linux虛拟檔案系統VFS解決

 2.5 四個對象之間關系圖

上面分别介紹了4種對象分别的屬性和方法,以下用圖來展示這4個對象的和VFS之間關系以及4個對象之間的關系。

(這個圖是依據我自己的了解畫出來的,假設由錯誤請幫忙指出,謝謝!)

Linux虛拟檔案系統VFS解決
Linux虛拟檔案系統VFS解決

 3. 檔案系統相關的資料結構

處理上面4個基本的對象之外,VFS中還有2個專門針對檔案系統的2個對象,

- struct file_system_type: 用來描寫叙述檔案系統的類型(比方ext3,ntfs等等)

- struct vfsmount        : 描寫叙述一個安裝檔案系統的執行個體

file_system_type 結構體位于:<linux/fs.h>

Linux虛拟檔案系統VFS解決
struct file_system_type {
    const char *name;   /* 檔案系統名稱 */
    int fs_flags;       /* 檔案系統類型标志 */
    /* 從磁盤中讀取超級塊,而且在檔案系統被安裝時,在記憶體中組裝超級塊對象 */
    int (*get_sb) (struct file_system_type *, int,
               const char *, void *, struct vfsmount *);
    /* 終止訪問超級塊 */
    void (*kill_sb) (struct super_block *);
    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;

    struct lock_class_key i_lock_key;
    struct lock_class_key i_mutex_key;
    struct lock_class_key i_mutex_dir_key;
    struct lock_class_key i_alloc_sem_key;
};      
Linux虛拟檔案系統VFS解決

每種檔案系統,無論由多少個執行個體安裝到系統中,還是根本沒有安裝到系統中,都僅僅有一個 file_system_type 結構。

當檔案系統被實際安裝時。會在安裝點建立一個 vfsmount 結構體。

結構體代表檔案系統的執行個體。也就是檔案系統被安裝幾次,就會建立幾個 vfsmount

vfsmount 的定義參見:<linux/mount.h>

Linux虛拟檔案系統VFS解決
struct vfsmount {
    struct list_head mnt_hash;      /* 散清單 */
    struct vfsmount *mnt_parent;    /* 父檔案系統,也就是要挂載到哪個檔案系統 */
    struct dentry *mnt_mountpoint;    /* 安裝點的檔案夾項 */
    struct dentry *mnt_root;        /* 該檔案系統的根檔案夾項 */
    struct super_block *mnt_sb;        /* 該檔案系統的超級塊 */
    struct list_head mnt_mounts;    /* 子檔案系統連結清單 */
    struct list_head mnt_child;        /* 子檔案系統連結清單 */
    int mnt_flags;                  /* 安裝标志 */
    /* 4 bytes hole on 64bits arches */
    const char *mnt_devname;        /* 裝置檔案名稱 e.g. /dev/dsk/hda1 */
    struct list_head mnt_list;      /* 描寫叙述符連結清單 */
    struct list_head mnt_expire;    /* 到期連結清單的入口 */
    struct list_head mnt_share;        /* 共享安裝連結清單的入口 */
    struct list_head mnt_slave_list;/* 從安裝連結清單 */
    struct list_head mnt_slave;        /* 從安裝連結清單的入口 */
    struct vfsmount *mnt_master;    /* 從安裝連結清單的主人 */
    struct mnt_namespace *mnt_ns;    /* 相關的命名空間 */
    int mnt_id;            /* 安裝辨別符 */
    int mnt_group_id;        /* 組辨別符 */
    /*
     * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
     * to let these frequently modified fields in a separate cache line
     * (so that reads of mnt_flags wont ping-pong on SMP machines)
     */
    atomic_t mnt_count;         /* 使用計數 */
    int mnt_expiry_mark;        /* 假設标記為到期,則為 True */
    int mnt_pinned;             /* "釘住"程序計數 */
    int mnt_ghosts;             /* "鏡像"引用計數 */
#ifdef CONFIG_SMP
    int *mnt_writers;           /* 寫者引用計數 */
#else
    int mnt_writers;            /* 寫者引用計數 */
#endif
};      
Linux虛拟檔案系統VFS解決

 4. 程序相關的資料結構

以上介紹的都是在核心角度看到的 VFS 各個結構,是以結構體中包括的屬性許多。

而從程序的角度來看的話,大多數時候并不須要那麼多的屬性,全部VFS通過下面3個結構體和程序緊密聯系在一起。

- struct files_struct  :由程序描寫叙述符中的 files 檔案夾項指向。全部與單個程序相關的資訊(比方打開的檔案和檔案描寫叙述符)都包括在當中。

- struct fs_struct     :由程序描寫叙述符中的 fs 域指向。包括檔案系統和程序相關的資訊。

- struct mmt_namespace :由程序描寫叙述符中的 mmt_namespace 域指向。

struct files_struct 位于:<linux/fdtable.h>

Linux虛拟檔案系統VFS解決
struct files_struct {
    atomic_t count;      /* 使用計數 */
    struct fdtable *fdt; /* 指向其它fd表的指針 */
    struct fdtable fdtab;/* 基 fd 表 */
    spinlock_t file_lock ____cacheline_aligned_in_smp; /* 單個檔案的鎖 */
    int next_fd;                                       /* 緩存下一個可用的fd */
    struct embedded_fd_set close_on_exec_init;         /* exec()時關閉的檔案描寫叙述符連結清單 */
    struct embedded_fd_set open_fds_init;              /* 打開的檔案描寫叙述符連結清單 */
    struct file * fd_array[NR_OPEN_DEFAULT];           /* 預設的檔案對象數組 */
};      
Linux虛拟檔案系統VFS解決

struct fs_struct 位于:<linux/fs_struct.h>

Linux虛拟檔案系統VFS解決
struct fs_struct {
    int users;               /* 使用者數目 */
    rwlock_t lock;           /* 保護結構體的讀寫鎖 */
    int umask;               /* 掩碼 */
    int in_exec;             /* 目前正在運作的檔案 */
    struct path root, pwd;   /* 根檔案夾路徑和目前工作檔案夾路徑 */
};      
Linux虛拟檔案系統VFS解決

struct mmt_namespace 位于:<linux/mmt_namespace.h>

可是在2.6核心之後似乎沒有這個結構體了,而是用 struct nsproxy 來取代。

下面是 struct task_struct 結構體中關于檔案系統的3個屬性。

struct task_struct 的定義位于:<linux/sched.h>

/* filesystem information */
    struct fs_struct *fs;
/* open file information */
    struct files_struct *files;
/* namespaces */
    struct nsproxy *nsproxy;      

繼續閱讀