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















 
 
 










 
 
 
 