沒想到Virtual File System VFS還是比較受歡迎的,是以今天詳細寫一下這一部分。
VFS是一個抽象層,對不同檔案系統的實作屏蔽,對上提供統一的接口。

這張圖是Linux核心中對于VFS相關資料結構的描述。
每一個程序在核心中,都對應一個task_struct結構
include/linux/sched.h中有
struct task_struct {
/* Open file information: */
struct files_struct *files;
從注釋就可以看出是對所有打開的檔案的一個結構。
struct files_struct {
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
這裡面有一個資料,儲存了打開的所有檔案,每個檔案有一個檔案描述符File Desicriptor FD,其中預設開啟的有stdin, stdout, stderr,分别為0,1,2。
是以在指令行執行一個背景指令的時候常這樣用:
nohup run_command.sh > run.log 2>&1 &
也即我們把stderr和并到stdout裡面,全部輸出到檔案run.log裡面。
整個系統所有打開的檔案儲存在同一個連結清單中的,當一個檔案被打開多次的時候,f_count記錄被打開的次數,一般的檔案系統不保護檔案被多個程序寫入,需要程序之間通過同步機制做這件事情。一旦一個檔案被多個程序打開,如果在指令行删除檔案之後,檔案可能不可見了,但是不會被删除,已經打開的程序仍然可以讀寫檔案,直到引用為0。
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
其中path為
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
這裡dentry稱為directory cache,顧名思義是一個緩存,為了查詢快的,從系統啟動開始,所有被引用過的檔案,都會在這裡緩存一下,在dentry結構裡面有hashlist,可以友善通過檔案或者路徑名進行查找,有lru list,可以不斷的淘汰。
這裡vfsmount,稱為mount list,每個被mount的linux檔案系統,都會對應一項。對于被Mount的檔案系統的跟路徑和mount point的路徑,各對應一個dentry。
如圖是dentry和vfsmount的對應關系,對于左上角的圖。
對于作業系統的根路徑/對應一個dentry和一個vfsmount,還有一個file結構指向dentry和vfsmount。
home路徑是一個mount point,因而對應兩個dentry,一個表示上面這個檔案系統的mount point,一個表示下面這個檔案系統的root directory。有一個vfsmount對應于home路徑,parent指向/對應的vfsmount。有一個file結構指向root directory的dentry和vfsmount。
project路徑也是一個mount point,也對應兩個dentry,有一個vfsmount,并且parent指向home對應的vfsmount。有一個file結構指向root directory的dentry和vfsmount。
對于普通的檔案或者路徑data和guide,各有一個dentry對應,各有一個file指向相應的dentry,vfsmount都指向project的vfsmount。
在file這個結構中,最本質的是struct inode *f_inode,了解檔案系統結構的同學知道,每個檔案都有一個inode儲存資訊。
如圖所示,檔案系統會有SuperBlock,還有Inode BitMap,通過Inode的一個ID号,可以在Inode Table裡面找到對應的inode。
Inode裡面儲存的是這個檔案的資料儲存在了哪些block中。
核心記憶體中的inode是硬碟上inode的一個緩存。
struct inode {
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
/* Stat data, not accessed from path walking */
unsigned long i_ino;
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
unsigned int i_blkbits;
blkcnt_t i_blocks;
const struct file_operations *i_fop;
其中inode_operation是對inode可以執行的操作,file_operation是對檔案可以執行的操作,對于不同的檔案系統,這兩個結構是不同的。
對于NFS來講,有下面的檔案系統類型
struct file_system_type nfs_fs_type = {
.owner = THIS_MODULE,
.name = "nfs",
.mount = nfs_fs_mount,
.kill_sb = nfs_kill_super,
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
};
需要注冊檔案系統給VFS
ret = register_filesystem(&nfs_fs_type);
ret = register_nfs4_fs();
當應用層調用系統調用Mount的時候,會在核心裡面調用
long do_mount(const char *dev_name, const char __user *dir_name,
const char *type_page, unsigned long flags, void *data_page)
最終會調用
struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct mount *mnt;
struct dentry *root;
mnt = alloc_vfsmnt(name);
root = mount_fs(type, flags, name, data);
mnt->mnt.mnt_root = root;
mnt->mnt.mnt_sb = root->d_sb;
mnt->mnt_mountpoint = mnt->mnt.mnt_root;
mnt->mnt_parent = mnt;
lock_mount_hash();
list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
unlock_mount_hash();
return &mnt->mnt;
}
其中
struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
struct dentry *root;
struct super_block *sb;
root = type->mount(type, flags, name, data);
到這裡會調用NFS這個具體的檔案系統的函數
struct dentry *nfs_fs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *raw_data)
裡面重要的兩步如下:
nfs_mod = get_nfs_version(mount_info.parsed->version);
mntroot = nfs_mod->rpc_ops->try_mount(flags, dev_name, &mount_info, nfs_mod);
已經開始調用rpc層了。
struct dentry *nfs_try_mount(int flags, const char *dev_name,
struct nfs_mount_info *mount_info,
struct nfs_subversion *nfs_mod)
會調用
struct nfs_server *nfs3_create_server(struct nfs_mount_info *mount_info,
struct nfs_subversion *nfs_mod)
{
struct nfs_server *server = nfs_create_server(mount_info, nfs_mod);
/* Create a client RPC handle for the NFS v3 ACL management interface */
if (!IS_ERR(server))
nfs_init_server_aclclient(server);
return server;
}
最終會建立RPC的Client,進行互相通信。
error = nfs_init_server_rpcclient(server, &timeparms,
data->selected_flavor);
是以是符合上述過程的。
RPC層就比較複雜了。