Linux 的虛擬文件系統(tǒng)(真正理解“一切皆文件”)
1,引言
Linux 中允許眾多不同的文件系統(tǒng)共存,如 ext2, ext3, vfat 等。通過使用同一套文件 I/O 系統(tǒng) 調(diào)用即可對 Linux 中的任意文件進行操作而無需考慮其所在的具體文件系統(tǒng)格式;更進一步,對文件的 操作可以跨文件系統(tǒng)而執(zhí)行。如圖 1 所示,我們可以使用 cp 命令從 vfat 文件系統(tǒng)格式的硬盤拷貝數(shù)據(jù)到 ext3 文件系統(tǒng)格式的硬盤;而這樣的操作涉及到兩個不同的文件系統(tǒng)。
圖 1. 跨文件系統(tǒng)的文件操作

“一切皆是文件”是 Unix/Linux 的基本哲學之一。不僅普通的文件,目錄、字符設備、塊設備、 套接字等在 Unix/Linux 中都是以文件被對待;它們雖然類型不同,但是對其提供的卻是同一套操作界面。
圖 2. 一切皆是文件

而虛擬文件系統(tǒng)正是實現(xiàn)上述兩點 Linux 特性的關鍵所在。虛擬文件系統(tǒng)(Virtual File System, 簡稱 VFS), 是 Linux 內(nèi)核中的一個軟件層,用于給用戶空間的程序提供文件系統(tǒng)接口;同時,它也提供了內(nèi)核中的一個 抽象功能,允許不同的文件系統(tǒng)共存。系統(tǒng)中所有的文件系統(tǒng)不但依賴 VFS 共存,而且也依靠 VFS 協(xié)同工作。
為了能夠支持各種實際文件系統(tǒng),VFS 定義了所有文件系統(tǒng)都支持的基本的、概念上的接口和數(shù)據(jù) 結構;同時實際文件系統(tǒng)也提供 VFS 所期望的抽象接口和數(shù)據(jù)結構,將自身的諸如文件、目錄等概念在形式 上與VFS的定義保持一致。換句話說,一個實際的文件系統(tǒng)想要被 Linux 支持,就必須提供一個符合VFS標準 的接口,才能與 VFS 協(xié)同工作。實際文件系統(tǒng)在統(tǒng)一的接口和數(shù)據(jù)結構下隱藏了具體的實現(xiàn)細節(jié),所以在VFS 層和內(nèi)核的其他部分看來,所有文件系統(tǒng)都是相同的。圖3顯示了VFS在內(nèi)核中與實際的文件系統(tǒng)的協(xié)同關系。
圖3. VFS在內(nèi)核中與其他的內(nèi)核模塊的協(xié)同關系

我們已經(jīng)知道,正是由于在內(nèi)核中引入了VFS,跨文件系統(tǒng)的文件操作才能實現(xiàn),“一切皆是文件” 的口號才能承諾。而為什么引入了VFS,就能實現(xiàn)這兩個特性呢?在接下來,我們將以這樣的一個思路來切入
文章的正題:我們將先簡要介紹下用以描述VFS模型的一些數(shù)據(jù)結構,總結出這些數(shù)據(jù)結構相互間的關系;然后 選擇兩個具有代表性的文件I/O操作sys_open()和sys_read()來詳細說明內(nèi)核是如何借助VFS和具體的文件系統(tǒng)打 交道以實現(xiàn)跨文件系統(tǒng)的文件操作和承諾“一切皆是文件”的口號。
2 VFS數(shù)據(jù)結構
2.1 一些基本概念
從本質上講,文件系統(tǒng)是特殊的數(shù)據(jù)分層存儲結構,它包含文件、目錄和相關的控制信息。為了描述 這個結構,Linux引入了一些基本概念:
文件 一組在邏輯上具有完整意義的信息項的系列。在Linux中,除了普通文件,其他諸如目錄、設備、套接字等 也以文件被對待??傊?ldquo;一切皆文件”。
目錄 目錄好比一個文件夾,用來容納相關文件。因為目錄可以包含子目錄,所以目錄是可以層層嵌套,形成 文件路徑。在Linux中,目錄也是以一種特殊文件被對待的,所以用于文件的操作同樣也可以用在目錄上。
目錄項 在一個文件路徑中,路徑中的每一部分都被稱為目錄項;如路徑/home/source/helloworld.c中,目錄 /, home, source和文件 helloworld.c都是一個目錄項。
索引節(jié)點 用于存儲文件的元數(shù)據(jù)的一個數(shù)據(jù)結構。文件的元數(shù)據(jù),也就是文件的相關信息,和文件本身是兩個不同 的概念。它包含的是諸如文件的大小、擁有者、創(chuàng)建時間、磁盤位置等和文件相關的信息。
超級塊 用于存儲文件系統(tǒng)的控制信息的數(shù)據(jù)結構。描述文件系統(tǒng)的狀態(tài)、文件系統(tǒng)類型、大小、區(qū)塊數(shù)、索引節(jié)點數(shù)等,存放于磁盤的特定扇區(qū)中。
如上的幾個概念在磁盤中的位置關系如圖4所示。
圖4. 磁盤與文件系統(tǒng)

