內核-虛擬文件系統

files_struct 概述
虛擬文件系統(VFS)是linux內核和具體I/O設備之間的封裝的一層共通訪問接口,通過這層接口,linux內核可以以同一的方式訪問各種I/O設備。虛擬文件系統本身是linux內核的一部分,是純軟件的東西,並不需要任何硬件的支持。
虛擬文件系統又稱虛擬文件系統轉換(Virual Filesystem Switch ,簡稱VFS)。說它虛擬,是因爲它所有的數據結構都是在運行以後才建立,並在卸載時刪除,而在磁盤上並沒有存儲這些數據結構,顯然如果只有VFS,系統是無法工作的,因爲它的這些數據結構不能憑空而來,只有與實際的文件系統,如Ext2、Minix、MSDOS、VFAT等相結合,才能開始工作,所以VFS並不是一個真正的文件系統。與VFS相對,我們稱Ext2、Minix、MSDOS等爲具體文件系統。
1.虛擬文件系統的作用
在第一章Linux內核結構一節中,我們把VFS稱爲內核的一個子系統,其它子系統只與VFS打交道,而並不與具體文件系統發生聯繫。對具體文件系統來說,VFS是一個管理者,而對內核的其它子系統來說,VFS是它們與具體文件系統的一個接口,整個Linux中文件系統的邏輯關係如圖8.1所示:

圖8.1 Linux中文件系統的邏輯關係示意圖
圖8.1這裏寫圖片描述
VFS提供一個統一的接口(實際上就是file_operatoin數據結構,稍後介紹),一個具體文件系統要想被Linux支持,就必須按照這個接口編寫自己的操作函數,而將自己的細節對內核其它子系統隱藏起來。因而,對內核其它子系統以及運行在操作系統之上的用戶程序而言,所有的文件系統都是一樣的。實際上,要支持一個新的文件系統,主要任務就是編寫這些接口函數。
概括說來,******VFS主要有以下幾個作用:
(1)對具體文件系統的數據結構進行抽象,以一種統一的數據結構進行管理。
(2)接受用戶層的系統調用 ,例如write、open、stat、link等等。
(3)支持多種具體文件系統之間相互訪問。
(4)接受內核其他子系統的操作請求,特別是內存管理子系統。
(5)簡化了應用程序的開發:應用通過統一的系統調用訪問各種存儲介質
(6)簡化了新文件系統加入內核的過程:新文件系統只要實現VFS的各個接口即可,不需要修改內核部分**
通過VFS,Linux可以支持很多種具體文件系統。

  1. 虛擬文件系統的4個主要對象
    虛擬文件中的4個主要對象,具體每個對象的含義參見如下的詳細介紹。
    通用文件模型由下列對象類型組成:
    ·超級塊(superblock)對象: 存放系統中已安裝文件系統的有關信息。對於基於磁盤的文件系統,這類對象通常對應於存放在磁盤上的文件系統控制塊,也就是說,每個文件系統都有一個超級塊對象.
    ·索引節點(inode)對象: 存放關於具體文件的一般信息。對於基於磁盤的文件系統,這類對象通常對應於存放在磁盤上的文件控制塊(FCB),也就是說,每個文件都有一個索引節點對象。每個索引節點對象都有一個索引節點號,這個號唯一地標識某個文件系統中的指定文件。
    ·目錄項(dentry)對象: 存放目錄項與對應文件進行鏈接的信息。VFS把每個目錄看作一個由若干子目錄和文件組成的常規文件。例如,在查找 路徑名/tmp/test時 , 內核爲 根目錄“/ ”創建一個目錄項對象, 爲根目錄下的 tmp項創建一個第二級目錄項對象,爲 /tmp 目錄下的test項創建一個第三級目錄項對象。
    ·文件(file)對象: 存放打開文件與進程之間進行交互的有關信息。這類信息僅當進程訪問文件期間存在於內存中。
    下面我們討論超級塊、索引節點、目錄項及文件的數據結構,它們的共同特點有兩個:
    ·充分考慮到對多種具體文件系統的兼容性
    ·是“虛”的,也就是說只能存在於內存

2.1 超級塊
超級塊(super_block)主要存儲文件系統相關的信息,這是個針對文件系統級別的概念。
它一般存儲在磁盤的特定扇區中,對應於存放在磁盤上的文件系統控制塊。但是對於那些基於內存的文件系統(比如proc,sysfs),超級塊是在使用時創建在內存中的。
超級塊的定義在:

“`
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;                /* 安裝權限 */
u;                                /*一個共用體,其成員是各種文件系統
                                  的 fsname_sb_info數據結構 */

};/*
* 超級塊結構中定義的字段非常多,
* 這裏只介紹一些重要的屬性
*/

/*
* 其中的 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調用該函數中斷安裝操作 */

};
“`超級塊最後一個u 聯合體域包括屬於具體文件系統的超級塊信息:

