參考《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>

/*
* 超級塊結構中定義的字段許多,
* 這裡僅僅介紹一些重要的屬性
*/
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調用該函數中斷安裝操作 */
};

2.2 索引節點
索引節點是VFS中的核心概念,它包括核心在操作檔案或檔案夾時須要的所有資訊。
一個索引節點代表檔案系統中的一個檔案(這裡的檔案不僅是指我們平時所覺得的普通的檔案,還包含檔案夾,特殊裝置檔案等等)。
索引節點和超級塊一樣是實際存儲在磁盤上的,當被應用程式訪問到時才會在記憶體中建立。
索引節點定義在:<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 私有指針 */
};
/*
* 當中的 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);
};

2.5 四個對象之間關系圖
上面分别介紹了4種對象分别的屬性和方法,以下用圖來展示這4個對象的和VFS之間關系以及4個對象之間的關系。
(這個圖是依據我自己的了解畫出來的,假設由錯誤請幫忙指出,謝謝!)
3. 檔案系統相關的資料結構
處理上面4個基本的對象之外,VFS中還有2個專門針對檔案系統的2個對象,
- struct file_system_type: 用來描寫叙述檔案系統的類型(比方ext3,ntfs等等)
- struct vfsmount : 描寫叙述一個安裝檔案系統的執行個體
file_system_type 結構體位于:<linux/fs.h>

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

每種檔案系統,無論由多少個執行個體安裝到系統中,還是根本沒有安裝到系統中,都僅僅有一個 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 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;