1 引言

Linux 中允許眾多不同的檔案系統共存,如 ext2, ext3, vfat 等。通過使用同一套檔案 I/O 系統 呼叫即可對 Linux 中的任意檔案進行操作而無需考慮其所在的具體檔案系統格式;更進一步,對檔案的 操作可以跨檔案系統而執行。如圖 1 所示,我們可以使用 cp 命令從 vfat 檔案系統格式的硬碟拷貝資料到 ext3 檔案系統格式的硬碟;而這樣的操作涉及到兩個不同的檔案系統。


圖 1. 跨檔案系統的檔案操作
圖片示例_跨檔案系統的檔案操作 

 

“一切皆是檔案”是 Unix/Linux 的基本哲學之一。不僅普通的檔案,目錄、字元裝置、塊裝置、 套接字等在 Unix/Linux 中都是以檔案被對待;它們雖然型別不同,但是對其提供的卻是同一套操作介面。


圖 2. 一切皆是檔案
圖片示例_一切皆是檔案 

而虛擬檔案系統正是實現上述兩點 Linux 特性的關鍵所在。虛擬檔案系統(Virtual File System, 簡稱 VFS), 是 Linux 核心中的一個軟體層,用於給使用者空間的程式提供檔案系統介面;同時,它也提供了核心中的一個 抽象功能,允許不同的檔案系統共存。系統中所有的檔案系統不但依賴 VFS 共存,而且也依靠 VFS 協同工作。

為了能夠支援各種實際檔案系統,VFS 定義了所有檔案系統都支援的基本的、概念上的介面和資料 結構;同時實際檔案系統也提供 VFS 所期望的抽象介面和資料結構,將自身的諸如檔案、目錄等概念在形式 上與VFS的定義保持一致。換句話說,一個實際的檔案系統想要被 Linux 支援,就必須提供一個符合VFS標準 的介面,才能與 VFS 協同工作。實際檔案系統在統一的介面和資料結構下隱藏了具體的實現細節,所以在VFS 層和核心的其他部分看來,所有檔案系統都是相同的。圖3顯示了VFS在核心中與實際的檔案系統的協同關係。


圖3. VFS在核心中與其他的核心模組的協同關係
圖片示例_VFS在核心中與其他的核心模組的協同關係 

我們已經知道,正是由於在核心中引入了VFS,跨檔案系統的檔案操作才能實現,“一切皆是檔案” 的口號才能承諾。而為什麼引入了VFS,就能實現這兩個特性呢?在接下來,我們將以這樣的一個思路來切入 文章的正題:我們將先簡要介紹下用以描述VFS模型的一些資料結構,總結出這些資料結構相互間的關係;然後 選擇兩個具有代表性的檔案I/O操作sys_open()和sys_read()來詳細說明核心是如何藉助VFS和具體的檔案系統打 交道以實現跨檔案系統的檔案操作和承諾“一切皆是檔案”的口號。




回頁首


2 VFS資料結構

2.1 一些基本概念

從本質上講,檔案系統是特殊的資料分層儲存結構,它包含檔案、目錄和相關的控制資訊。為了描述 這個結構,Linux引入了一些基本概念:

檔案 一組在邏輯上具有完整意義的資訊項的系列。在Linux中,除了普通檔案,其他諸如目錄、裝置、套接字等 也以檔案被對待。總之,“一切皆檔案”。

目錄 目錄好比一個資料夾,用來容納相關檔案。因為目錄可以包含子目錄,所以目錄是可以層層巢狀,形成 檔案路徑。在Linux中,目錄也是以一種特殊檔案被對待的,所以用於檔案的操作同樣也可以用在目錄上。

目錄項 在一個檔案路徑中,路徑中的每一部分都被稱為目錄項;如路徑/home/source/helloworld.c中,目錄 /, home, source和檔案 helloworld.c都是一個目錄項。

