1。萬物皆檔案

Everything is a file。

用過Linux的朋友一定聽過類似的話。雖然有一些例外,但在Linux上大部分資源確實都是檔案,而且都是透過VFS來訪問的。

比如儲存資料的檔案,可以執行的二進位制程式,層次化的目錄結構等等。這些檔案都是使用

基於磁碟硬體的檔案系統

(比如ext2/3/4,xfs等)來管理的,就是說它們都是儲存在真實磁碟裝置上的。在Linux中大部分檔案都是這種型別的。

還有一部分檔案是虛擬檔案,並不儲存在真實的磁碟硬體裝置上,而是使用

虛擬檔案系統

(比如sys,proc,cgroup等)來管理。這些虛機檔案系統是核心執行時生成的,從而提供了從使用者態透過VFS來和核心態通訊的方式。

舉個例子,使用stat命令可以看到檔案/proc/cpuinfo的長度為0。

VFS(一) 虛擬檔案系統概述

讀取/proc/cpuinfo的時候,可以讀到核心的關於cpu的資訊,這些資訊都是核心生成的。

VFS(一) 虛擬檔案系統概述

2。檔案型別

Linux下一共有七種檔案型別,比較常用的檔案型別是普通檔案,目錄,軟鏈等。

使用ls -l命令來區分不同的檔案型別,輸出的第一個字元代表檔案型別。

-:普通檔案

d:目錄

c:字元裝置

b:塊裝置

s:套接字

p:管道

l:軟鏈

一個Linux系統裡可能有上百萬個上述不同型別的檔案。

不同型別的檔案底層解釋方式並不相同。

比如普通檔案和套接字的讀寫邏輯就不一樣。

而相同型別的檔案的底層解釋方式也有可能不同。

比如都是普通檔案,/var/log/message和/proc/1/cmdline的讀寫邏輯也不一樣,等等。

因為這些檔案都使用不同的檔案系統例項來管理。比如/var/log/message可能是由ext4來管理的,而/proc/1/cmdline是由procfs管理,而socket是由sockfs來管理。

所以不同檔案的解釋權在於特定的檔案系統例項。

3.VFS虛擬檔案系統

Linux支援各種各樣的檔案系統,在核心原始碼的fs目錄下,可以找到很多常用的檔案系統實現,比如ext4,xfs,sysfs等。

VFS(一) 虛擬檔案系統概述

為了支援各種各樣檔案系統,Linux在使用者程序和檔案系統例項中間引入了一個抽象層,對不同檔案系統的訪問都使用相同的方法,並提供了檔案的統一檢視。

不同的檔案系統的底層實現方式可能有很大的差異,但VFS並不關心這些。透過提供公共元件和統一框架,VFS對上層系統呼叫遮蔽了具體檔案系統實現之間的差異性,為所有檔案的訪問提供了相同的API,並遵循相同的呼叫語義。

VFS(Virtual FileSystem Switch)抽象層。

VFS(一) 虛擬檔案系統概述

4。VFS資料結構

在操作檔案的時候,在使用者態看到的是檔案描述符fd(一個整數)。

程序內部使用open函式開啟一個檔案,並返回fd。之後透過read/write/close/ioctl等函式來對fd進行操作。

每使用open開啟一個檔案,就會分配出一個新的fd來標識該檔案。fd只在當前程序內有效。

在核心態操作操作檔案要複雜的多,具體的實現會涉及數量龐大的彼此關聯的資料結構,以下是一些比較重要的資料結構:

struct file

對應到程序內開啟的檔案,file儲存在程序的fdtable的陣列中,使用fd作為索引。

每個檔案關聯一個file_operation,包含檔案讀寫,記憶體對映,設定檔案位置等函式指標。使用者態的read/write等對檔案進行的函式呼叫最終都會執行到這裡。

struct inode

檔案索引節點,用來儲存檔案的元資訊。每個檔案唯一的對應到一個inode。

每個inode有一個分割槽內唯一編號,可以使用ls -i來檢視。

每個inode關聯一個inode_operation,包含相關元資料建立,刪除,查詢,修改屬性等函式指標。

struct dentry

目錄項快取,用來加速檔案查詢結果,建立檔名和inode的對映關係。

dentry的例項之間會形成一個小型的類似檔案系統的拓撲結構。dentry目錄下的檔案和子目錄都會進入d_subdir連結串列,d_parent指向父dentry。

每個dentry可能關聯一個dentry_operation,包含雜湊,比較檔名等函式指標。

struct super_block

超級塊控制結構。裝載檔案系統時,藉助檔案系統例項對應的fill_super函式生成。該結構儲存了檔案系統的元資訊。

每個超級塊關聯一個super_block_operation,實現特定檔案系統例項超級塊的操作函式。

struct address_space

地址空間結構。每個inode都有一個地址空間。該結構用來建立快取資料和後備儲存器資料之間的對映關係。快取的結構是記憶體頁(page cache),快取的資料就是檔案內容。

struct file_system_type

檔案系統型別。每個檔案系統都有一個型別,使用register_filesystem註冊到核心中。其中比較重要的是mount函式指標,用來掛載特定的檔案系統例項。

對於上述資料結構中的_operation結構,都是VFS中抽象出來的通用的函式指標,用來將通用的框架和各種各樣的檔案系統例項的實現進行解耦。各個檔案系統的實現透過專用的operation結構和VFS繫結起來。

比如ext4中檔案的file_operation為ext4_file_operations,目錄的file_operation為ext4_dir_operations,inode的inode_operation為ext4_file_inode_operations。

下圖給出這些資料結構彼此之間關係的一個簡化版本:

VFS(一) 虛擬檔案系統概述

5。VFS開啟檔案

5。1。查詢檔案

在對檔案進行讀寫之前,需要先使用open函式開啟這個檔案。開啟檔案需要先對檔案進行查詢定位。

比如最常用的開啟bash,就要開啟bash檔案,路徑位於/usr/bin/bash。檔案系統的組織是樹狀目錄,所以

查詢的第一步是始於根目錄/。

根目錄/對應的inode是已知的。檔案系統掛載時,在構建超級塊中會載入根目錄/對應的inode(ext4中根目錄對應的inode號是2)和dentry。

根目錄/(2號inode)中的資料是具體的目錄項,每一項都包含一個子項(子目錄或者檔案)的名字,和子項關聯的inode號。

查詢的第二步是找到子目錄usr對應的inode號。

在根目錄/(2號inode)中掃描名字為usr的目錄項,找到usr對應的inode(5505025號inode)。

然後重複第二步,直到找到bash檔案對應的inode(5505117號inode),最後載入對應的檔案內容。

VFS(一) 虛擬檔案系統概述

VFS(一) 虛擬檔案系統概述

但這樣的查詢比較慢,實際上在VFS中會使用dentry_hashtable來加速查詢過程。

5。2。開啟檔案

應用程式使用標準庫的open函式來開啟一個檔案。

在x86_64架構上open函式會執行syscall指令從使用者態轉換到核心態,並最終呼叫到do_sys_open函式。

do_sys_open的流程:

VFS(一) 虛擬檔案系統概述

do_filp_open的流程:

VFS(一) 虛擬檔案系統概述