解鎖Linux內核黑科技:VFS虛擬文件系統詳解
在Linux內核的廣袤世界里,隱藏著許多精妙的設計,其中虛擬文件系統(VFS)堪稱一顆璀璨的明珠。你是否好奇,為何在 Linux 系統中,無論是常見的本地磁盤文件系統,還是復雜的網絡文件系統,我們都能用統一的方式去操作文件和目錄?又是什么神奇的機制,讓 Linux 能輕松支持種類繁多的文件系統,從古老的 ext2 到如今的 ext4,從分布式的 Ceph 到網絡共享的 NFS?
答案就在 VFS 之中。它就像一個強大的幕后協調者,在用戶與各種千差萬別的文件系統之間,搭建起一座溝通的橋梁,讓一切變得簡單而有序。今天,就讓我們一同揭開 VFS 的神秘面紗,深入探索其內部的運作機制,領略 Linux 內核設計的智慧魅力 。
一、VFS是什么?
在日常生活中,我們可能會遇到這樣的情況:家里有不同的房間,每個房間都有對應的鑰匙。如果沒有統一的鑰匙管理系統,我們每次找鑰匙就會很麻煩,可能要在一堆鑰匙中翻找半天。但如果有一個類似于 “萬能鑰匙接口” 的東西,它可以適配各種鎖芯,只要把不同的鑰匙適配到這個接口上,我們就能用一種統一的方式去開鎖,這會大大提高我們的效率。
在 Linux 內核的世界里,VFS(Virtual File System)就扮演著這樣一個 “萬能鑰匙接口” 的角色 。VFS 是 Linux 內核中一個非常關鍵的抽象層,它的核心使命是為所有類型的文件系統提供一個統一的訪問接口。簡單來說,無論底層是 ext4、XFS 這樣常見的本地磁盤文件系統,還是像 NFS(Network File System)這樣的網絡文件系統,又或是像 procfs、sysfs 這類特殊用途的文件系統,VFS 都能讓它們在 Linux 系統中和諧共處,并為用戶和應用程序提供一致的操作方式。
VFS 就像是一個智能的翻譯官,它把用戶和應用程序發出的各種文件操作請求,比如打開文件、讀取文件、寫入文件等,準確無誤地翻譯成底層不同文件系統能夠理解的指令。同時,它又把底層文件系統返回的結果,以統一的格式呈現給用戶和應用程序。這樣一來,用戶和應用程序就完全不用關心底層文件系統的具體實現細節,只需要和 VFS 進行交互就可以了,極大地簡化了文件操作的復雜性。
另外簡單提一下兩個數據結構:
- 每種注冊到內核的文件系統類型以struct file_system_type結構表示,每種文件系統類型中都有一個鏈表,指向所有屬于該類型的文件系統的超級塊。
- 當一個文件系統掛載到內核文件系統的目錄樹上,會生成一個掛載點,用來管理所掛載的文件系統的信息。該掛載點用一個struct vfsmount結構表示。
1.1文件系統象層
能夠使用通用接口對所有類型的文件系統進行操作,是因為內核在它的底層文件系統接口上建立了一個抽象層。 VFS抽象層之所以能夠銜接各種各樣的文件系統,是因為它定義了所有文件系統都支持的、基本的、概念上的接口和數據結構。
1.2Unix文件系統
Unix使用四種和文件系統相關的傳統抽象概念:文件、目錄項、索引節點和安裝節點 。 linux系統中所有都看作是文件,文件通過目錄組織起來。在Unix系統當中,目錄屬于普通文件,它列出包含在其中的所有文件。 Unix系統當中將文件的相關信息和文件本身這兩個概念加以區分,例如訪問控制權限等。
文件的相關信息,有時也被稱為文件的元數據,被存儲在單獨的數據結構當中,該結構被稱之為索引節點(inode)。 對于其他的一些文件系統,比如FAT NTFS,他們沒有索引節點的概念,但是如果需要在Unix系統中工作的話,還是需要進行封裝,封裝成適合Unix系統的格式。
二、VFS的關鍵特性
2.1統一接口
VFS 提供的統一接口,就像是一個標準化的工具盒,里面裝著各種標準工具,無論面對什么樣的文件系統 “工作”,都能使用這些標準工具來完成 。在 Linux 系統中,常見的文件系統操作,如創建文件、讀取文件、寫入文件、刪除文件、打開文件、關閉文件、重命名文件、獲取文件屬性等,VFS 都為它們提供了統一的系統調用接口。例如,當我們使用open函數來打開一個文件時,無論這個文件是存儲在本地的 ext4 文件系統上,還是位于遠程的 NFS 文件系統中,我們調用open函數的方式和參數都是一樣的。
同樣,read函數用于讀取文件內容,write函數用于寫入文件內容,close函數用于關閉文件,這些函數的使用方式不會因為底層文件系統的不同而改變。對于應用程序開發者來說,他們只需要熟悉這些統一的接口,就可以輕松地編寫與文件系統交互的代碼,而無需花費大量時間去了解不同文件系統的復雜細節。
2.2多文件系統支持
Linux 系統的一大優勢就是能夠同時掛載多種不同類型的文件系統,這都得益于 VFS 強大的多文件系統支持能力。在一臺 Linux 服務器上,我們可能會同時掛載 ext4 文件系統用于存儲系統文件和用戶數據,掛載 NFS 文件系統用于訪問遠程網絡存儲設備上的文件,掛載 procfs 文件系統用于獲取系統進程信息,掛載 sysfs 文件系統用于訪問內核對象和設備信息等。
VFS 就像是一個高效的交通樞紐管理員,負責協調管理這些不同文件系統的工作 。當用戶發起一個文件操作請求時,VFS 會根據文件的路徑信息,準確地判斷出該請求應該由哪個文件系統來處理,然后將請求轉發給相應的文件系統驅動程序。例如,當用戶訪問/home/user/data.txt這個文件時,VFS 會根據/home所在的文件系統信息,確定該文件位于 ext4 文件系統上,然后調用 ext4 文件系統的驅動程序來完成文件的訪問操作;如果用戶訪問/mnt/nfs_share/file.txt,VFS 則會判斷出這是一個 NFS 文件系統上的文件,進而將請求轉發給 NFS 文件系統驅動程序。這種多文件系統支持的特性,使得 Linux 系統能夠適應各種復雜的應用場景,滿足不同用戶的多樣化需求。
2.3抽象對象模型
VFS 通過一套抽象對象模型來管理文件系統,這套模型主要包括超級塊(superblock)、索引節點(inode)、目錄項(dentry)和文件對象(file),它們相互協作,構成了 VFS 管理文件系統的堅實基礎。
(1)超級塊對象super block
超級塊就像是文件系統的 “總管家”,它存儲了整個文件系統的關鍵控制信息 。每個文件系統都有一個對應的超級塊,其中包含了文件系統的類型、塊大小、inode 表信息、空閑塊列表、文件系統的狀態等重要內容。超級塊在文件系統掛載時被讀取到內存中,并且在文件系統的整個生命周期內都發揮著重要作用。它為文件系統的管理和操作提供了全局的視角,比如通過超級塊可以快速了解文件系統的基本屬性,以及獲取文件系統中其他重要數據結構的位置信息。
struct super_block {
/* 全局鏈表元素 */
struct list_head s_list;
/* 底層文件系統所在的設備 */
dev_t s_dev;
/* 文件系統中每一塊的長度 */
unsigned long s_blocksize;
/* 文件系統中每一塊的長度(以2為底的對數) */
unsigned char s_blocksize_bits;
/* 是否需要向磁盤回寫 */
unsigned char s_dirt;
unsigned long long s_maxbytes; /* Max file size */
/* 文件系統類型 */
struct file_system_type *s_type;
/* 超級塊操作方法 */
const struct super_operations *s_op;
struct dquot_operations *dq_op;
struct quotactl_ops *s_qcop;
const struct export_operations *s_export_op;
unsigned long s_flags;
unsigned long s_magic;
/* 全局根目錄的dentry */
struct dentry *s_root;
struct rw_semaphore s_umount;
struct mutex s_lock;
int s_count;
int s_need_sync;
atomic_t s_active;
#ifdef CONFIG_SECURITY
void *s_security;
#endif
struct xattr_handler **s_xattr;
/* 超級塊管理的所有inode的鏈表 */
struct list_head s_inodes; /* all inodes */
/* 臟的inode的鏈表 */
struct list_head s_dirty; /* dirty inodes */
struct list_head s_io; /* parked for writeback */
struct list_head s_more_io; /* parked for more writeback */
struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */
/* file結構的鏈表,該超級塊上所有打開的文件 */
struct list_head s_files;
/* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
/* 不再使用的dentry的LRU鏈表 */
struct list_head s_dentry_lru; /* unused dentry lru */
int s_nr_dentry_unused; /* # of dentry on lru */struct block_device *s_bdev;
struct mtd_info *s_mtd;
/* 相同文件系統類型的超級塊鏈表的節點 */
struct list_head s_instances;
struct quota_info s_dquot; /* Diskquota specific options */int s_frozen;
wait_queue_head_t s_wait_unfrozen;char s_id[32]; /* Informational name */void *s_fs_info; /* Filesystem private info */
fmode_t s_mode;/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge *//* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype;/*
* Saved mount options for lazy filesystems using
* generic_show_options()
*/
char *s_options;
};s_list:是一個list_head結構體對象。list_head結構體如下定義。內核中使用一個雙向環形鏈表將所有超級塊連起來管理,即每個超級塊的s_list屬性都包含了指向內核鏈表中前一個元素和后一個元素的指針。全局變量super_blocks用于指向鏈表中第一個元素。Linux內核經常用該對象間接定義雙向循環鏈表來管理數據。
struct{
list_head *prev;
list_head *next;
}- s_blocksize:文件系統中數據塊大小,單位是字
- s_dirt:臟位,在具體的硬件設備中有關于其文件系統的數據,將設備掛載以后,會用其關于文件系統的數據來初始化內存中的super_block結構體對象。而VFS是允許對超級塊對象進行修改的,修改后的數據最終是要寫回磁盤對應區域的。s_dirt用于判斷超級塊對象中數據是否臟了即被修改過了,即與磁盤上的超級塊區域是否一致。
- s_dirty:臟inode的雙向循環鏈表,用于同步內存數據和底層存儲介質。當我們在用戶去用open打開一個文件,內存中會創建dentry和inode,當我們用write往文件中寫入數據,則該inode臟了,將其加入到s_dirty鏈表
- s_files:該超級塊表是的文件系統中所有被打開的文件。
- s_type:是指向file_system_type類型的指針,file_system_type結構體用于保存具體的文件系統的信息。
- s_op:super_operations結構體類型的指針,因為一個超級塊對應一種文件系統,而每種文件系統的操作函數可能是不同的。super_operations結構體由一些函數指針組成,這些函數指針用特定文件系統的超級塊區域操作函數來初始化。比如里邊會有函數實現獲取和返回底層文件系統inode的方法。
- s_inodes:是一個list_head結構體對象,指向超級塊對應文件系統中的所有inode索引節點的鏈表。
(2)索引節點對象inode
索引節點則是文件或目錄的 “信息卡片”,每個文件或目錄在文件系統中都對應一個唯一的 inode 。inode 中存儲了文件的元數據,如文件的大小、權限、所有者、創建時間、修改時間、訪問時間、文件數據塊的位置信息等。inode 不包含文件的名字,文件名是通過目錄項來管理的。當我們對文件進行操作時,VFS 首先會通過文件名找到對應的 inode,然后根據 inode 中的信息來執行具體的操作,比如讀取文件數據時,就需要根據 inode 中記錄的數據塊位置信息,從磁盤上讀取相應的數據塊。
struct inode {
/* 全局的散列表 */
struct hlist_node i_hash;
/* 根據inode的狀態可能處理不同的鏈表中(inode_unused/inode_in_use/super_block->dirty) */
struct list_head i_list;
/* super_block->s_inodes鏈表的節點 */
struct list_head i_sb_list;
/* inode對應的dentry鏈表,可能多個dentry指向同一個文件 */
struct list_head i_dentry;
/* inode編號 */
unsigned long i_ino;
/* 訪問該inode的進程數目 */
atomic_t i_count;
/* inode的硬鏈接數 */
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
/* inode表示設備文件時的設備號 */
dev_t i_rdev;
u64 i_version;
/* 文件的大小,以字節為單位 */
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* 最后訪問時間 */
struct timespec i_atime;
/* 最后修改inode數據的時間 */
struct timespec i_mtime;
/* 最后修改inode自身的時間 */
struct timespec i_ctime;
/* 以block為單位的inode的大小 */
blkcnt_t i_blocks;
unsigned int i_blkbits;
unsigned short i_bytes;
/* 文件屬性,低12位為文件訪問權限,同chmod參數含義,其余位為文件類型,如普通文件、目錄、socket、設備文件等 */
umode_t i_mode;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
/* inode操作 */
const struct inode_operations *i_op;
/* file操作 */
const struct file_operations *i_fop;
/* inode所屬的super_block */
struct super_block *i_sb;
struct file_lock *i_flock;
/* inode的地址空間映射 */
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices; /* 若為設備文件的inode,則為設備的打開文件列表節點 */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev; /* 若為塊設備的inode,則指向該設備實例 */
struct cdev *i_cdev; /* 若為字符設備的inode,則指向該設備實例 */
};__u32 i_generation;#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_mark_entries; /* fsnotify mark entries */
#endif#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode */
struct mutex inotify_mutex; /* protects the watches list */
#endifunsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */unsigned int i_flags; /* 文件打開標記,如noatime */atomic_t i_writecount;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
void *i_private; /* fs or device private pointer */
};以下是通用的inode對象結構體定義:
- i_no:便是inode的唯一性編號
- i_count:訪問該inode結構體對象的進程數
- i_nlink:硬鏈接計數,等于0時將文件從磁盤移除。
- i_hash:指向哈希鏈表指針,用于查詢,已經inode號碼和對應超級塊的時候,通過哈希表來快速查詢地址。具體看下邊管理inode節點。也是list_head類型對象,這種對象就對應了一個雙向循環鏈表。
- i_dentry:指向目錄項鏈表指針,因為一個inode可以對象多個dentry,因此用一個鏈表將于本inode關聯的目錄項都連在一起。
- i_op:索引節點操作函數指針,指向了inode_operation結構體,提供與inode相關的操作
- i_fop:指向file_operations結構提供文件操作,在file結構體中也有指向file_operations結構的指針。
- i_sb:inode所屬文件系統的超級塊指針
管理inode節點的四個鏈表(前兩個是全局鏈表,第三個在超級塊中):
- inode_unused:目前未被使用的inode節點鏈表,即尚在內存中沒有銷毀,但是沒有進程使用,i_count為0。
- inode_in_use:當前正在使用的inode鏈表,i_count > 0且 i_nlink > 0
- super_block中的s_dirty:將所有修改過的inode鏈接起來
- inode_hashtable:為了加快查找效率,將正在使用的和臟的inode放入一個哈希表中,但是不同的inode的哈希值可能相等,hash值相等的inode哦那個過i_hash成員連接。
注意是所有位于內存中的inode會存放在一個名為inode_hashtable的全局哈希表中,如果inode還在磁盤,沒有緩存到內存,則不會加入全局哈希表。inode_hashtable加快了對索引節點對象的搜索,但前提是要知道inode號碼和對應的超級塊對象。在inode_hashtable哈希表中的元素是鏈表,是通過inode對象中的i_hash成員鏈接起來的雙向循環鏈表,在這個子鏈表中對應的inode的哈希值是相等的。即inode_hashtable本質是一個數據和鏈表的結合體。
(3)目錄項對象
目錄項是文件系統層次結構的 “導航員”,它用于表示文件系統中的目錄和文件,是連接文件名和inode的橋梁 。在文件系統的路徑中,每一部分都對應一個目錄項,例如/home/user/data.txt這個路徑中,/、home、user和data.txt分別是一個目錄項。目錄項中包含了文件名、指向inode 的指針以及一些其他的元數據。通過目錄項,VFS 可以快速地從文件系統的根目錄開始,沿著目錄層次結構找到目標文件或目錄的 inode,從而實現對文件的訪問。同時,目錄項還會被緩存起來,形成目錄項高速緩存(dentry cache),這樣在下次訪問相同的文件或目錄時,可以大大提高查找速度。
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
/* 該dentry是否是一個裝載點 */
int d_mounted;
/* 文件所屬的inode */
struct inode *d_inode;
/*
* The next three fields are touched by __d_lookup. Place them here so they all fit in a cache line.
*/
/* 全局的dentry散列表 */
struct hlist_node d_hash; /* lookup hash list */
/* 父目錄的dentry */
struct dentry *d_parent; /* parent directory */
/* 文件的名稱,例如對/tmp/a.sh,文件名即為a.sh */
struct qstr d_name;
/* 臟的dentry鏈表的節點 */
struct list_head d_lru; /* LRU list */
/*
* 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;
/* 該dentry子目錄中的dentry的節點鏈表 */
struct list_head d_subdirs; /* our children */
/* 硬鏈接使用幾個不同名稱表示同一個文件時,用于連接各個dentry */
struct list_head d_alias; /* inode alias list */
unsigned long d_time; /* used by d_revalidate */
const struct dentry_operations *d_op;
/* 所屬的super_block */
struct super_block *d_sb; /* The root of the dentry tree */
void *d_fsdata; /* fs-specific data */
/* 如果文件名由少量字符組成,在保存在這里,加速訪問 */
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};- d_count:目錄項對象引用計數器
- d_name:文件名
- d_inode:inode節點的指針,便于快速找到對應的索引節點
- d_sb:指向對應超級塊的指針
- d_op:指向dentry對應的操作函數集
- d_subdirs & d_child:某目錄的d_subdirs與該目錄下所有文件的d_child成員一起形成一個雙向循環鏈表,將該目錄下的所有文件連接在一起,目的是保留文件的目錄結構,即一個d_subdirs和多個d_child一起形成鏈表,d_subdirs對應文件在d_child對應文件的上一層目錄。具體可見下圖。
- d_parent:指向父目錄的dentry對象。
通過d_subdirs和d_child把同一目錄下的文件都連接了起來形成鏈表,然后通過d_parent成員可以確定該鏈表對應的文件所在的目錄,這三個成員一起就能完全保留文件之間的目錄層次關系。比如當移動文件的時候,只需要將dentry對象從舊得父dentry鏈表(d_subdirs)上脫離,鏈接到新的父dentry的d_subdirs鏈表上,并將d_parent成員指向新的父dentry即可??梢钥吹揭苿游募]有移動底層文件,甚至沒有改變inode,只是改變了緩存中的dentry(最終改變目錄文件),因此在同一個文件系統中移動文件會很快,但是跨文件系統就會改變inode和底層數據區了,因此速度很慢。
(4)文件對象
文件對象是表示打開文件的 “活動記錄”,當一個文件被打開時,VFS 會創建一個對應的文件對象 。文件對象中包含了指向 inode 的指針、當前文件的讀寫位置、文件操作的狀態標志以及指向文件操作函數集的指針等信息。文件對象主要用于在文件打開期間,記錄和管理文件的操作狀態,例如當我們對一個打開的文件進行讀寫操作時,文件對象會記錄當前的讀寫位置,以便下次繼續進行讀寫操作。文件對象還提供了對文件進行各種操作的接口,這些接口最終會調用 inode 中定義的文件操作函數集來完成實際的文件操作。
<pre style="overflow-wrap: break-word; margin: 0px; padding: 0px; white-space: normal; background: rgb(84, 82, 82); color: rgb(238, 238, 238); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">struct file { union { struct list_head fu_list; //文件對象鏈表指針linux/include/linux/list.h struct rcu_head fu_rcuhead; //RCU(Read-Copy Update)是Linux 2.6內核中新的鎖機制 } f_u; struct path f_path; //包含dentry和mnt兩個成員,用于確定文件路徑 #define f_dentry f_path.dentry //f_path的成員之一,當前文件的dentry結構 #define f_vfsmnt f_path.mnt //表示當前文件所在文件系統的掛載根目錄 const struct file_operations *f_op; //與該文件相關聯的操作函數 atomic_t f_count; //文件的引用計數(有多少進程打開該文件) unsigned int f_flags; //對應于open時指定的flag mode_t f_mode; //讀寫模式:open的mod_t mode參數 off_t f_pos; //該文件在當前進程中的文件偏移量 struct fown_struct f_owner; //該結構的作用是通過信號進行I/O時間通知的數據。 unsigned int f_uid, f_gid; //文件所有者id,所有者組id struct file_ra_state f_ra; //在linux/include/linux/fs.h中定義,文件預讀相關 unsigned long f_version; //記錄文件的版本號,每次使用后都自動遞增。 #ifdef CONFIG_SECURITY void *f_security; //用來描述安全措施或者是記錄與安全有關的信息。 #endif /* needed for tty driver, and maybe others */ void *private_data; //可以用字段指向已分配的數據 #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; 文件的事件輪詢等待者鏈表的頭, spinlock_t f_ep_lock; f_ep_lock是保護f_ep_links鏈表的自旋鎖。 #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; 文件地址空間的指針 };</pre>
<pre style="overflow-wrap: break-word; margin: 0px; padding: 0px; white-space: normal; background: rgb(84, 82, 82); color: rgb(238, 238, 238); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">struct files_struct { atomic_t count; /* 共享該表的進程數 */ rwlock_t file_lock; /* 保護該結構體的鎖*/ int max_fds; /*當前文件對象的最大數*/ int max_fdset; /*當前文件描述符的最大數*/ int next_fd; /*已分配的文件描述符加1*/ struct file ** fd; /* 指向文件對象指針數組的指針 */ fd_set *close_on_exec; /*指向執行exec()時需要關閉的文件描述符*/ fd_set *open_fds; /*指向打開文件描述符的指針*/ fd_set close_on_exec_init; /* 執行exec()時關閉的初始文件*/ fd_set open_fds_init; /*文件描述符的初值集合*/ struct file * fd_array[32]; /* 文件對象指針的初始化數組*/ };</pre>三、VFS的工作原理
VFS 之所以能夠銜接各種各樣的文件系統,是因為它抽象了一個通用的文件系統模型,定義了通用文件系統都支持的、概念上的接口。新的文件系統只要支持并實現這些接口,并注冊到 Linux 內核中,即可安裝和使用。
舉個例子,比如 Linux 寫一個文件:
int ret = write(fd, buf, len);調用了write()系統調用,它的過程簡要如下:
- 首先,勾起 VFS 通用系統調用sys_write()處理。
- 接著,sys_write()根據fd找到所在的文件系統提供的寫操作函數,比如op_write()。
- 最后,調用op_write(
圖片
3.1文件操作流程示例
為了更深入地理解 VFS 的工作原理,我們以打開文件這一常見操作為例,詳細剖析其背后的具體流程。當我們在應用程序中調用open函數打開一個文件時,看似簡單的一個操作,實際上在 Linux 內核中涉及到多個步驟和 VFS 關鍵數據結構的協同工作 。
應用程序發起調用:應用程序中執行open函數,例如int fd = open("/home/user/data.txt", O_RDONLY);,這里傳遞了文件的路徑/home/user/data.txt以及打開模式O_RDONLY(表示只讀打開)。此時,應用程序通過系統調用接口陷入內核態,將控制權交給內核中的 VFS 模塊來處理這個請求。
路徑解析與目錄項查找:VFS 首先會對傳入的文件路徑進行解析。它從根目錄/開始,逐步解析路徑中的每一個部分。在這個過程中,VFS 會利用目錄項高速緩存(dentry cache)來加速查找過程。目錄項高速緩存中存儲了最近訪問過的目錄項信息,當 VFS 解析路徑時,會首先在緩存中查找對應的目錄項。
如果在緩存中找到,就可以直接獲取該目錄項的相關信息,避免了對磁盤的直接訪問,大大提高了查找效率;如果緩存中沒有命中,VFS 就需要從磁盤上讀取目錄項信息,并將其添加到緩存中,以便下次訪問時能夠快速命中 。對于/home/user/data.txt這個路徑,VFS 會依次查找根目錄/的目錄項、home目錄的目錄項、user目錄的目錄項,最后找到data.txt文件的目錄項。每個目錄項都包含了文件名以及指向對應索引節點(inode)的指針。
索引節點獲取與文件對象創建:通過找到的文件目錄項,VFS 可以獲取到對應的索引節點。索引節點中存儲了文件的元數據,如文件的大小、權限、所有者、創建時間、修改時間、數據塊位置信息等。如果索引節點不在內存中,VFS 會從磁盤上讀取該索引節點并將其加載到內存中 。接下來,VFS 會為打開的文件創建一個文件對象。
文件對象中包含了指向索引節點的指針、當前文件的讀寫位置、文件操作的狀態標志以及指向文件操作函數集的指針等信息。文件對象的創建標志著文件已經被成功打開,應用程序可以通過返回的文件描述符(在上述例子中,fd就是返回的文件描述符)來對文件進行后續的操作,如讀取文件內容、寫入文件內容等 。
3.2與底層文件系統的交互
VFS 作為一個抽象層,與底層各種具體的文件系統之間有著緊密的交互關系。當 VFS 接收到應用程序的文件操作請求后,它需要根據文件路徑確定對應的底層文件系統,并將操作請求傳遞給底層文件系統的具體實現 。
確定底層文件系統:VFS 在掛載文件系統時,會為每個掛載的文件系統創建一個對應的超級塊(superblock),超級塊中包含了文件系統的類型、掛載點等重要信息。當 VFS 接收到文件操作請求時,它會根據文件路徑中的掛載點信息,找到對應的超級塊,從而確定該文件屬于哪個底層文件系統 。例如,對于/home/user/data.txt這個文件路徑,如果/home掛載的是 ext4 文件系統,VFS 就會根據/home對應的超級塊信息,確定該文件由 ext4 文件系統來處理。
操作請求傳遞:一旦確定了底層文件系統,VFS 就會將文件操作請求傳遞給該文件系統的具體實現。每個文件系統都實現了一組與文件操作相關的函數,這些函數被封裝在一個函數指針表中,稱為文件操作函數集(file_operations) 。例如,對于打開文件操作,ext4 文件系統會實現自己的open函數,VFS 會調用 ext4 文件系統的open函數來完成實際的文件打開操作。在傳遞操作請求時,VFS 會將文件對象、索引節點以及其他相關參數傳遞給底層文件系統的函數,底層文件系統根據這些參數來執行具體的操作 。底層文件系統在完成操作后,會將結果返回給 VFS,VFS 再將結果返回給應用程序。
四、VFS與其他內核組件的關系
4.1與進程管理的關聯
在 Linux 系統中,進程是資源分配和調度的基本單位,而文件操作是進程運行過程中常見的行為之一。進程與 VFS 之間存在著緊密的聯系,這種聯系主要體現在進程對文件的各種操作上,比如打開文件、讀取文件、寫入文件、關閉文件等 。當進程進行這些文件操作時,它并不是直接與底層的文件系統打交道,而是通過 VFS 提供的統一接口來完成。這就好比進程是一個顧客,它只需要告訴 VFS 這個 “服務員” 自己想要進行什么樣的文件操作,VFS 就會去協調底層的文件系統來滿足進程的需求 。
以進程打開文件為例,當一個進程調用open函數打開一個文件時,VFS 會在后臺進行一系列復雜的操作。首先,VFS 會根據進程提供的文件路徑,在目錄項高速緩存(dentry cache)中查找對應的目錄項。如果目錄項不在緩存中,VFS 就會從磁盤上讀取目錄項信息,并將其加入到緩存中。通過目錄項,VFS 可以找到對應的索引節點(inode) 。索引節點中包含了文件的各種元數據,如文件的大小、權限、所有者、創建時間、修改時間、數據塊位置信息等。如果索引節點不在內存中,VFS 會從磁盤上讀取該索引節點并將其加載到內存中 。
接下來,VFS 會為打開的文件創建一個文件對象。文件對象中包含了指向索引節點的指針、當前文件的讀寫位置、文件操作的狀態標志以及指向文件操作函數集的指針等信息 。這個文件對象就像是進程與文件之間的 “橋梁”,進程通過文件對象來對文件進行后續的操作。同時,進程的task_struct結構體中包含一個files_struct結構體,files_struct結構體中維護著一個文件描述符表,文件描述符表中的每一項都指向一個打開的文件對象 。通過這種方式,進程可以方便地管理自己打開的文件。
在這個過程中,我們可以看到 VFS 起到了關鍵的作用。它不僅為進程提供了統一的文件操作接口,使得進程無需關心底層文件系統的具體實現細節,還負責管理文件系統相關的數據結構,如目錄項、索引節點、文件對象等,確保文件操作的順利進行 。同時,VFS 與進程管理之間的緊密協作也體現了 Linux 內核設計的精妙之處,各個組件之間相互配合,共同為系統的高效運行提供保障 。
4.2在 Linux 內核架構中的位置
為了更直觀地理解 VFS 在 Linux 內核中的地位,我們來看一下 Linux 內核架構圖:
圖片
從圖中可以清晰地看到,VFS 處于用戶空間和各種具體文件系統之間,是連接兩者的關鍵橋梁 。用戶空間的應用程序通過系統調用接口(System Call Interface)與內核進行交互,而 VFS 則負責處理這些系統調用,并將其轉發到底層的具體文件系統 。
在 Linux 系統中,“一切皆文件” 的理念深入人心,而 VFS 正是這一理念的重要體現。它將不同類型的文件系統,如基于磁盤的文件系統(如ext4、XFS 等)、網絡文件系統(如 NFS、CIFS 等)、虛擬文件系統(如 /proc、/sys 等),都統一抽象成可以通過文件操作接口(如open、close、read、write 等)訪問的對象 。這使得應用程序可以使用統一的方式來操作不同類型的文件系統,大大提高了系統的通用性和可擴展性 。
例如,當應用程序調用read函數讀取文件內容時,這個請求首先會通過系統調用接口進入內核空間,然后由 VFS 接收 。VFS 根據文件的路徑信息,確定該文件所屬的具體文件系統,并將read請求轉發給對應的文件系統驅動程序 。文件系統驅動程序根據 VFS 傳遞的參數,在磁盤或其他存儲設備上讀取相應的數據,并將數據返回給 VFS 。VFS 再將數據返回給應用程序,完成整個文件讀取操作 。
VFS 在 Linux 內核架構中的核心位置,使其成為了文件系統管理的關鍵組件。它不僅為用戶空間提供了統一的文件訪問接口,還協調了不同文件系統之間的工作,確保了 Linux 系統在文件管理方面的高效性和靈活性 。





