索引節點 用於儲存檔案的元資料的一個數據結構。檔案的元資料,也就是檔案的相關資訊,和檔案本身是兩個不同 的概念。它包含的是諸如檔案的大小、擁有者、建立時間、磁碟位置等和檔案相關的資訊。

超級塊 用於儲存檔案系統的控制資訊的資料結構。描述檔案系統的狀態、檔案系統型別、大小、區塊數、索引節 點數等,存放於磁碟的特定扇區中。

如上的幾個概念在磁碟中的位置關係如圖4所示。


圖4. 磁碟與檔案系統
圖片示例_磁碟與檔案系統.jpg 

關於檔案系統的三個易混淆的概念:

建立 以某種方式格式化磁碟的過程就是在其之上建立一個檔案系統的過程。建立文現系統時,會在磁碟的特定位置寫入 關於該檔案系統的控制資訊。

註冊 向核心報到,宣告自己能被核心支援。一般在編譯核心的時侯註冊;也可以載入模組的方式手動註冊。註冊過程實 際上是將表示各實際檔案系統的資料結構struct file_system_type 例項化。

安裝 也就是我們熟悉的mount操作,將檔案系統加入到Linux的根檔案系統的目錄樹結構上;這樣檔案系統才能被訪問。

2.2 VFS資料結構

VFS依靠四個主要的資料結構和一些輔助的資料結構來描述其結構資訊,這些資料結構表現得就像是物件; 每個主要物件中都包含由操作函式表構成的操作物件,這些操作物件描述了核心針對這幾個主要的物件可以進行的操作。

2.2.1 超級塊物件

儲存一個已安裝的檔案系統的控制資訊,代表一個已安裝的檔案系統;每次一個實際的檔案系統被安裝時, 核心會從磁碟的特定位置讀取一些控制資訊來填充記憶體中的超級塊物件。一個安裝例項和一個超級塊物件一一對應。 超級塊通過其結構中的一個域s_type記錄它所屬的檔案系統型別。

根據第三部分追蹤原始碼的需要,以下是對該超級塊結構的部分相關成員域的描述,(如下同):


清單1. 超級塊
struct super_block { //超級塊資料結構
        struct list_head s_list;                /*指向超級塊連結串列的指標*/
        ……
        struct file_system_type  *s_type;       /*檔案系統型別*/
       struct super_operations  *s_op;         /*超級塊方法*/
        ……
        struct list_head         s_instances;   /*該型別檔案系統*/
        ……
};

struct super_operations { //超級塊方法
……
//該函式在給定的超級塊下建立並初始化一個新的索引節點物件
struct inode *(*alloc_inode)(struct super_block *sb);
……
//該函式從磁碟上讀取索引節點,並動態填充記憶體中對應的索引節點物件的剩餘部分
void (*read_inode) (struct inode *);
……
};


2.2.2 索引節點物件

索引節點物件儲存了檔案的相關資訊,代表了儲存裝置上的一個實際的物理檔案。當一個 檔案首次被訪問時,核心會在記憶體中組裝相應的索引節點物件,以便向核心提供對一個檔案進行操 作時所必需的全部資訊;這些資訊一部分儲存在磁碟特定位置,另外一部分是在載入時動態填充的。


清單2. 索引節點
struct inode {//索引節點結構
      ……
      struct inode_operations  *i_op;     /*索引節點操作表*/
     struct file_operations   *i_fop;  /*該索引節點對應檔案的檔案操作集*/
     struct super_block       *i_sb;     /*相關的超級塊*/
     ……
};

struct inode_operations { //索引節點方法
……
//該函式為dentry物件所對應的檔案建立一個新的索引節點,主要是由open()系統呼叫來呼叫
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);

 //在特定目錄中尋找dentry物件所對應的索引節點
 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
 ……

};


2.2.3 目錄項物件

