解鎖Linux內(nèi)核黑科技:VFS虛擬文件系統(tǒng)詳解
在Linux內(nèi)核的廣袤世界里,隱藏著許多精妙的設計,其中虛擬文件系統(tǒng)(VFS)堪稱一顆璀璨的明珠。你是否好奇,為何在 Linux 系統(tǒng)中,無論是常見的本地磁盤文件系統(tǒng),還是復雜的網(wǎng)絡文件系統(tǒng),我們都能用統(tǒng)一的方式去操作文件和目錄?又是什么神奇的機制,讓 Linux 能輕松支持種類繁多的文件系統(tǒng),從古老的 ext2 到如今的 ext4,從分布式的 Ceph 到網(wǎng)絡共享的 NFS?
答案就在 VFS 之中。它就像一個強大的幕后協(xié)調(diào)者,在用戶與各種千差萬別的文件系統(tǒng)之間,搭建起一座溝通的橋梁,讓一切變得簡單而有序。今天,就讓我們一同揭開 VFS 的神秘面紗,深入探索其內(nèi)部的運作機制,領略 Linux 內(nèi)核設計的智慧魅力 。
一、VFS是什么?
在日常生活中,我們可能會遇到這樣的情況:家里有不同的房間,每個房間都有對應的鑰匙。如果沒有統(tǒng)一的鑰匙管理系統(tǒng),我們每次找鑰匙就會很麻煩,可能要在一堆鑰匙中翻找半天。但如果有一個類似于 “萬能鑰匙接口” 的東西,它可以適配各種鎖芯,只要把不同的鑰匙適配到這個接口上,我們就能用一種統(tǒng)一的方式去開鎖,這會大大提高我們的效率。
在 Linux 內(nèi)核的世界里,VFS(Virtual File System)就扮演著這樣一個 “萬能鑰匙接口” 的角色 。VFS 是 Linux 內(nèi)核中一個非常關鍵的抽象層,它的核心使命是為所有類型的文件系統(tǒng)提供一個統(tǒng)一的訪問接口。簡單來說,無論底層是 ext4、XFS 這樣常見的本地磁盤文件系統(tǒng),還是像 NFS(Network File System)這樣的網(wǎng)絡文件系統(tǒng),又或是像 procfs、sysfs 這類特殊用途的文件系統(tǒng),VFS 都能讓它們在 Linux 系統(tǒng)中和諧共處,并為用戶和應用程序提供一致的操作方式。
VFS 就像是一個智能的翻譯官,它把用戶和應用程序發(fā)出的各種文件操作請求,比如打開文件、讀取文件、寫入文件等,準確無誤地翻譯成底層不同文件系統(tǒng)能夠理解的指令。同時,它又把底層文件系統(tǒng)返回的結果,以統(tǒng)一的格式呈現(xiàn)給用戶和應用程序。這樣一來,用戶和應用程序就完全不用關心底層文件系統(tǒng)的具體實現(xiàn)細節(jié),只需要和 VFS 進行交互就可以了,極大地簡化了文件操作的復雜性。
另外簡單提一下兩個數(shù)據(jù)結構:
- 每種注冊到內(nèi)核的文件系統(tǒng)類型以struct file_system_type結構表示,每種文件系統(tǒng)類型中都有一個鏈表,指向所有屬于該類型的文件系統(tǒng)的超級塊。
- 當一個文件系統(tǒng)掛載到內(nèi)核文件系統(tǒng)的目錄樹上,會生成一個掛載點,用來管理所掛載的文件系統(tǒng)的信息。該掛載點用一個struct vfsmount結構表示。
1.1文件系統(tǒng)象層
能夠使用通用接口對所有類型的文件系統(tǒng)進行操作,是因為內(nèi)核在它的底層文件系統(tǒng)接口上建立了一個抽象層。 VFS抽象層之所以能夠銜接各種各樣的文件系統(tǒng),是因為它定義了所有文件系統(tǒng)都支持的、基本的、概念上的接口和數(shù)據(jù)結構。
1.2Unix文件系統(tǒng)
Unix使用四種和文件系統(tǒng)相關的傳統(tǒng)抽象概念:文件、目錄項、索引節(jié)點和安裝節(jié)點 。 linux系統(tǒng)中所有都看作是文件,文件通過目錄組織起來。在Unix系統(tǒng)當中,目錄屬于普通文件,它列出包含在其中的所有文件。 Unix系統(tǒng)當中將文件的相關信息和文件本身這兩個概念加以區(qū)分,例如訪問控制權限等。
文件的相關信息,有時也被稱為文件的元數(shù)據(jù),被存儲在單獨的數(shù)據(jù)結構當中,該結構被稱之為索引節(jié)點(inode)。 對于其他的一些文件系統(tǒng),比如FAT NTFS,他們沒有索引節(jié)點的概念,但是如果需要在Unix系統(tǒng)中工作的話,還是需要進行封裝,封裝成適合Unix系統(tǒng)的格式。
二、VFS的關鍵特性
2.1統(tǒng)一接口
VFS 提供的統(tǒng)一接口,就像是一個標準化的工具盒,里面裝著各種標準工具,無論面對什么樣的文件系統(tǒng) “工作”,都能使用這些標準工具來完成 。在 Linux 系統(tǒng)中,常見的文件系統(tǒng)操作,如創(chuàng)建文件、讀取文件、寫入文件、刪除文件、打開文件、關閉文件、重命名文件、獲取文件屬性等,VFS 都為它們提供了統(tǒng)一的系統(tǒng)調(diào)用接口。例如,當我們使用open函數(shù)來打開一個文件時,無論這個文件是存儲在本地的 ext4 文件系統(tǒng)上,還是位于遠程的 NFS 文件系統(tǒng)中,我們調(diào)用open函數(shù)的方式和參數(shù)都是一樣的。
同樣,read函數(shù)用于讀取文件內(nèi)容,write函數(shù)用于寫入文件內(nèi)容,close函數(shù)用于關閉文件,這些函數(shù)的使用方式不會因為底層文件系統(tǒng)的不同而改變。對于應用程序開發(fā)者來說,他們只需要熟悉這些統(tǒng)一的接口,就可以輕松地編寫與文件系統(tǒng)交互的代碼,而無需花費大量時間去了解不同文件系統(tǒng)的復雜細節(jié)。
2.2多文件系統(tǒng)支持
Linux 系統(tǒng)的一大優(yōu)勢就是能夠同時掛載多種不同類型的文件系統(tǒng),這都得益于 VFS 強大的多文件系統(tǒng)支持能力。在一臺 Linux 服務器上,我們可能會同時掛載 ext4 文件系統(tǒng)用于存儲系統(tǒng)文件和用戶數(shù)據(jù),掛載 NFS 文件系統(tǒng)用于訪問遠程網(wǎng)絡存儲設備上的文件,掛載 procfs 文件系統(tǒng)用于獲取系統(tǒng)進程信息,掛載 sysfs 文件系統(tǒng)用于訪問內(nèi)核對象和設備信息等。
VFS 就像是一個高效的交通樞紐管理員,負責協(xié)調(diào)管理這些不同文件系統(tǒng)的工作 。當用戶發(fā)起一個文件操作請求時,VFS 會根據(jù)文件的路徑信息,準確地判斷出該請求應該由哪個文件系統(tǒng)來處理,然后將請求轉(zhuǎn)發(fā)給相應的文件系統(tǒng)驅(qū)動程序。例如,當用戶訪問/home/user/data.txt這個文件時,VFS 會根據(jù)/home所在的文件系統(tǒng)信息,確定該文件位于 ext4 文件系統(tǒng)上,然后調(diào)用 ext4 文件系統(tǒng)的驅(qū)動程序來完成文件的訪問操作;如果用戶訪問/mnt/nfs_share/file.txt,VFS 則會判斷出這是一個 NFS 文件系統(tǒng)上的文件,進而將請求轉(zhuǎn)發(fā)給 NFS 文件系統(tǒng)驅(qū)動程序。這種多文件系統(tǒng)支持的特性,使得 Linux 系統(tǒng)能夠適應各種復雜的應用場景,滿足不同用戶的多樣化需求。
2.3抽象對象模型
VFS 通過一套抽象對象模型來管理文件系統(tǒng),這套模型主要包括超級塊(superblock)、索引節(jié)點(inode)、目錄項(dentry)和文件對象(file),它們相互協(xié)作,構成了 VFS 管理文件系統(tǒng)的堅實基礎。
(1)超級塊對象super block
超級塊就像是文件系統(tǒng)的 “總管家”,它存儲了整個文件系統(tǒng)的關鍵控制信息 。每個文件系統(tǒng)都有一個對應的超級塊,其中包含了文件系統(tǒng)的類型、塊大小、inode 表信息、空閑塊列表、文件系統(tǒng)的狀態(tài)等重要內(nèi)容。超級塊在文件系統(tǒng)掛載時被讀取到內(nèi)存中,并且在文件系統(tǒng)的整個生命周期內(nèi)都發(fā)揮著重要作用。它為文件系統(tǒng)的管理和操作提供了全局的視角,比如通過超級塊可以快速了解文件系統(tǒng)的基本屬性,以及獲取文件系統(tǒng)中其他重要數(shù)據(jù)結構的位置信息。
struct super_block {
/* 全局鏈表元素 */
struct list_head s_list;
/* 底層文件系統(tǒng)所在的設備 */
dev_t s_dev;
/* 文件系統(tǒng)中每一塊的長度 */
unsigned long s_blocksize;
/* 文件系統(tǒng)中每一塊的長度(以2為底的對數(shù)) */
unsigned char s_blocksize_bits;
/* 是否需要向磁盤回寫 */
unsigned char s_dirt;
unsigned long long s_maxbytes; /* Max file size */
/* 文件系統(tǒng)類型 */
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;
/* 相同文件系統(tǒng)類型的超級塊鏈表的節(jié)點 */
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結構體如下定義。內(nèi)核中使用一個雙向環(huán)形鏈表將所有超級塊連起來管理,即每個超級塊的s_list屬性都包含了指向內(nèi)核鏈表中前一個元素和后一個元素的指針。全局變量super_blocks用于指向鏈表中第一個元素。Linux內(nèi)核經(jīng)常用該對象間接定義雙向循環(huán)鏈表來管理數(shù)據(jù)。
struct{
list_head *prev;
list_head *next;
}
- s_blocksize:文件系統(tǒng)中數(shù)據(jù)塊大小,單位是字
- s_dirt:臟位,在具體的硬件設備中有關于其文件系統(tǒng)的數(shù)據(jù),將設備掛載以后,會用其關于文件系統(tǒng)的數(shù)據(jù)來初始化內(nèi)存中的super_block結構體對象。而VFS是允許對超級塊對象進行修改的,修改后的數(shù)據(jù)最終是要寫回磁盤對應區(qū)域的。s_dirt用于判斷超級塊對象中數(shù)據(jù)是否臟了即被修改過了,即與磁盤上的超級塊區(qū)域是否一致。
- s_dirty:臟inode的雙向循環(huán)鏈表,用于同步內(nèi)存數(shù)據(jù)和底層存儲介質(zhì)。當我們在用戶去用open打開一個文件,內(nèi)存中會創(chuàng)建dentry和inode,當我們用write往文件中寫入數(shù)據(jù),則該inode臟了,將其加入到s_dirty鏈表
- s_files:該超級塊表是的文件系統(tǒng)中所有被打開的文件。
- s_type:是指向file_system_type類型的指針,file_system_type結構體用于保存具體的文件系統(tǒng)的信息。
- s_op:super_operations結構體類型的指針,因為一個超級塊對應一種文件系統(tǒng),而每種文件系統(tǒng)的操作函數(shù)可能是不同的。super_operations結構體由一些函數(shù)指針組成,這些函數(shù)指針用特定文件系統(tǒng)的超級塊區(qū)域操作函數(shù)來初始化。比如里邊會有函數(shù)實現(xiàn)獲取和返回底層文件系統(tǒng)inode的方法。
- s_inodes:是一個list_head結構體對象,指向超級塊對應文件系統(tǒng)中的所有inode索引節(jié)點的鏈表。
(2)索引節(jié)點對象inode
索引節(jié)點則是文件或目錄的 “信息卡片”,每個文件或目錄在文件系統(tǒng)中都對應一個唯一的 inode 。inode 中存儲了文件的元數(shù)據(jù),如文件的大小、權限、所有者、創(chuàng)建時間、修改時間、訪問時間、文件數(shù)據(jù)塊的位置信息等。inode 不包含文件的名字,文件名是通過目錄項來管理的。當我們對文件進行操作時,VFS 首先會通過文件名找到對應的 inode,然后根據(jù) inode 中的信息來執(zhí)行具體的操作,比如讀取文件數(shù)據(jù)時,就需要根據(jù) inode 中記錄的數(shù)據(jù)塊位置信息,從磁盤上讀取相應的數(shù)據(jù)塊。
struct inode {
/* 全局的散列表 */
struct hlist_node i_hash;
/* 根據(jù)inode的狀態(tài)可能處理不同的鏈表中(inode_unused/inode_in_use/super_block->dirty) */
struct list_head i_list;
/* super_block->s_inodes鏈表的節(jié)點 */
struct list_head i_sb_list;
/* inode對應的dentry鏈表,可能多個dentry指向同一個文件 */
struct list_head i_dentry;
/* inode編號 */
unsigned long i_ino;
/* 訪問該inode的進程數(shù)目 */
atomic_t i_count;
/* inode的硬鏈接數(shù) */
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
/* inode表示設備文件時的設備號 */
dev_t i_rdev;
u64 i_version;
/* 文件的大小,以字節(jié)為單位 */
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* 最后訪問時間 */
struct timespec i_atime;
/* 最后修改inode數(shù)據(jù)的時間 */
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參數(shù)含義,其余位為文件類型,如普通文件、目錄、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,則為設備的打開文件列表節(jié)點 */
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結構體對象的進程數(shù)
- i_nlink:硬鏈接計數(shù),等于0時將文件從磁盤移除。
- i_hash:指向哈希鏈表指針,用于查詢,已經(jīng)inode號碼和對應超級塊的時候,通過哈希表來快速查詢地址。具體看下邊管理inode節(jié)點。也是list_head類型對象,這種對象就對應了一個雙向循環(huán)鏈表。
- i_dentry:指向目錄項鏈表指針,因為一個inode可以對象多個dentry,因此用一個鏈表將于本inode關聯(lián)的目錄項都連在一起。
- i_op:索引節(jié)點操作函數(shù)指針,指向了inode_operation結構體,提供與inode相關的操作
- i_fop:指向file_operations結構提供文件操作,在file結構體中也有指向file_operations結構的指針。
- i_sb:inode所屬文件系統(tǒng)的超級塊指針
管理inode節(jié)點的四個鏈表(前兩個是全局鏈表,第三個在超級塊中):
- inode_unused:目前未被使用的inode節(jié)點鏈表,即尚在內(nèi)存中沒有銷毀,但是沒有進程使用,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成員連接。
注意是所有位于內(nèi)存中的inode會存放在一個名為inode_hashtable的全局哈希表中,如果inode還在磁盤,沒有緩存到內(nèi)存,則不會加入全局哈希表。inode_hashtable加快了對索引節(jié)點對象的搜索,但前提是要知道inode號碼和對應的超級塊對象。在inode_hashtable哈希表中的元素是鏈表,是通過inode對象中的i_hash成員鏈接起來的雙向循環(huán)鏈表,在這個子鏈表中對應的inode的哈希值是相等的。即inode_hashtable本質(zhì)是一個數(shù)據(jù)和鏈表的結合體。
(3)目錄項對象
目錄項是文件系統(tǒng)層次結構的 “導航員”,它用于表示文件系統(tǒng)中的目錄和文件,是連接文件名和inode的橋梁 。在文件系統(tǒng)的路徑中,每一部分都對應一個目錄項,例如/home/user/data.txt這個路徑中,/、home、user和data.txt分別是一個目錄項。目錄項中包含了文件名、指向inode 的指針以及一些其他的元數(shù)據(jù)。通過目錄項,VFS 可以快速地從文件系統(tǒng)的根目錄開始,沿著目錄層次結構找到目標文件或目錄的 inode,從而實現(xiàn)對文件的訪問。同時,目錄項還會被緩存起來,形成目錄項高速緩存(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鏈表的節(jié)點 */
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的節(jié)點鏈表 */
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:目錄項對象引用計數(shù)器
- d_name:文件名
- d_inode:inode節(jié)點的指針,便于快速找到對應的索引節(jié)點
- d_sb:指向?qū)墘K的指針
- d_op:指向dentry對應的操作函數(shù)集
- d_subdirs & d_child:某目錄的d_subdirs與該目錄下所有文件的d_child成員一起形成一個雙向循環(huán)鏈表,將該目錄下的所有文件連接在一起,目的是保留文件的目錄結構,即一個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(最終改變目錄文件),因此在同一個文件系統(tǒng)中移動文件會很快,但是跨文件系統(tǒng)就會改變inode和底層數(shù)據(jù)區(qū)了,因此速度很慢。
(4)文件對象
文件對象是表示打開文件的 “活動記錄”,當一個文件被打開時,VFS 會創(chuàng)建一個對應的文件對象 。文件對象中包含了指向 inode 的指針、當前文件的讀寫位置、文件操作的狀態(tài)標志以及指向文件操作函數(shù)集的指針等信息。文件對象主要用于在文件打開期間,記錄和管理文件的操作狀態(tài),例如當我們對一個打開的文件進行讀寫操作時,文件對象會記錄當前的讀寫位置,以便下次繼續(xù)進行讀寫操作。文件對象還提供了對文件進行各種操作的接口,這些接口最終會調(diào)用 inode 中定義的文件操作函數(shù)集來完成實際的文件操作。
<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內(nèi)核中新的鎖機制 } f_u; struct path f_path; //包含dentry和mnt兩個成員,用于確定文件路徑 #define f_dentry f_path.dentry //f_path的成員之一,當前文件的dentry結構 #define f_vfsmnt f_path.mnt //表示當前文件所在文件系統(tǒng)的掛載根目錄 const struct file_operations *f_op; //與該文件相關聯(lián)的操作函數(shù) atomic_t f_count; //文件的引用計數(shù)(有多少進程打開該文件) unsigned int f_flags; //對應于open時指定的flag mode_t f_mode; //讀寫模式:open的mod_t mode參數(shù) off_t f_pos; //該文件在當前進程中的文件偏移量 struct fown_struct f_owner; //該結構的作用是通過信號進行I/O時間通知的數(shù)據(jù)。 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; //可以用字段指向已分配的數(shù)據(jù) #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; /* 共享該表的進程數(shù) */ rwlock_t file_lock; /* 保護該結構體的鎖*/ int max_fds; /*當前文件對象的最大數(shù)*/ int max_fdset; /*當前文件描述符的最大數(shù)*/ int next_fd; /*已分配的文件描述符加1*/ struct file ** fd; /* 指向文件對象指針數(shù)組的指針 */ fd_set *close_on_exec; /*指向執(zhí)行exec()時需要關閉的文件描述符*/ fd_set *open_fds; /*指向打開文件描述符的指針*/ fd_set close_on_exec_init; /* 執(zhí)行exec()時關閉的初始文件*/ fd_set open_fds_init; /*文件描述符的初值集合*/ struct file * fd_array[32]; /* 文件對象指針的初始化數(shù)組*/ };</pre>
三、VFS的工作原理
VFS 之所以能夠銜接各種各樣的文件系統(tǒng),是因為它抽象了一個通用的文件系統(tǒng)模型,定義了通用文件系統(tǒng)都支持的、概念上的接口。新的文件系統(tǒng)只要支持并實現(xiàn)這些接口,并注冊到 Linux 內(nèi)核中,即可安裝和使用。
舉個例子,比如 Linux 寫一個文件:
int ret = write(fd, buf, len);
調(diào)用了write()系統(tǒng)調(diào)用,它的過程簡要如下:
- 首先,勾起 VFS 通用系統(tǒng)調(diào)用sys_write()處理。
- 接著,sys_write()根據(jù)fd找到所在的文件系統(tǒng)提供的寫操作函數(shù),比如op_write()。
- 最后,調(diào)用op_write(
圖片
3.1文件操作流程示例
為了更深入地理解 VFS 的工作原理,我們以打開文件這一常見操作為例,詳細剖析其背后的具體流程。當我們在應用程序中調(diào)用open函數(shù)打開一個文件時,看似簡單的一個操作,實際上在 Linux 內(nèi)核中涉及到多個步驟和 VFS 關鍵數(shù)據(jù)結構的協(xié)同工作 。
應用程序發(fā)起調(diào)用:應用程序中執(zhí)行open函數(shù),例如int fd = open("/home/user/data.txt", O_RDONLY);,這里傳遞了文件的路徑/home/user/data.txt以及打開模式O_RDONLY(表示只讀打開)。此時,應用程序通過系統(tǒng)調(diào)用接口陷入內(nèi)核態(tài),將控制權交給內(nèi)核中的 VFS 模塊來處理這個請求。
路徑解析與目錄項查找:VFS 首先會對傳入的文件路徑進行解析。它從根目錄/開始,逐步解析路徑中的每一個部分。在這個過程中,VFS 會利用目錄項高速緩存(dentry cache)來加速查找過程。目錄項高速緩存中存儲了最近訪問過的目錄項信息,當 VFS 解析路徑時,會首先在緩存中查找對應的目錄項。
如果在緩存中找到,就可以直接獲取該目錄項的相關信息,避免了對磁盤的直接訪問,大大提高了查找效率;如果緩存中沒有命中,VFS 就需要從磁盤上讀取目錄項信息,并將其添加到緩存中,以便下次訪問時能夠快速命中 。對于/home/user/data.txt這個路徑,VFS 會依次查找根目錄/的目錄項、home目錄的目錄項、user目錄的目錄項,最后找到data.txt文件的目錄項。每個目錄項都包含了文件名以及指向?qū)饕?jié)點(inode)的指針。
索引節(jié)點獲取與文件對象創(chuàng)建:通過找到的文件目錄項,VFS 可以獲取到對應的索引節(jié)點。索引節(jié)點中存儲了文件的元數(shù)據(jù),如文件的大小、權限、所有者、創(chuàng)建時間、修改時間、數(shù)據(jù)塊位置信息等。如果索引節(jié)點不在內(nèi)存中,VFS 會從磁盤上讀取該索引節(jié)點并將其加載到內(nèi)存中 。接下來,VFS 會為打開的文件創(chuàng)建一個文件對象。
文件對象中包含了指向索引節(jié)點的指針、當前文件的讀寫位置、文件操作的狀態(tài)標志以及指向文件操作函數(shù)集的指針等信息。文件對象的創(chuàng)建標志著文件已經(jīng)被成功打開,應用程序可以通過返回的文件描述符(在上述例子中,fd就是返回的文件描述符)來對文件進行后續(xù)的操作,如讀取文件內(nèi)容、寫入文件內(nèi)容等 。
3.2與底層文件系統(tǒng)的交互
VFS 作為一個抽象層,與底層各種具體的文件系統(tǒng)之間有著緊密的交互關系。當 VFS 接收到應用程序的文件操作請求后,它需要根據(jù)文件路徑確定對應的底層文件系統(tǒng),并將操作請求傳遞給底層文件系統(tǒng)的具體實現(xiàn) 。
確定底層文件系統(tǒng):VFS 在掛載文件系統(tǒng)時,會為每個掛載的文件系統(tǒng)創(chuàng)建一個對應的超級塊(superblock),超級塊中包含了文件系統(tǒng)的類型、掛載點等重要信息。當 VFS 接收到文件操作請求時,它會根據(jù)文件路徑中的掛載點信息,找到對應的超級塊,從而確定該文件屬于哪個底層文件系統(tǒng) 。例如,對于/home/user/data.txt這個文件路徑,如果/home掛載的是 ext4 文件系統(tǒng),VFS 就會根據(jù)/home對應的超級塊信息,確定該文件由 ext4 文件系統(tǒng)來處理。
操作請求傳遞:一旦確定了底層文件系統(tǒng),VFS 就會將文件操作請求傳遞給該文件系統(tǒng)的具體實現(xiàn)。每個文件系統(tǒng)都實現(xiàn)了一組與文件操作相關的函數(shù),這些函數(shù)被封裝在一個函數(shù)指針表中,稱為文件操作函數(shù)集(file_operations) 。例如,對于打開文件操作,ext4 文件系統(tǒng)會實現(xiàn)自己的open函數(shù),VFS 會調(diào)用 ext4 文件系統(tǒng)的open函數(shù)來完成實際的文件打開操作。在傳遞操作請求時,VFS 會將文件對象、索引節(jié)點以及其他相關參數(shù)傳遞給底層文件系統(tǒng)的函數(shù),底層文件系統(tǒng)根據(jù)這些參數(shù)來執(zhí)行具體的操作 。底層文件系統(tǒng)在完成操作后,會將結果返回給 VFS,VFS 再將結果返回給應用程序。
四、VFS與其他內(nèi)核組件的關系
4.1與進程管理的關聯(lián)
在 Linux 系統(tǒng)中,進程是資源分配和調(diào)度的基本單位,而文件操作是進程運行過程中常見的行為之一。進程與 VFS 之間存在著緊密的聯(lián)系,這種聯(lián)系主要體現(xiàn)在進程對文件的各種操作上,比如打開文件、讀取文件、寫入文件、關閉文件等 。當進程進行這些文件操作時,它并不是直接與底層的文件系統(tǒng)打交道,而是通過 VFS 提供的統(tǒng)一接口來完成。這就好比進程是一個顧客,它只需要告訴 VFS 這個 “服務員” 自己想要進行什么樣的文件操作,VFS 就會去協(xié)調(diào)底層的文件系統(tǒng)來滿足進程的需求 。
以進程打開文件為例,當一個進程調(diào)用open函數(shù)打開一個文件時,VFS 會在后臺進行一系列復雜的操作。首先,VFS 會根據(jù)進程提供的文件路徑,在目錄項高速緩存(dentry cache)中查找對應的目錄項。如果目錄項不在緩存中,VFS 就會從磁盤上讀取目錄項信息,并將其加入到緩存中。通過目錄項,VFS 可以找到對應的索引節(jié)點(inode) 。索引節(jié)點中包含了文件的各種元數(shù)據(jù),如文件的大小、權限、所有者、創(chuàng)建時間、修改時間、數(shù)據(jù)塊位置信息等。如果索引節(jié)點不在內(nèi)存中,VFS 會從磁盤上讀取該索引節(jié)點并將其加載到內(nèi)存中 。
接下來,VFS 會為打開的文件創(chuàng)建一個文件對象。文件對象中包含了指向索引節(jié)點的指針、當前文件的讀寫位置、文件操作的狀態(tài)標志以及指向文件操作函數(shù)集的指針等信息 。這個文件對象就像是進程與文件之間的 “橋梁”,進程通過文件對象來對文件進行后續(xù)的操作。同時,進程的task_struct結構體中包含一個files_struct結構體,files_struct結構體中維護著一個文件描述符表,文件描述符表中的每一項都指向一個打開的文件對象 。通過這種方式,進程可以方便地管理自己打開的文件。
在這個過程中,我們可以看到 VFS 起到了關鍵的作用。它不僅為進程提供了統(tǒng)一的文件操作接口,使得進程無需關心底層文件系統(tǒng)的具體實現(xiàn)細節(jié),還負責管理文件系統(tǒng)相關的數(shù)據(jù)結構,如目錄項、索引節(jié)點、文件對象等,確保文件操作的順利進行 。同時,VFS 與進程管理之間的緊密協(xié)作也體現(xiàn)了 Linux 內(nèi)核設計的精妙之處,各個組件之間相互配合,共同為系統(tǒng)的高效運行提供保障 。
4.2在 Linux 內(nèi)核架構中的位置
為了更直觀地理解 VFS 在 Linux 內(nèi)核中的地位,我們來看一下 Linux 內(nèi)核架構圖:
圖片
從圖中可以清晰地看到,VFS 處于用戶空間和各種具體文件系統(tǒng)之間,是連接兩者的關鍵橋梁 。用戶空間的應用程序通過系統(tǒng)調(diào)用接口(System Call Interface)與內(nèi)核進行交互,而 VFS 則負責處理這些系統(tǒng)調(diào)用,并將其轉(zhuǎn)發(fā)到底層的具體文件系統(tǒng) 。
在 Linux 系統(tǒng)中,“一切皆文件” 的理念深入人心,而 VFS 正是這一理念的重要體現(xiàn)。它將不同類型的文件系統(tǒng),如基于磁盤的文件系統(tǒng)(如ext4、XFS 等)、網(wǎng)絡文件系統(tǒng)(如 NFS、CIFS 等)、虛擬文件系統(tǒng)(如 /proc、/sys 等),都統(tǒng)一抽象成可以通過文件操作接口(如open、close、read、write 等)訪問的對象 。這使得應用程序可以使用統(tǒng)一的方式來操作不同類型的文件系統(tǒng),大大提高了系統(tǒng)的通用性和可擴展性 。
例如,當應用程序調(diào)用read函數(shù)讀取文件內(nèi)容時,這個請求首先會通過系統(tǒng)調(diào)用接口進入內(nèi)核空間,然后由 VFS 接收 。VFS 根據(jù)文件的路徑信息,確定該文件所屬的具體文件系統(tǒng),并將read請求轉(zhuǎn)發(fā)給對應的文件系統(tǒng)驅(qū)動程序 。文件系統(tǒng)驅(qū)動程序根據(jù) VFS 傳遞的參數(shù),在磁盤或其他存儲設備上讀取相應的數(shù)據(jù),并將數(shù)據(jù)返回給 VFS 。VFS 再將數(shù)據(jù)返回給應用程序,完成整個文件讀取操作 。
VFS 在 Linux 內(nèi)核架構中的核心位置,使其成為了文件系統(tǒng)管理的關鍵組件。它不僅為用戶空間提供了統(tǒng)一的文件訪問接口,還協(xié)調(diào)了不同文件系統(tǒng)之間的工作,確保了 Linux 系統(tǒng)在文件管理方面的高效性和靈活性 。