這裏寫代碼片union {
                struct Minix_sb_info    Minix_sb;
                struct Ext2_sb_info     Ext2_sb;
                struct ext3_sb_info     ext3_sb;
                struct hpfs_sb_info     hpfs_sb;
                struct ntfs_sb_info     ntfs_sb;
                struct msdos_sb_info    msdos_sb;
                struct isofs_sb_info    isofs_sb;
                struct nfs_sb_info      nfs_sb;
                struct sysv_sb_info     sysv_sb;
                struct affs_sb_info     affs_sb;
                struct ufs_sb_info      ufs_sb;
                struct efs_sb_info      efs_sb;
                struct shmem_sb_info    shmem_sb;
                struct romfs_sb_info    romfs_sb;
                struct smb_sb_info      smbfs_sb;
                struct hfs_sb_info      hfs_sb;
                struct adfs_sb_info     adfs_sb;
                struct qnx4_sb_info     qnx4_sb;
                struct reiserfs_sb_info reiserfs_sb;
                struct bfs_sb_info      bfs_sb;
                struct udf_sb_info      udf_sb;
               struct ncp_sb_info      ncpfs_sb;
                struct usbdev_sb_info   usbdevfs_sb;
               struct jffs2_sb_info    jffs2_sb;
               struct cramfs_sb_info   cramfs_sb;
                void                    *generic_sbp;
       } u;

``
         通常,爲了效率起見u域的數據被複制到內存。任何基於磁盤的文件系統都需要訪問和更改自己的磁盤分配位示圖,以便分配和釋放磁盤塊。VFS允許這些文件系統直接對內存超級塊的u聯合體域進行操作,無需訪問磁盤。

     2.2 索引節點
索引節點是VFS中的核心概念,它包含內核在操作文件或目錄時需要的全部信息。
一個索引節點代表文件系統中的一個文件(這裏的文件不僅是指我們平時所認爲的普通的文件,還包括目錄,特殊設備文件等等)。
索引節點和超級塊一樣是實際存儲在磁盤上的,當被應用程序訪問到時纔會在內存中創建。
文件名可以隨時更改,但是索引節點對文件是唯一的,並且隨文件的存在而存在。這裏主要是強調一點,具體文件系統的索引節點是存儲在磁盤上的,是一種靜態結構,要使用它,必須調入內存,填寫VFS的索引節點,因此,也稱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 *);
};

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>
/* 目錄項對象結構 */
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);
};

2.4 文件對象

文件對象表示進程已打開的文件,從用戶角度來看,我們在代碼中操作的就是一個文件對象。

文件對象反過來指向一個目錄項對象(目錄項反過來指向一個索引節點)

其實只有目錄項對象才表示一個已打開的實際文件,雖然一個文件對應的文件對象不是唯一的,但其對應的索引節點和目錄項對象卻是唯一的。
文件對象的定義在: <linux/fs.h>
 * 文件對象結構中定義的字段非常多,
 * 這裏只介紹一些重要的屬性
 */
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);
};
![這裏寫圖片描述](http://img.blog.csdn.net/20150312160805256)
![這裏寫圖片描述](http://img.blog.csdn.net/20150312161029244)
3. 文件系統相關的數據結構

處理上面4個主要的對象之外,VFS中還有2個專門針對文件系統的2個對象,

- struct file_system_type: 用來描述文件系統的類型(比如ext3,ntfs等等)

- struct vfsmount        : 描述一個安裝文件系統的實例



file_system_type 結構體位於:<linux/fs.h>
    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;
};

每種文件系統,不管由多少個實例安裝到系統中,還是根本沒有安裝到系統中,都只有一個 file_system_type 結構。



當文件系統被實際安裝時,會在安裝點創建一個 vfsmount 結構體。

結構體代表文件系統的實例,也就是文件系統被安裝幾次,就會創建幾個 vfsmount

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

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

};



4. 進程相關的數據結構

以上介紹的都是在內核角度看到的 VFS 各個結構,所以結構體中包含的屬性非常多。

而從進程的角度來看的話,大多數時候並不需要那麼多的屬性,所有VFS通過以下3個結構體和進程緊密聯繫在一起。

- struct files_struct  :由進程描述符中的 files 目錄項指向,所有與單個進程相關的信息(比如打開的文件和文件描述符)都包含在其中。

- struct fs_struct     :由進程描述符中的 fs 域指向,包含文件系統和進程相關的信息。

- struct mmt_namespace :由進程描述符中的 mmt_namespace 域指向。



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

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]; /* 缺省的文件對象數組 */
};


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];           /* 缺省的文件對象數組 */
};

struct fs_struct 位於:<linux/fs_struct.h

struct fs_struct {
int users; /* 用戶數目 */
rwlock_t lock; /* 保護結構體的讀寫鎖 */
int umask; /* 掩碼 */
int in_exec; /* 當前正在執行的文件 */
struct path root, pwd; /* 根目錄路徑和當前工作目錄路徑 */
};




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;

“`

  1. 小結

VFS 統一了文件系統的實現框架,使得在linux上實現新文件系統的工作變得簡單。

目前linux內核中已經支持60多種文件系統,具體支持的文件系統可以查看 內核源碼 fs 文件夾下的內容。

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