引入目錄項的概念主要是出於方便查詢檔案的目的。一個路徑的各個組成部分,不管是目錄還是 普通的檔案,都是一個目錄項物件。如,在路徑/home/source/test.c中,目錄 /, home, source和檔案 test.c都對應一個目錄項物件。不同於前面的兩個物件,目錄項物件沒有對應的磁碟資料結構,VFS在遍 歷路徑名的過程中現場將它們逐個地解析成目錄項物件。


清單3. 目錄項
struct dentry {//目錄項結構
     ……
     struct inode *d_inode;           /*相關的索引節點*/
    struct dentry *d_parent;         /*父目錄的目錄項物件*/
    struct qstr d_name;              /*目錄項的名字*/
    ……
     struct list_head d_subdirs;      /*子目錄*/
    ……
     struct dentry_operations *d_op;  /*目錄項操作表*/
    struct super_block *d_sb;        /*檔案超級塊*/
    ……
};

struct dentry_operations {
//判斷目錄項是否有效;
int (*d_revalidate)(struct dentry *, struct nameidata *);
//為目錄項生成雜湊值;
int (*d_hash) (struct dentry *, struct qstr *);
……
};


2.2.4 檔案物件

檔案物件是已開啟的檔案在記憶體中的表示,主要用於建立程序和磁碟上的檔案的對應關係。它由sys_open() 現場建立,由sys_close()銷燬。檔案物件和物理檔案的關係有點像程序和程式的關係一樣。當我們站在使用者空間來看 待VFS,我們像是隻需與檔案物件打交道,而無須關心超級塊,索引節點或目錄項。因為多個程序可以同時開啟和操作 同一個檔案,所以同一個檔案也可能存在多個對應的檔案物件。檔案物件僅僅在程序觀點上代表已經開啟的檔案,它 反過來指向目錄項物件(反過來指向索引節點)。一個檔案對應的檔案物件可能不是惟一的,但是其對應的索引節點和 目錄項物件無疑是惟一的。


清單4. 檔案物件
struct file {
    ……
     struct list_head        f_list;        /*檔案物件連結串列*/
    struct dentry          *f_dentry;       /*相關目錄項物件*/
    struct vfsmount        *f_vfsmnt;       /*相關的安裝檔案系統*/
    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 和檔案系統相關

根據檔案系統所在的物理介質和資料在物理介質上的組織方式來區分不同的檔案系統型別的。 file_system_type結構用於描述具體的檔案系統的型別資訊。被Linux支援的檔案系統,都有且僅有一 個file_system_type結構而不管它有零個或多個例項被安裝到系統中。

而與此對應的是每當一個檔案系統被實際安裝,就有一個vfsmount結構體被建立,這個結構體對應一個安裝點。


清單5. 和檔案系統相關
struct file_system_type {
        const char *name;                /*檔案系統的名字*/
        struct subsystem subsys;         /*sysfs子系統物件*/
        int fs_flags;                    /*檔案系統型別標誌*/
    /*在檔案系統被安裝時,從磁碟中讀取超級塊,在記憶體中組裝超級塊物件*/
    struct super_block *(*get_sb) (struct file_system_type*, 
                                    int, const char*, void *);
    
    void (*kill_sb) (struct super_block *);  /*終止訪問超級塊*/            
    struct module *owner;                    /*檔案系統模組*/
    struct file_system_type * next;          /*連結串列中的下一個檔案系統型別*/
    struct list_head fs_supers;              /*具有同一種檔案系統型別的超級塊物件連結串列*/

};

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; /子檔案系統連結串列/
atomic_t mnt_count; /使用計數/
int mnt_flags; /安裝標誌/
char *mnt_devname; /裝置檔名/
struct list_head mnt_list; /描述符連結串列/
struct list_head mnt_fslink; /具體檔案系統的到期列表/
struct namespace *mnt_namespace; /相關的名字空間/
};


2.2.5.2 和程序相關


