虛擬檔案系統(VFS)是linux核心和具體I/O裝置之間的封裝的一層共通訪問介面,通過這層介面,linux核心可以以同一的方式訪問各種I/O裝置。
虛擬檔案系統本身是linux核心的一部分,是純軟體的東西,並不需要任何硬體的支援。
主要內容:
- 虛擬檔案系統的作用
- 虛擬檔案系統的4個主要物件
- 檔案系統相關的資料結構
- 程式相關的資料結構
- 小結
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;
5. 小結
VFS 統一了檔案系統的實現框架,使得在linux上實現新檔案系統的工作變得簡單。
目前linux核心中已經支援60多種檔案系統,具體支援的檔案系統可以檢視 核心原始碼 fs 資料夾下的內容。