1. 虛擬文件系統概述

1.1 VFS簡介

虛擬文件系統(Virtual File System,簡稱VFS)是Linux內核的子系統之一,它為用戶程序提供文件和文件系統操作的統一介面,屏蔽不同文件系統的差異和操作細節。藉助VFS可以直接使用open()read()write()這樣的系統調用操作文件,而無須考慮具體的文件系統和實際的存儲介質。

舉個例子,Linux用戶程序可以通過read() 來讀取ext3NFSXFS等文件系統的文件,也可以讀取存儲在SSDHDD等不同存儲介質的文件,無須考慮不同文件系統或者不同存儲介質的差異。

通過VFS系統,Linux提供了通用的系統調用,可以跨越不同文件系統和介質之間執行,極大簡化了用戶訪問不同文件系統的過程。另一方面,新的文件系統、新類型的存儲介質,可以無須編譯的情況下,動態載入到Linux中。

"一切皆文件"是Linux的基本哲學之一,不僅是普通的文件,包括目錄、字元設備、塊設備、套接字等,都可以以文件的方式被對待。實現這一行為的基礎,正是Linux的虛擬文件系統機制。

1.2 VFS原理

VFS之所以能夠銜接各種各樣的文件系統,是因為它抽象了一個通用的文件系統模型,定義了通用文件系統都支持的、概念上的介面。新的文件系統只要支持並實現這些介面,並註冊到Linux內核中,即可安裝和使用。

舉個例子,比如Linux寫一個文件:

int ret = write(fd, buf, len);

調用了write()系統調用,它的過程簡要如下:

  • 首先,勾起VFS通用系統調用sys_write()處理。
  • 接著,sys_write()根據fd找到所在的文件系統提供的寫操作函數,比如op_write()
  • 最後,調用op_write()實際的把數據寫入到文件中。

操作示意圖如下:

2. 虛擬文件系統組成部分

Linux為了實現這種VFS系統,採用面向對象的設計思路,主要抽象了四種對象類型:

  • 超級塊對象:代表一個已安裝的文件系統。
  • 索引節點對象:代表具體的文件。
  • 目錄項對象:代表一個目錄項,是文件路徑的一個組成部分。
  • 文件對象:代表進程打開的文件。

每個對象都包含一組操作方法,用於操作相應的文件系統。

備註:Linux將目錄當做文件對象來處理,是另一種形式的文件,它裡面包含了一個或多個目錄項。而目錄項是單獨抽象的對象,主要包括文件名和索引節點號。因為目錄是可以層層嵌套,以形成文件路徑,而路徑中的每一部分,其實就是目錄項。

接下來介紹一下各個對象的作用以及相關操作。

2.1 超級塊

超級塊用於存儲文件系統的元信息,由super_block結構體表示,定義在<linux/fs.h>中,元信息裡面包含文件系統的基本屬性信息,比如有:

  • 索引節點信息
  • 掛載的標誌
  • 操作方法 s_op
  • 安裝許可權
  • 文件系統類型、大小、區塊數
  • 等等等等

其中操作方法 s_op 對每個文件系統來說,是非常重要的,它指向該超級塊的操作函數表,包含一系列操作方法的實現,這些方法有:

  • 分配inode
  • 銷毀inode
  • 讀、寫inode
  • 文件同步
  • 等等

當VFS需要對超級塊進行操作時,首先要在超級塊的操作方法 s_op 中,找到對應的操作方法後再執行。比如文件系統要寫自己的超級塊:

superblock->s_op->write_supper(sb);

創建文件系統時,其實就是往存儲介質的特定位置,寫入超級塊信息;而卸載文件系統時,由VFS調用釋放超級塊。

Linux支持眾多不同的文件系統,file_system_type結構體用於描述每種文件系統的功能和行為,包括:

  • 名稱、類型等
  • 超級塊對象鏈表

當向內核註冊新的文件系統時,其實是將file_system_type對象實例化,然後加入到Linux的根文件系統的目錄樹結構上。

2.2 索引

索引節點對象包含Linux內核在操作文件、目錄時,所需要的全部信息,這些信息由inode結構體來描述,定義在<linux/fs.h>中,主要包含:

  • 超級塊相關信息
  • 目錄相關信息
  • 文件大小、訪問時間、許可權相關信息
  • 引用計數
  • 等等

一個索引節點inode代表文件系統中的一個文件,只有當文件被訪問時,才在內存中創建索引節點。與超級塊類似的是,索引節點對象也提供了許多操作介面,供VFS系統使用,這些介面包括:

  • create(): 創建新的索引節點(創建新的文件)
  • link(): 創建硬鏈接
  • symlink(): 創建符號鏈接。
  • mkdir(): 創建新的目錄。

等等,我們常規的文件操作,都能在索引節點中找到相應的操作介面。

2.3 目錄項

前面提到VFS把目錄當做文件對待,比如/usr/bin/vimusrbinvim都是文件,不過vim是一個普通文件,usrbin都是目錄文件,都是由索引節點對象標識。

由於VFS會經常的執行目錄相關的操作,比如切換到某個目錄、路徑名的查找等等,為了提高這個過程的效率,VFS引入了目錄項的概念。一個路徑的組成部分,不管是目錄還是普通文件,都是一個目錄項對象。/usrbinvim都對應一個目錄項對象。不過目錄項對象沒有對應的磁碟數據結構,是VFS在遍歷路徑的過程中,將它們逐個解析成目錄項對象。

目錄項由dentry結構體標識,定義在<linux/dcache.h>中,主要包含:

  • 父目錄項對象地址
  • 子目錄項鏈表
  • 目錄關聯的索引節點對象
  • 目錄項操作指針
  • 等等

目錄項有三種狀態:

  • 被使用:該目錄項指向一個有效的索引節點,並有一個或多個使用者,不能被丟棄。
  • 未被使用:也對應一個有效的索引節點,但VFS還未使用,被保留在緩存中。如果要回收內存的話,可以撤銷未使用的目錄項。
  • 負狀態:沒有對應有效的索引節點,因為索引節點被刪除了,或者路徑不正確,但是目錄項仍被保留了。

將整個文件系統的目錄結構解析成目錄項,是一件費力的工作,為了節省VFS操作目錄項的成本,內核會將目錄項緩存起來。

2.4 文件

文件對象是進程打開的文件在內存中的實例。Linux用戶程序可以通過open()系統調用來打開一個文件,通過close()系統調用來關閉一個文件。由於多個進程可以同時打開和操作同一個文件,所以同一個文件,在內存中也存在多個對應的文件對象,但對應的索引節點和目錄項是唯一的。

文件對象由file結構體表示,定義在<linux/fs.h>中,主要包含:

  • 文件操作方法
  • 文件對象的引用計數
  • 文件指針的偏移
  • 打開文件時的讀寫標識
  • 等等等等

類似於目錄項,文件對象也沒有實際的磁碟數據,只有當進程打開文件時,才會在內存中產生一個文件對象。

每個進程都有自己打開的一組文件,由file_struct結構體標識,該結構體由進程描述符中的files欄位指向。主要包括:

  • fdt
  • fd_array[NR_OPEN_DEFAULT]
  • 引用計數

fd_array數組指針指向已打開的文件對象,如果打開的文件對象個數 > NR_OPEN_DEFAULT,內核會分配一個新數組,並將 fdt 指向該數組。

除此之外,內核還為所有打開文件維持一張文件表,包括:

  • 文件狀態標誌
  • 文件偏移量

關於多進程打開同一文件以及文件共享更詳細的信息,可以閱讀《UNIX環境高級編程》第三章。

3. 總結

Linux支持了很多種類的文件系統,包含本地文件系統ext3ext4到網路文件系統NFSHDFS等,VFS系統屏蔽了不同文件系統的操作差異和實現細節,提供了統一的實現框架,也提供了標準的操作介面,這大大降低了操作文件和接入新文件系統的難度。

4. 參考

  • 深入理解Linux內核
  • Linux內核設計與實現

推薦閱讀:

相关文章