清單6. 開啟的檔案集
struct files_struct {//開啟的檔案集
         atomic_t count;              /*結構的使用計數*/
        ……
         int max_fds;                 /*檔案物件數的上限*/
        int max_fdset;               /*檔案描述符的上限*/
        int next_fd;                 /*下一個檔案描述符*/
        struct file ** fd;           /*全部檔案物件陣列*/
        ……
 };

struct fs_struct {//建立程序與檔案系統的關係
atomic_t count; /結構的使用計數/
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;      /*安裝點的資料*/
        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;   /*專用資料*/
};

2.2.6 物件間的聯絡

如上的資料結構並不是孤立存在的。正是通過它們的有機聯絡,VFS才能正常工作。如下的幾張圖是對它們之間的聯絡的描述。

如圖5所示,被Linux支援的檔案系統,都有且僅有一個file_system_type結構而不管它有零個或多個例項被安裝到系統 中。每安裝一個檔案系統,就對應有一個超級塊和安裝點。超級塊通過它的一個域s_type指向其對應的具體的檔案系統型別。具體的 檔案系統通過file_system_type中的一個域fs_supers連結具有同一種檔案型別的超級塊。同一種檔案系統型別的超級塊通過域s_instances鏈 接。


圖5. 超級塊、安裝點和具體的檔案系統的關係
圖片示例_超級塊,安裝點和具體的檔案系統的關係 

從圖6可知:程序通過task_struct中的一個域files_struct files來了解它當前所開啟的檔案物件;而我們通常所說的檔案 描述符其實是程序開啟的檔案物件陣列的索引值。檔案物件通過域f_dentry找到它對應的dentry物件,再由dentry物件的域d_inode找 到它對應的索引結點,這樣就建立了檔案物件與實際的物理檔案的關聯。最後,還有一點很重要的是, 檔案物件所對應的檔案操作函式 列表是通過索引結點的域i_fop得到的。圖6對第三部分原始碼的理解起到很大的作用。


圖6. 程序與超級塊、檔案、索引結點、目錄項的關係
圖片示例_程序與超級塊,檔案,索引結點,目錄項的關係 



回頁首


3 基於VFS的檔案I/O

到目前為止,文章主要都是從理論上來講述VFS的執行機制;接下來我們將深入原始碼層中,通過闡述兩個具有代表性的系統 呼叫sys_open()和sys_read()來更好地理解VFS向具體檔案系統提供的介面機制。由於本文更關注的是檔案操作的整個流程體制,所以我 們在追蹤原始碼時,對一些細節性的處理不予關心。又由於篇幅所限,只列出相關程式碼。本文中的原始碼來自於linux-2.6.17核心版本。

在深入sys_open()和sys_read()之前,我們先概覽下呼叫sys_read()的上下文。圖7描述了從使用者空間的read()呼叫到資料從 磁碟讀出的整個流程。當在使用者應用程式呼叫檔案I/O read()操作時,系統呼叫sys_read()被激發,sys_read()找到檔案所在的具體檔案 系統,把控制權傳給該檔案系統,最後由具體檔案系統與物理介質互動,從介質中讀出資料。


圖7. 從物理介質讀資料的過程
圖片示例_從物理介質讀資料的過程 

3.1 sys_open()

sys_open()系統呼叫開啟或建立一個檔案,成功返回該檔案的檔案描述符。圖8是sys_open()實現程式碼中主要的函式呼叫關係圖。


圖8. sys_open函式呼叫關係圖
圖片示例_sys_open函式呼叫關係圖 

由於sys_open()的程式碼量大,函式呼叫關係複雜,以下主要是對該函式做整體的解析;而對其中的一些關鍵點,則列出其關鍵程式碼。

a. 從sys_open()的函式呼叫關係圖可以看到,sys_open()在做了一些簡單的引數檢驗後,就把接力棒傳給do_sys_open():

1)、首先,get_unused_fd()得到一個可用的檔案描述符;通過該函式,可知檔案描述符實質是程序開啟檔案列表中對應某個檔案物件的索引值;

