淺談Linux虛擬文件系統
1. 虛擬文件系統概述
1.1 VFS簡介
虛擬文件系統(Virtual File System,簡稱VFS)是Linux內核的子系統之一,它為用戶程序提供文件和文件系統操作的統一介面,屏蔽不同文件系統的差異和操作細節。藉助VFS可以直接使用open()
、read()
、write()
這樣的系統調用操作文件,而無須考慮具體的文件系統和實際的存儲介質。
舉個例子,Linux用戶程序可以通過read()
來讀取ext3
、NFS
、XFS
等文件系統的文件,也可以讀取存儲在SSD
、HDD
等不同存儲介質的文件,無須考慮不同文件系統或者不同存儲介質的差異。
通過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/vim
,usr
、bin
和vim
都是文件,不過vim
是一個普通文件,usr
和bin
都是目錄文件,都是由索引節點對象標識。
由於VFS會經常的執行目錄相關的操作,比如切換到某個目錄、路徑名的查找等等,為了提高這個過程的效率,VFS引入了目錄項的概念。一個路徑的組成部分,不管是目錄還是普通文件,都是一個目錄項對象。/
、usr
、bin
、vim
都對應一個目錄項對象。不過目錄項對象沒有對應的磁碟數據結構,是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支持了很多種類的文件系統,包含本地文件系統ext3
、ext4
到網路文件系統NFS
、HDFS
等,VFS系統屏蔽了不同文件系統的操作差異和實現細節,提供了統一的實現框架,也提供了標準的操作介面,這大大降低了操作文件和接入新文件系統的難度。
4. 參考
- 深入理解Linux內核
- Linux內核設計與實現
推薦閱讀: