虛擬檔案系統

虛擬檔案系統(有時也稱作虛擬檔案交換,更常見的是簡稱VFS)作為核心子系統,為使用者空間程式提供了檔案和檔案系統相關的介面。

之所以可以使用這種通用介面對所有型別的檔案系統進行操作,是因為核心在它的底層檔案系統介面上建立了一個VFS抽象層,該抽象層使Linux能夠支援各種檔案系統,即便是它們在功能和行為上存在很大的差別。

VFS抽象層能夠支援各種各樣的檔案系統,因為它定義了所有檔案系統都支援的、基本的、概念上的介面和資料結構。而實際的檔案系統的程式碼在統一的介面和資料結構下隱藏了具體的實現細節。

Unix檔案系統

Unix使用了四種和檔案系統相關的傳統抽象概念:檔案、目錄項、索引節點和安裝點。
1. 檔案:檔案就是一個有序位元組串,位元組串中第一個位元組是檔案的頭,最後一個位元組是檔案的尾。典型的檔案操作有讀、寫、建立和刪除等。
2. 目錄項:檔案路徑中的每一部分都被稱作目錄條目。“/home/wolfman/butter”是檔案路徑的一個例子——根目錄/,目錄home,wolfman和檔案butter都是目錄條目,它們統稱為目錄項。(在Unix中,目錄屬於普通檔案,它列出包含在其中的所有檔案。)
3. 索引節點:Unix系統將檔案的相關資訊和檔案本身這兩個概念加以區分,例如訪問控制權限、大小、擁有者、建立時間等資訊。檔案相關資訊,有時被稱作檔案的元資料,被儲存在一個單獨的資料結構中,該結構被稱為索引節點(inode)。
4. 安裝點:在Unix中,檔案系統被安裝在一個特定的安裝點上,該安裝點在全域性層次結構中被稱作名稱空間,所有的已安裝檔案系統都作為根檔案系統樹的枝葉出現在系統中。

VFS物件及其資料結構

VFS採用的是面向物件的設計思路,但在C語言中,只能使用結構體方式實現,即包含資料的同時又包含操作這些資料的函式指標。

VFS中有四個主要的物件型別,它們分別是:

  • 超級塊物件,代表一個具體的已安裝檔案系統。
  • 索引節點物件,代表一個具體檔案。
  • 目錄項物件,代表一個目錄項,是路徑的一個組成部分。
  • 檔案物件,代表有程序開啟的檔案。

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

  • super_operations物件:包含核心針對特定檔案系統所能呼叫的方法。
  • inode_operations物件:包含核心對特定檔案所能呼叫的方法。
  • dentry_operations物件:包含核心對特定目錄所能呼叫的方法。
  • file_operations物件:包含程序針對已開啟檔案所能呼叫的方法。

超級塊物件

各種檔案系統都必須實現超級塊物件,該物件用於儲存特定檔案系統的資訊,通常對應於存放在磁碟特定扇區的檔案系統超級塊或檔案系統控制塊。對於並非基於磁碟的檔案系統(基於記憶體的檔案系統sysfs、proc等),它們會在使用時建立超級塊並將其儲存到記憶體中。
超級塊物件由結構體super_block表示,定義在 linux/fs.h 中,下面列出了一些重要的屬性:

/**
 * 超級塊結構中定義的欄位非常多,
 * 這裡只介紹一些重要的屬性
 */
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結構體表示,定義在 linux/fs.h 中,其形式如下:

/*
 * 其中的 s_op 中定義了超級塊的操作方法
 * 這裡只介紹一些相對重要的函式
 */
struct super_operations {

    /* 建立和初始化一個索引節點物件 */
    struct inode *(*alloc_inode)(struct super_block *sb);

    /* 釋放給定的索引節點 */
    void (*destroy_inode)(struct inode *);

    /* VFS在索引節點被修改時會呼叫這個函式 */
    void (*dirty_inode) (struct inode *); 

    /* 將索引節點寫入磁碟,wait表示寫操作是否需要同步 */
    int (*write_inode) (struct inode *, int);

    /* 最後一個指向索引節點的引用被刪除後,VFS會呼叫這個函式 */
    void (*drop_inode) (struct inode *);

    /* 從磁碟上刪除指定的索引節點 */
    void (*delete_inode) (struct inode *);

    /* 解除安裝檔案系統時由VFS呼叫,用來釋放超級塊 */
    void (*put_super) (struct super_block *);

    /* 用給定的超級塊更新磁碟上的超級塊 */
    void (*write_super) (struct super_block *);

    /* 使檔案系統中的資料與磁碟上的資料同步 */
    int (*sync_fs)(struct super_block *sb, int wait);

    /* VFS呼叫該函式獲取檔案系統狀態 */
    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 *);
};

索引節點物件

索引節點物件包含了核心在操作檔案或目錄時需要的全部資訊。索引節點物件在記憶體中建立,以便於檔案系統使用。

索引節點物件由inode結構體表示,它定義在 linux/fs.h 中,下面列出了一些重要的屬性:

/* 
 * 索引節點結構中定義的欄位非常多,
 * 這裡只介紹一些重要的屬性
 */
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 私有指標 */
};

索引節點物件的操作方法由結構體inode_operations定義,這裡只介紹一些重要的函式:

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 *);
};

目錄項物件

VFS把目錄當作檔案對待,檔案都可以由索引節點物件表示。
VFS經常需要執行目錄相關的操作,比如檔案查詢等。為了方便查詢操作,VFS引入了目錄項概念。在路徑中(包括普通檔案在內),每一個部分都是目錄項物件。
VFS在執行目錄操作時(如果需要的話)會現場建立目錄項物件。
目錄項物件由dentry結構體表示,定義在檔案 linux/dcache.h 中,下面列出幾個重要的選項:

struct dentry {
    atomic_t                d_count;        /* 使用計數 */
    unsigned int            d_flags;        /* 目錄項標識 */
    struct inode            *d_inode;       /* 相關聯的索引節點 */
    struct hlist_node       d_hash;         /* 散列表 */
    struct dentry           *d_parent;      /* 父目錄的目錄項物件 */
    struct qstr             d_name;         /* 目錄項名稱 */
    struct list_head        d_subdirs;      /* 子目錄連結串列 */
    struct list_head        d_alias;        /* 索引節點別名連結串列 */
    struct dentry_operations    *d_op;      /* 目錄項操作指標 */
    ...
}

目錄項物件有三種有效狀態:被使用、未被使用和負狀態。被使用和未被使用的目錄項都對應一個有效的索引節點,而負狀態的目錄項沒有對應的有效索引節點。

為了減少VFS層遍歷檔案路徑的時間,核心將目錄項物件快取在目錄項快取(簡稱dcache)中。目錄項快取包括三個主要部分:

  1. “被使用的”目錄項鍊表;
  2. “未被使用的”雙向連結串列;
  3. 散列表和相應的雜湊函式。

dentry_operations結構體指明瞭VFS操作目錄項的所有方法,定義在檔案 linux/dcache.h 中:

struct dentry_operations {

    /* 判斷目錄物件是否有效 */
    int (*d_revalidate)(struct dentry *, struct nameidata *);

    /* 為目錄項物件生成雜湊值 */
    int (*d_hash)(struct dentry *, struct qstr *);

    /* 比較name1和name2這兩個檔名 */
    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);
}

檔案物件

檔案物件是已開啟的檔案在記憶體中的表示。該物件由相應的open()系統呼叫建立,由close()系統呼叫撤銷,所有這些檔案相關的呼叫實際上都是檔案操作表中定義的方法。
檔案物件由file結構體表示,定義在檔案 linux/fs.h 中,下面給出幾個重要的選項:

struct file {
    struct path f_path; /* 包含目錄項 */
    struct file_operations *f_op;  /* 檔案操作表 */
    atomic_t f_count; /* 檔案物件的使用計數 */
    unsigned int f_flags; /* 當開啟檔案時所指定的標誌 */
    mode_t f_mode; /* 檔案的訪問模式 */
    loff_t f_pos; /* 檔案當前的位移量(檔案指標)*/
    ......
}

檔案物件的操作由file_operations結構體表示,定義在檔案 linux/fs.h 中:

struct file_operations { 

    /* 擁有該結構的模組的指標,一般為THIS_MODULES */
    struct module *owner;

    /* 用來修改檔案當前的讀寫位置 */
    loff_t (*llseek) (struct file *, loff_t, int);

    /* 從裝置中同步讀取資料 */
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 

    /* 向裝置傳送資料 */
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    /* 初始化一個非同步的讀取操作 */
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    /* 初始化一個非同步的寫入操作 */
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    /* 僅用於讀取目錄,對於裝置檔案,該欄位為NULL */
    int (*readdir) (struct file *, void *, filldir_t);

    /* 輪詢函式,判斷目前是否可以進行非阻塞的讀寫或寫入 */
    unsigned int (*poll) (struct file *, struct poll_table_struct *); 

    /* 執行裝置I/O控制命令 */
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 

    /* 不使用BLK檔案系統,將使用此種函式指標代替ioctl */
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    /* 在64位系統上,32位的ioctl呼叫將使用此函式指標代替 */
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 

    /* 用於請求將裝置記憶體對映到程序地址空間 */
    int (*mmap) (struct file *, struct vm_area_struct *); //

    /* 開啟檔案 */
    int (*open) (struct inode *, struct file *); 

    int (*flush) (struct file *, fl_owner_t id); 

    int (*release) (struct inode *, struct file *); 

    /* 重新整理待處理的資料 */
    int (*fsync) (struct file *, struct dentry *, int datasync);  

    /* 非同步重新整理待處理的資料 */
    int (*aio_fsync) (struct kiocb *, int datasync); 

    /* 通知裝置FASYNC標誌發生變化 */
    int (*fasync) (int, struct file *, int); 

    int (*lock) (struct file *, int, struct file_lock *); 

    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); 

    int (*check_flags) (int); 

    int (*flock) (struct file *, int, struct file_lock *);

    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 

    int (*setlease)(struct file *, long, struct file_lock **); 

};
.