2)、接著,do_filp_open()開啟檔案,返回一個file物件,代表由該程序開啟的一個檔案;程序通過這樣的一個數據結構對物理檔案進行讀寫操作。

3)、最後,fd_install()建立檔案描述符與file物件的聯絡,以後程序對檔案的讀寫都是通過操縱該檔案描述符而進行。

b. do_filp_open()用於開啟檔案,返回一個file物件;而開啟之前需要先找到該檔案:

1)、open_namei()用於根據檔案路徑名查詢檔案,藉助一個持有路徑資訊的資料結構nameidata而進行;

2)、查詢結束後將填充有路徑資訊的nameidata返回給接下來的函式nameidata_to_filp()從而得到最終的file物件;當達到目的後,nameidata這個資料結構將會馬上被釋放。

c.open_namei()用於查詢一個檔案:

1)、path_lookup_open()實現檔案的查詢功能;要開啟的檔案若不存在,還需要有一個新建的過程,則呼叫 path_lookup_create(),後者和前者封裝的是同一個實際的路徑查詢函式,只是引數不一樣,使它們在處理細節上有所偏差;

2)、當是以新建檔案的方式開啟檔案時,即設定了O_CREAT標識時需要建立一個新的索引節點,代表建立一個檔案。在vfs_create()裡的一句 核心語句dir->i_op->create(dir, dentry, mode, nd)可知它呼叫了具體的檔案系統所提供的建立索引節點的方法。注意:這邊的索引節點的概念,還只是位於記憶體之中,它和磁碟上的物理的索引節點的關係就像 位於記憶體中和位於磁碟中的檔案一樣。此時新建的索引節點還不能完全標誌一個物理檔案的成功建立,只有當把索引節點回寫到磁碟上才是一個物理檔案的真正創 建。想想我們以新建的方式開啟一個檔案,對其讀寫但最終沒有儲存而關閉,則位於記憶體中的索引節點會經歷從新建到消失的過程,而磁碟卻始終不知道有人曾經想 過建立一個檔案,這是因為索引節點沒有回寫的緣故。

3)、path_to_nameidata()填充nameidata資料結構;

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()最終都是呼叫 __path_lookup_intent_open()來實現查詢檔案的功能。 查詢時,在遍歷路徑的過程中,會逐層地將各個路徑組成部分解析成目錄項物件,如果此目錄項物件在目錄項快取中,則直接從快取中獲得;如果該目錄項在快取中 不存在,則進行一次實際的讀盤操作,從磁碟中讀取該目錄項所對應的索引節點。得到索引節點後,則建立索引節點與該目錄項的聯絡。如此迴圈,直到最終找到目 標檔案對應的目錄項,也就找到了索引節點,而由索引節點找到對應的超級塊物件就可知道該檔案所在的檔案系統的型別。 從磁碟中讀取該目錄項所對應的索引節點;這將引發VFS和實際的檔案系統的一次互動。從前面的VFS理論介紹可知,讀索引節點方法是由超級塊來提供的。而 當安裝一個實際的檔案系統時,在記憶體中建立的超級塊的資訊是由一個實際檔案系統的相關資訊來填充的,這裡的相關資訊就包括了實際檔案系統所定義的超級塊的 操作函式列表,當然也就包括了讀索引節點的具體執行方式。 當繼續追蹤一個實際檔案系統ext3的ext3_read_inode()時,可發現這個函式很重要的一個工作就是為不同的檔案型別設定不同的索引節點操 作函式表和檔案操作函式表。


清單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子函式:__dentry_open

這是VFS與實際的檔案系統聯絡的一個關鍵點。從3.1.1小節分析中可知,呼叫實際檔案系統讀取索引節點的方法讀取索引節點時,實際檔案系統會根據檔案 的不同型別賦予索引節點不同的檔案操作函式集,如普通檔案有普通檔案對應的一套操作函式,裝置檔案有裝置檔案對應的一套操作函式。這樣當把對應的索引節點 的檔案操作函式集賦予檔案物件,以後對該檔案進行操作時,比如讀操作,VFS雖然對各種不同檔案都是執行同一個read()操作介面,但是真正讀時,核心 卻知道怎麼區分對待不同的檔案型別。