關于文件系統(tǒng)的三個易混淆的概念:
創(chuàng)建 以某種方式格式化磁盤的過程就是在其之上建立一個文件系統(tǒng)的過程。創(chuàng)建文現(xiàn)系統(tǒng)時,會在磁盤的特定位置寫入 關于該文件系統(tǒng)的控制信息。
注冊 向內(nèi)核報到,聲明自己能被內(nèi)核支持。一般在編譯內(nèi)核的時侯注冊;也可以加載模塊的方式手動注冊。注冊過程實 際上是將表示各實際文件系統(tǒng)的數(shù)據(jù)結構struct file_system_type 實例化。
安裝 也就是我們熟悉的mount操作,將文件系統(tǒng)加入到Linux的根文件系統(tǒng)的目錄樹結構上;這樣文件系統(tǒng)才能被訪問。
2.2 VFS數(shù)據(jù)結構
VFS依靠四個主要的數(shù)據(jù)結構和一些輔助的數(shù)據(jù)結構來描述其結構信息,這些數(shù)據(jù)結構表現(xiàn)得就像是對象; 每個主要對象中都包含由操作函數(shù)表構成的操作對象,這些操作對象描述了內(nèi)核針對這幾個主要的對象可以進行的操作。
2.2.1 超級塊對象
存儲一個已安裝的文件系統(tǒng)的控制信息,代表一個已安裝的文件系統(tǒng);每次一個實際的文件系統(tǒng)被安裝時, 內(nèi)核會從磁盤的特定位置讀取一些控制信息來填充內(nèi)存中的超級塊對象。一個安裝實例和一個超級塊對象一一對應。 超級塊通過其結構中的一個域s_type記錄它所屬的文件系統(tǒng)類型。
根據(jù)第三部分追蹤源代碼的需要,以下是對該超級塊結構的部分相關成員域的描述,(如下同):
清單1. 超級塊
- struct super_block { //超級塊數(shù)據(jù)結構
 - struct list_head s_list; /*指向超級塊鏈表的指針*/
 - ……
 - struct file_system_type *s_type; /*文件系統(tǒng)類型*/
 - struct super_operations *s_op; /*超級塊方法*/
 - ……
 - struct list_head s_instances; /*該類型文件系統(tǒng)*/
 - ……
 - };
 - struct super_operations { //超級塊方法
 - ……
 - //該函數(shù)在給定的超級塊下創(chuàng)建并初始化一個新的索引節(jié)點對象
 - struct inode *(*alloc_inode)(struct super_block *sb);
 - ……
 - //該函數(shù)從磁盤上讀取索引節(jié)點,并動態(tài)填充內(nèi)存中對應的索引節(jié)點對象的剩余部分
 - void (*read_inode) (struct inode *);
 - ……
 - };
 