清單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;
        ……
        //整個函式的工作在於填充一個file物件
        ……
         f->f_mapping = inode->i_mapping;  
        f->f_dentry = dentry;
        f->f_vfsmnt = mnt;
        f->f_pos = 0;  
        //將對應的索引節點的檔案操作函式集賦予檔案物件的操作列表
        <span class="boldcode">f->f_op = fops_get(inode->i_fop); </span>
        ……  
        //若檔案自己定義了open操作,則執行這個特定的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()系統呼叫用於從已開啟的檔案讀取資料。如read成功,則返回讀到的位元組數。如已到達檔案的尾端,則返回0。圖9是sys_read()實現程式碼中的函式呼叫關係圖。


圖9. sys_read函式呼叫關係圖
圖片示例_sys_read函式呼叫關係圖 

對檔案進行讀操作時,需要先開啟它。從3.1小結可知,開啟一個檔案時,會在記憶體組裝一個檔案物件,希望對該檔案執行的操作方法已在檔案物件設定好。所以 對檔案進行讀操作時,VFS在做了一些簡單的轉換後(由檔案描述符得到其對應的檔案物件;其核心思想是返回 current->files->fd[fd]所指向的檔案物件),就可以通過語句 file->f_op->read(file, buf, count, pos)輕鬆呼叫實際檔案系統的相應方法對檔案進行讀操作了。




回頁首


4 解決問題

4.1 跨檔案系統的檔案操作的基本原理

到此,我們也就能夠解釋在Linux中為什麼能夠跨檔案系統地操作檔案了。舉個例子,將vfat格式的磁碟上的一個檔案a.txt拷貝到ext3格式的磁 盤上,命名為b.txt。這包含兩個過程,對a.txt進行讀操作,對b.txt進行寫操作。讀寫操作前,需要先開啟檔案。由前面的分析可知,開啟檔案 時,VFS會知道該檔案對應的檔案系統格式,以後操作該檔案時,VFS會呼叫其對應的實際檔案系統的操作方法。所以,VFS呼叫vfat的讀檔案方法將 a.txt的資料讀入記憶體;在將a.txt在記憶體中的資料對映到b.txt對應的記憶體空間後,VFS呼叫ext3的寫檔案方法將b.txt寫入磁碟;從而 實現了最終的跨檔案系統的複製操作。

4.2“一切皆是檔案”的實現根本

不論是普通的檔案,還是特殊的目錄、裝置等,VFS都將它們同等看待成檔案,通過同一套檔案操作介面來對它們進行操作。操作檔案時需先開啟;開啟檔案 時,VFS會知道該檔案對應的檔案系統格式;當VFS把控制權傳給實際的檔案系統時,實際的檔案系統再做出具體區分,對不同的檔案型別執行不同的操作。這 也就是“一切皆是檔案”的根本所在。




回頁首


5 總結

VFS即虛擬檔案系統是Linux檔案系統中的一個抽象軟體層;因為它的支援,眾多不同的實際檔案系統才能在Linux中共存,跨檔案系統操作才能實現。 VFS藉助它四個主要的資料結構即超級塊、索引節點、目錄項和檔案物件以及一些輔助的資料結構,向Linux中不管是普通的檔案還是目錄、裝置、套接字等 都提供同樣的操作介面,如開啟、讀寫、關閉等。只有當把控制權傳給實際的檔案系統時,實際的檔案系統才會做出區分,對不同的檔案型別執行不同的操作。由此 可見,正是有了VFS的存在,跨檔案系統操作才能執行,Unix/Linux中的“一切皆是檔案”的口號才能夠得以實現。