2.2.2 索引節(jié)點對象
索引節(jié)點對象存儲了文件的相關信息,代表了存儲設備上的一個實際的物理文件。當一個 文件***被訪問時,內(nèi)核會在內(nèi)存中組裝相應的索引節(jié)點對象,以便向內(nèi)核提供對一個文件進行操 作時所必需的全部信息;這些信息一部分存儲在磁盤特定位置,另外一部分是在加載時動態(tài)填充的。
清單2. 索引節(jié)點
struct inode {//索引節(jié)點結構 …… struct inode_operations *i_op; /*索引節(jié)點操作表*/ struct file_operations *i_fop; /*該索引節(jié)點對應文件的文件操作集*/ struct super_block *i_sb; /*相關的超級塊*/ ……};struct inode_operations { //索引節(jié)點方法 …… //該函數(shù)為dentry對象所對應的文件創(chuàng)建一個新的索引節(jié)點,主要是由open()系統(tǒng)調(diào)用來調(diào)用 int (*create) (struct inode *,struct dentry *,int, struct nameidata *); //在特定目錄中尋找dentry對象所對應的索引節(jié)點 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *); ……};
2.2.3 目錄項對象
引入目錄項的概念主要是出于方便查找文件的目的。一個路徑的各個組成部分,不管是目錄還是 普通的文件,都是一個目錄項對象。如,在路徑/home/source/test.c中,目錄 /, home, source和文件 test.c都對應一個目錄項對象。不同于前面的兩個對象,目錄項對象沒有對應的磁盤數(shù)據(jù)結構,VFS在遍 歷路徑名的過程中現(xiàn)場將它們逐個地解析成目錄項對象。
清單3. 目錄項
- struct inode {//索引節(jié)點結構
 - ……
 - struct inode_operations *i_op; /*索引節(jié)點操作表*/
 - struct file_operations *i_fop; /*該索引節(jié)點對應文件的文件操作集*/
 - struct super_block *i_sb; /*相關的超級塊*/
 - ……
 - };
 - struct inode_operations { //索引節(jié)點方法
 - ……
 - //該函數(shù)為dentry對象所對應的文件創(chuàng)建一個新的索引節(jié)點,主要是由open()系統(tǒng)調(diào)用來調(diào)用
 - int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
 - //在特定目錄中尋找dentry對象所對應的索引節(jié)點
 - struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
 - ……
 - };
 
2.2.4 文件對象
文件對象是已打開的文件在內(nèi)存中的表示,主要用于建立進程和磁盤上的文件的對應關系。它由sys_open() 現(xiàn)場創(chuàng)建,由sys_close()銷毀。文件對象和物理文件的關系有點像進程和程序的關系一樣。當我們站在用戶空間來看 待VFS,我們像是只需與文件對象打交道,而無須關心超級塊,索引節(jié)點或目錄項。因為多個進程可以同時打開和操作 同一個文件,所以同一個文件也可能存在多個對應的文件對象。文件對象僅僅在進程觀點上代表已經(jīng)打開的文件,它 反過來指向目錄項對象(反過來指向索引節(jié)點)。一個文件對應的文件對象可能不是惟一的,但是其對應的索引節(jié)點和 目錄項對象無疑是惟一的。
清單4. 文件對象
- struct file {
 - ……
 - struct list_head f_list; /*文件對象鏈表*/
 - struct dentry *f_dentry; /*相關目錄項對象*/
 - struct vfsmount *f_vfsmnt; /*相關的安裝文件系統(tǒng)*/
 - struct file_operations *f_op; /*文件操作表*/
 - ……
 - };
 - struct file_operations {
 - ……
 - //文件讀操作
 - ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
 - ……
 - //文件寫操作
 - ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
 - ……
 - int (*readdir) (struct file *, void *, filldir_t);
 - ……
 - //文件打開操作
 - int (*open) (struct inode *, struct file *);
 - ……
 - };
 
2.2.5 其他VFS對象
2.2.5.1 和文件系統(tǒng)相關
根據(jù)文件系統(tǒng)所在的物理介質和數(shù)據(jù)在物理介質上的組織方式來區(qū)分不同的文件系統(tǒng)類型的。 file_system_type結構用于描述具體的文件系統(tǒng)的類型信息。被Linux支持的文件系統(tǒng),都有且僅有一 個file_system_type結構而不管它有零個或多個實例被安裝到系統(tǒng)中。
而與此對應的是每當一個文件系統(tǒng)被實際安裝,就有一個vfsmount結構體被創(chuàng)建,這個結構體對應一個安裝點。
清單5. 和文件系統(tǒng)相關
- struct file_system_type {
 - const char *name; /*文件系統(tǒng)的名字*/
 - struct subsystem subsys; /*sysfs子系統(tǒng)對象*/
 - int fs_flags; /*文件系統(tǒng)類型標志*/
 - /*在文件系統(tǒng)被安裝時,從磁盤中讀取超級塊,在內(nèi)存中組裝超級塊對象*/
 - struct super_block *(*get_sb) (struct file_system_type*,
 - int, const char*, void *);
 - void (*kill_sb) (struct super_block *); /*終止訪問超級塊*/
 - struct module *owner; /*文件系統(tǒng)模塊*/
 - struct file_system_type * next; /*鏈表中的下一個文件系統(tǒng)類型*/
 - struct list_head fs_supers; /*具有同一種文件系統(tǒng)類型的超級塊對象鏈表*/
 - };
 - struct vfsmount
 - {
 - struct list_head mnt_hash; /*散列表*/
 - struct vfsmount *mnt_parent; /*父文件系統(tǒng)*/
 - struct dentry *mnt_mountpoint; /*安裝點的目錄項對象*/
 - struct dentry *mnt_root; /*該文件系統(tǒng)的根目錄項對象*/
 - struct super_block *mnt_sb; /*該文件系統(tǒng)的超級塊*/
 - struct list_head mnt_mounts; /*子文件系統(tǒng)鏈表*/
 - struct list_head mnt_child; /*子文件系統(tǒng)鏈表*/
 - atomic_t mnt_count; /*使用計數(shù)*/
 - int mnt_flags; /*安裝標志*/
 - char *mnt_devname; /*設備文件名*/
 - struct list_head mnt_list; /*描述符鏈表*/
 - struct list_head mnt_fslink; /*具體文件系統(tǒng)的到期列表*/
 - struct namespace *mnt_namespace; /*相關的名字空間*/
 - };
 
2.2.5.2 和進程相關
清單6. 打開的文件集
- struct files_struct {//打開的文件集
 - atomic_t count; /*結構的使用計數(shù)*/
 - ……
 - int max_fds; /*文件對象數(shù)的上限*/
 - int max_fdset; /*文件描述符的上限*/
 - int next_fd; /*下一個文件描述符*/
 - struct file ** fd; /*全部文件對象數(shù)組*/
 - ……
 - };
 - struct fs_struct {//建立進程與文件系統(tǒng)的關系
 - atomic_t count; /*結構的使用計數(shù)*/
 - rwlock_t lock; /*保護該結構體的鎖*/
 - int umask; /*默認的文件訪問權限*/
 - struct dentry * root; /*根目錄的目錄項對象*/
 - struct dentry * pwd; /*當前工作目錄的目錄項對象*/
 - struct dentry * altroot; /*可供選擇的根目錄的目錄項對象*/
 - struct vfsmount * rootmnt; /*根目錄的安裝點對象*/
 - struct vfsmount * pwdmnt; /*pwd的安裝點對象*/
 - struct vfsmount * altrootmnt;/*可供選擇的根目錄的安裝點對象*/
 - };
 
2.2.5.3 和路徑查找相關
清單7. 輔助查找
- struct nameidata {
 - struct dentry *dentry; /*目錄項對象的地址*/
 - struct vfsmount *mnt; /*安裝點的數(shù)據(jù)*/
 - struct qstr last; /*路徑中的***一個component*/
 - unsigned int flags; /*查找標識*/
 - int last_type; /*路徑中的***一個component的類型*/
 - unsigned depth; /*當前symbolic link的嵌套深度,不能大于6*/
 - char *saved_names[MAX_NESTED_LINKS + 1];/
 - /*和嵌套symbolic link 相關的pathname*/
 - union {
 - struct open_intent open; /*說明文件該如何訪問*/
 - } intent; /*專用數(shù)據(jù)*/
 - };
 
2.2.6 對象間的聯(lián)系
如上的數(shù)據(jù)結構并不是孤立存在的。正是通過它們的有機聯(lián)系,VFS才能正常工作。如下的幾張圖是對它們之間的聯(lián)系的描述。
如圖5所示,被Linux支持的文件系統(tǒng),都有且僅有一個file_system_type結構而不管它有零個或多個實例被安裝到系統(tǒng) 中。每安裝一個文件系統(tǒng),就對應有一個超級塊和安裝點。超級塊通過它的一個域s_type指向其對應的具體的文件系統(tǒng)類型。具體的 文件系統(tǒng)通過file_system_type中的一個域fs_supers鏈接具有同一種文件類型的超級塊。同一種文件系統(tǒng)類型的超級塊通過域s_instances鏈 接。
圖5. 超級塊、安裝點和具體的文件系統(tǒng)的關系

從圖6可知:進程通過task_struct中的一個域files_struct files來了解它當前所打開的文件對象;而我們通常所說的文件 描述符其實是進程打開的文件對象數(shù)組的索引值。文件對象通過域f_dentry找到它對應的dentry對象,再由dentry對象的域d_inode找 到它對應的索引結點,這樣就建立了文件對象與實際的物理文件的關聯(lián)。***,還有一點很重要的是, 文件對象所對應的文件操作函數(shù) 列表是通過索引結點的域i_fop得到的。圖6對第三部分源碼的理解起到很大的作用。
圖6. 進程與超級塊、文件、索引結點、目錄項的關系

3 基于VFS的文件I/O
到目前為止,文章主要都是從理論上來講述VFS的運行機制;接下來我們將深入源代碼層中,通過闡述兩個具有代表性的系統(tǒng) 調(diào)用sys_open()和sys_read()來更好地理解VFS向具體文件系統(tǒng)提供的接口機制。由于本文更關注的是文件操作的整個流程體制,所以我 們在追蹤源代碼時,對一些細節(jié)性的處理不予關心。又由于篇幅所限,只列出相關代碼。本文中的源代碼來自于linux-2.6.17內(nèi)核版本。
在深入sys_open()和sys_read()之前,我們先概覽下調(diào)用sys_read()的上下文。圖7描述了從用戶空間的read()調(diào)用到數(shù)據(jù)從 磁盤讀出的整個流程。當在用戶應用程序調(diào)用文件I/O read()操作時,系統(tǒng)調(diào)用sys_read()被激發(fā),sys_read()找到文件所在的具體文件 系統(tǒng),把控制權傳給該文件系統(tǒng),***由具體文件系統(tǒng)與物理介質交互,從介質中讀出數(shù)據(jù)。
圖7. 從物理介質讀數(shù)據(jù)的過程

3.1 sys_open()
sys_open()系統(tǒng)調(diào)用打開或創(chuàng)建一個文件,成功返回該文件的文件描述符。圖8是sys_open()實現(xiàn)代碼中主要的函數(shù)調(diào)用關系圖。
圖8. sys_open函數(shù)調(diào)用關系圖

由于sys_open()的代碼量大,函數(shù)調(diào)用關系復雜,以下主要是對該函數(shù)做整體的解析;而對其中的一些關鍵點,則列出其關鍵代碼。
a. 從sys_open()的函數(shù)調(diào)用關系圖可以看到,sys_open()在做了一些簡單的參數(shù)檢驗后,就把接力棒傳給do_sys_open():
1)、首先,get_unused_fd()得到一個可用的文件描述符;通過該函數(shù),可知文件描述符實質是進程打開文件列表中對應某個文件對象的索引值;
2)、接著,do_filp_open()打開文件,返回一個file對象,代表由該進程打開的一個文件;進程通過這樣的一個數(shù)據(jù)結構對物理文件進行讀寫操作。
3)、***,fd_install()建立文件描述符與file對象的聯(lián)系,以后進程對文件的讀寫都是通過操縱該文件描述符而進行。
b. do_filp_open()用于打開文件,返回一個file對象;而打開之前需要先找到該文件:
1)、open_namei()用于根據(jù)文件路徑名查找文件,借助一個持有路徑信息的數(shù)據(jù)結構nameidata而進行;
2)、查找結束后將填充有路徑信息的nameidata返回給接下來的函數(shù)nameidata_to_filp()從而得到最終的file對象;當達到目的后,nameidata這個數(shù)據(jù)結構將會馬上被釋放。
c.open_namei()用于查找一個文件:
1)、path_lookup_open()實現(xiàn)文件的查找功能;要打開的文件若不存在,還需要有一個新建的過程,則調(diào)用 path_lookup_create(),后者和前者封裝的是同一個實際的路徑查找函數(shù),只是參數(shù)不一樣,使它們在處理細節(jié)上有所偏差;
2)、當是以新建文件的方式打開文件時,即設置了O_CREAT標識時需要創(chuàng)建一個新的索引節(jié)點,代表創(chuàng)建一個文件。在vfs_create()里的一句 核心語句dir->i_op->create(dir, dentry, mode, nd)可知它調(diào)用了具體的文件系統(tǒng)所提供的創(chuàng)建索引節(jié)點的方法。注意:這邊的索引節(jié)點的概念,還只是位于內(nèi)存之中,它和磁盤上的物理的索引節(jié)點的關系就像 位于內(nèi)存中和位于磁盤中的文件一樣。此時新建的索引節(jié)點還不能完全標志一個物理文件的成功創(chuàng)建,只有當把索引節(jié)點回寫到磁盤上才是一個物理文件的真正創(chuàng) 建。想想我們以新建的方式打開一個文件,對其讀寫但最終沒有保存而關閉,則位于內(nèi)存中的索引節(jié)點會經(jīng)歷從新建到消失的過程,而磁盤卻始終不知道有人曾經(jīng)想 過創(chuàng)建一個文件,這是因為索引節(jié)點沒有回寫的緣故。
3)、path_to_nameidata()填充nameidata數(shù)據(jù)結構;
4)、may_open()檢查是否可以打開該文件;一些文件如鏈接文件和只有寫權限的目錄是不能被打開的,先檢查 nd->dentry->inode所指的文件是否是這一類文件,是的話則錯誤返回。還有一些文件是不能以TRUNC的方式打開的,若 nd->dentry->inode所指的文件屬于這一類,則顯式地關閉TRUNC標志位。接著如果有以TRUNC方式打開文件的,則更新 nd->dentry->inode的信息
3.1.1__path_lookup_intent_open()
不管是path_lookup_open()還是path_lookup_create()最終都是調(diào)用 __path_lookup_intent_open()來實現(xiàn)查找文件的功能。 查找時,在遍歷路徑的過程中,會逐層地將各個路徑組成部分解析成目錄項對象,如果此目錄項對象在目錄項緩存中,則直接從緩存中獲得;如果該目錄項在緩存中 不存在,則進行一次實際的讀盤操作,從磁盤中讀取該目錄項所對應的索引節(jié)點。得到索引節(jié)點后,則建立索引節(jié)點與該目錄項的聯(lián)系。如此循環(huán),直到最終找到目 標文件對應的目錄項,也就找到了索引節(jié)點,而由索引節(jié)點找到對應的超級塊對象就可知道該文件所在的文件系統(tǒng)的類型。 從磁盤中讀取該目錄項所對應的索引節(jié)點;這將引發(fā)VFS和實際的文件系統(tǒng)的一次交互。從前面的VFS理論介紹可知,讀索引節(jié)點方法是由超級塊來提供的。而 當安裝一個實際的文件系統(tǒng)時,在內(nèi)存中創(chuàng)建的超級塊的信息是由一個實際文件系統(tǒng)的相關信息來填充的,這里的相關信息就包括了實際文件系統(tǒng)所定義的超級塊的 操作函數(shù)列表,當然也就包括了讀索引節(jié)點的具體執(zhí)行方式。 當繼續(xù)追蹤一個實際文件系統(tǒng)ext3的ext3_read_inode()時,可發(fā)現(xiàn)這個函數(shù)很重要的一個工作就是為不同的文件類型設置不同的索引節(jié)點操 作函數(shù)表和文件操作函數(shù)表。
清單8. ext3_read_inode
- void ext3_read_inode(struct inode * inode)
 - {
 - ……
 - //是普通文件
 - if (S_ISREG(inode->i_mode)) {
 - inode->i_op = &ext3_file_inode_operations;
 - inode->i_fop = &ext3_file_operations;
 - ext3_set_aops(inode);
 - } else if (S_ISDIR(inode->i_mode)) {
 - //是目錄文件
 - inode->i_op = &ext3_dir_inode_operations;
 - inode->i_fop = &ext3_dir_operations;
 - } else if (S_ISLNK(inode->i_mode)) {
 - // 是連接文件
 - ……
 - } else {
 - // 如果以上三種情況都排除了,則是設備驅動
 - //這里的設備還包括套結字、FIFO等偽設備
 - ……
 - }
 
3.1.2 nameidata_to_filp子函數(shù):__dentry_open
這是VFS與實際的文件系統(tǒng)聯(lián)系的一個關鍵點。從3.1.1小節(jié)分析中可知,調(diào)用實際文件系統(tǒng)讀取索引節(jié)點的方法讀取索引節(jié)點時,實際文件系統(tǒng)會根據(jù)文件 的不同類型賦予索引節(jié)點不同的文件操作函數(shù)集,如普通文件有普通文件對應的一套操作函數(shù),設備文件有設備文件對應的一套操作函數(shù)。這樣當把對應的索引節(jié)點 的文件操作函數(shù)集賦予文件對象,以后對該文件進行操作時,比如讀操作,VFS雖然對各種不同文件都是執(zhí)行同一個read()操作界面,但是真正讀時,內(nèi)核 卻知道怎么區(qū)分對待不同的文件類型。
清單9. __dentry_open
- static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
 - int flags, struct file *f,
 - int (*open)(struct inode *, struct file *))
 - {
 - struct inode *inode;
 - ……
 - //整個函數(shù)的工作在于填充一個file對象
 - ……
 - f->f_mapping = inode->i_mapping;
 - f->f_dentry = dentry;
 - f->f_vfsmnt = mnt;
 - f->f_pos = 0;
 - //將對應的索引節(jié)點的文件操作函數(shù)集賦予文件對象的操作列表
 - <span class="boldcode">f->f_op = fops_get(inode->i_fop); </span>
 - ……
 - //若文件自己定義了open操作,則執(zhí)行這個特定的open操作。
 - if (!open && f->f_op)
 - open = f->f_op->open;
 - if (open) {
 - error = open(inode, f);
 - if (error)
 - goto cleanup_all;
 - ……
 - return f;
 - }
 
3.2 sys_read()
sys_read()系統(tǒng)調(diào)用用于從已打開的文件讀取數(shù)據(jù)。如read成功,則返回讀到的字節(jié)數(shù)。如已到達文件的尾端,則返回0。圖9是sys_read()實現(xiàn)代碼中的函數(shù)調(diào)用關系圖。
圖9. sys_read函數(shù)調(diào)用關系圖

對文件進行讀操作時,需要先打開它。從3.1小結可知,打開一個文件時,會在內(nèi)存組裝一個文件對象,希望對該文件執(zhí)行的操作方法已在文件對象設置好。所以 對文件進行讀操作時,VFS在做了一些簡單的轉換后(由文件描述符得到其對應的文件對象;其核心思想是返回 current->files->fd[fd]所指向的文件對象),就可以通過語句 file->f_op->read(file, buf, count, pos)輕松調(diào)用實際文件系統(tǒng)的相應方法對文件進行讀操作了。
4 解決問題
4.1 跨文件系統(tǒng)的文件操作的基本原理
到此,我們也就能夠解釋在Linux中為什么能夠跨文件系統(tǒng)地操作文件了。舉個例子,將vfat格式的磁盤上的一個文件a.txt拷貝到ext3格式的磁 盤上,命名為b.txt。這包含兩個過程,對a.txt進行讀操作,對b.txt進行寫操作。讀寫操作前,需要先打開文件。由前面的分析可知,打開文件 時,VFS會知道該文件對應的文件系統(tǒng)格式,以后操作該文件時,VFS會調(diào)用其對應的實際文件系統(tǒng)的操作方法。所以,VFS調(diào)用vfat的讀文件方法將 a.txt的數(shù)據(jù)讀入內(nèi)存;在將a.txt在內(nèi)存中的數(shù)據(jù)映射到b.txt對應的內(nèi)存空間后,VFS調(diào)用ext3的寫文件方法將b.txt寫入磁盤;從而 實現(xiàn)了最終的跨文件系統(tǒng)的復制操作。
4.2“一切皆是文件”的實現(xiàn)根本
不論是普通的文件,還是特殊的目錄、設備等,VFS都將它們同等看待成文件,通過同一套文件操作界面來對它們進行操作。操作文件時需先打開;打開文件 時,VFS會知道該文件對應的文件系統(tǒng)格式;當VFS把控制權傳給實際的文件系統(tǒng)時,實際的文件系統(tǒng)再做出具體區(qū)分,對不同的文件類型執(zhí)行不同的操作。這 也就是“一切皆是文件”的根本所在。
5 總結
VFS即虛擬文件系統(tǒng)是Linux文件系統(tǒng)中的一個抽象軟件層;因為它的支持,眾多不同的實際文件系統(tǒng)才能在Linux中共存,跨文件系統(tǒng)操作才能實現(xiàn)。 VFS借助它四個主要的數(shù)據(jù)結構即超級塊、索引節(jié)點、目錄項和文件對象以及一些輔助的數(shù)據(jù)結構,向Linux中不管是普通的文件還是目錄、設備、套接字等 都提供同樣的操作界面,如打開、讀寫、關閉等。只有當把控制權傳給實際的文件系統(tǒng)時,實際的文件系統(tǒng)才會做出區(qū)分,對不同的文件類型執(zhí)行不同的操作。由此 可見,正是有了VFS的存在,跨文件系統(tǒng)操作才能執(zhí)行,Unix/Linux中的“一切皆是文件”的口號才能夠得以實現(xiàn)。















 
 
 








 
 
 
 