linux的虛擬文件系統

來源:fireaxe ( [email protected])
鏈接:http://blog.chinaunix.net/uid-20528014-id-4094714.html


看到一篇文章講的很好,至少對於我而言,我對vfs有了一個初步的認識,所以將這篇文章裝載到博客裏,分享一下.
文章原文如下:


VFS是Linux非常核心的一個概念,linux下的大部分操作都要用到VFS的相關功能。這裏從使用者的角度,對VFS進行了簡單說明。使用者不但需要知道Linux下有哪些文件操作的函數,還需要對VFS的結構有一個比較清晰的瞭解,才能更好的使用它。例如hard link 與symbolic,如果沒有VFS結構的相瞭解,就無法搞清楚如何使用它們。

本文首先是建立了一個簡單的目錄模型,然後介紹該目錄在VFS的結構,最終總結出如何使用各個文件操作函數。

本着簡單使用的原則,主要使用了分析加猜測的方法。鑑於本人水平有限,文中不免會有些錯誤。歡迎各位讀者理性閱讀,大膽批判。您的批判是我進步的動力。

目錄

1 目錄模型
2 VFS的概念
3 VFS的構建
4 VFS的結構
5 Dentry cache
6 無denty時定位文件
7 有dentry時定位文件
8 Symbolic link
9 hard link
10 進程對文件的管理
11 open的過程
12 open與dup
13 Fork對打開文件的影響
14 文件操作函數解析
1 目錄模型

以下面的目錄爲例。

dir爲第一級目錄,dir中有subdir0與subdir1兩個子目錄與一個文件file0。“subdir0”中有兩個文件file1與file0。subdir1中有一個文件file3。

2 VFS的概念

VFS是Linux中的一個虛擬文件文件系統,也稱爲虛擬文件系統交換層(Virtual Filesystem Switch)。它爲應用程序員提供一層抽象,屏蔽底層各種文件系統的差異。如下圖所示:

不同的文件系統,如Ext2/3、XFS、FAT32等,具有不同的結構,假如用戶調用open等文件IO函數去打開文件,具體的實現會非常不同。爲了屏蔽這種差異,Linux引入了VFS的概念。相當於是Linux自建了一個新的貯存在內存中的文件系統。所有其他文件系統都需要先轉換成VFS的結構才能爲用戶所調用。

3 VFS的構建

所謂VFS的構建就是加載實際文件系統的過程,也就是mount被調用的過程。如下圖所示,以mount一個ext2的文件系統爲例。

這是一個經過簡化的Ext2磁盤結構,只是用於說明用它構建VFS的基本過程。
mount命令的一般形式爲:mount /dev/sdb1 /mnt/mysdb1
/dev/sdb1是設備名,/mnt/mysdb1是掛載點。

VFS文件系統的基本結構是dentry結構體與inode結構體。

Dentry代表一個文件目錄中的一個點,可以是目錄也可以是文件。

Inode代表一個在磁盤上的文件,它與磁盤文件一一對應。

Inode與dentry不一定一一對應,一個inode可能會對應多個dentry項。(hard link)Mount時,linux首先找到磁盤分區的super block,然後通過解析磁盤的inode table與file data,構建出自己的dentry列表與indoe列表。

需要注意的是,VFS實際上是按照Ext的方式進行構建的,所以兩者非常相似(畢竟Ext是Linux的原生文件系統)。

比如inode節點,Ext與VFS中都把文件管理結構稱爲inode,但實際上它們是不一樣的。Ext的inode節點在磁盤上;VFS的inode節點在內存裏。Ext-inode中的一些成員變量其實是沒有用的,如引用計數等。保留它們的目的是爲了與vfs-node保持一致。這樣在用ext-inode節點構造vfs-inode節點時,就不需要一個一個賦值,只需一次內存拷貝即可。

如果是非EXT格式的磁盤,就沒有這麼幸運了,所以mount非EXT磁盤會慢一些。

4 VFS的結構

構建出VFS文件系統後,下一步是把第一節中提到的目錄模型映射到VFS結構體系中。

上文提到了VFS主要由denty與inode構成。Dentry用於維護VFS的目錄結構,每個dentry項就代表着我們用ls時看的的一項(每個目錄和每個文件都對應着一個dentry項)。Inode爲文件節點,它與文件一一對應。Linux中,目錄也是一種文件,所以dentry也會對應一個inode節點。

下圖是第一節中的目錄模型在VFS中的結構。

5 Dentry cache

每個文件都要對應一個inode節點與至少一個dentry項。假設我們有一個100G的硬盤,上面寫滿了空文件,那個需要多少內存才能重建VFS呢?

文件最少要佔用1個block(一般是4K)。假一個dentry與一個inode需要100byte,則dentry與inode需要佔用1/40的空間。1G硬盤則需要2.5G空間。最近都開始換裝1T硬盤了,需要 25G的內存才能放下inode與dentry,相信沒有幾臺電腦可以承受。

爲了避免資源浪費,VFS採用了dentry cache的設計。

當有用戶用ls命令查看某一個目錄或用open命令打開一個文件時,VFS會爲這裏用的每個目錄項與文件建立dentry項與inode,即“按需創建”。然後維護一個LRU(Least Recently Used)列表,當Linux認爲VFS佔用太多資源時,VFS會釋放掉長時間沒有被使用的dentry項與inode項。

需要注意的是:這裏的建立於釋放是從內存佔用的角度看。從Linux角度看,dentry與inode是VFS中固有的東西。所不同的只是VFS是否把dentry與inode讀到了內存中。對於Ext2/3文件系統,構建dentry與inode的過程非常簡單,但對於其他文件系統,則會慢得多。

瞭解了Dentry cache的概念,才能明白爲何下面會有兩種定位文件的方式。

6 無denty時定位文件

因爲上面提到的Denty Cache,VFS並不能保證隨時都有dentry項與inode項可用。下面是無dentry項與inode項時的定位方式。

爲了簡化問題,這裏假設已經找到了dir的dentry項(找到dentry的過程會在後面講解)。

首先,通過dir對應的dentry0找到inode0節點,有了inode節點就可以讀取目錄中的信息。其中包含了該目錄包含的下一級目錄與文件文件列表,包括name與inode號。實際上用ls命令查看的就是這些信息。“ls -i”會顯示出文件的inode號。

ls -i
975248 subdir0 975247 subdir1 975251 file0

接着,根據file1對應的inode號重建inode4,並通過文件數據與inode4重建file1的dentry節點。然後,根據通過根據subdir0對應的inode號重建inode2,並通過文件數據(目錄也是文件)與inode2重建subdir0的dentry節點:dentry1。

ls -i

975311 file1 975312 file2

最後,就可以通過inode4節點訪問文件了。

注意:文件對應的inode號是確定的,只是inode結構體需要重新構造。

7 有dentry時定位文件

一旦在Dentry cache中建立了dentry項,下次訪問就很方便了。

Dentry中的一個關鍵變量是d_subdirs,它保存了下一級目錄的列表,用於快速定位文件。

首先,在代表dir目錄的dentry0的d_subdirs中查找名字爲“subdir0”的dentry項,找到了dentry1。

然後在dentry1中查找名字爲“file1”的dentry項,然後找到了file1對應的dentry項,

最後通過file1對應的dentry項獲得file1對應的inode4。

與無dentry項時比較,有dentry項時的操作精簡了許多。

8 Symbolic link

建立symboliclink的命令爲:ln -s 源文件目標文件

Linux中的symbolic link類似於Windows系統中的快捷方式。如下圖所示,symlink1是指向file1的symbolic link。symlink1本身也是文件,因此有自己獨立的inode節點。symlink中實際存儲的是源文件的相對路徑。

大部分文件操作會直接對symbolic link指向的目標進行操作,比如open(“symlikn1”),實際上打開的是file3。

如果file3不在會發生什麼事情呢?open函數照樣會按照symlink1中的文件路徑打開文件。但file3不存在,因此會報錯說文件不存在。

9 hard link

Linux除了symbolic link,還有hard link的概念。

Hard link建立實際上是dentry項的一個拷貝,它們都指向同一個inode節點。當我們使用write改寫file1的內容時,hardlink1的內容也會被改寫,因爲所以實際上它們是同一個文件。

如下圖所示,hardlink1是file1的一個hard link。它們都指向同一個inode1節點。Inode1中有一個計數器,用於記錄有幾個dentry項指向它。刪除任意一個dentry項都不會導致inode1的刪除。只有所有指向inode1的dentry都被刪除了,inode1纔會被刪除。

他們實際從某種意義上講,所有dentry項都是hard link。

10 進程對文件的管理

進程控制塊task_struct中有兩個變量與文件有關:fs與files。

files中存儲着root與pwd兩個指向dentry項的指針。用戶定路徑時,絕對路徑會通過root進行定位;相對路徑會的通過pwd進行定位。(一個進程的root不一定是文件系統的根目錄。比如ftp進程的根目錄不是文件系統的根目錄,這樣才能保證用戶只能訪問ftp目錄下的內容)

fs是一個file object列表,其中每一個節點對應着一個被打開了的文件。當進程定位到文件時,會構造一個file object,並通過f_inode 關聯到inode節點。文件關閉時(close),進程會釋放對應對應file object。File object中的f_mode是打開時選擇的權限,f_pos爲讀寫位置。當打開同一個文件多次時,每次都會構造一個新的file object。每個file object中有獨立的f_mode與f_pos。

11 open的過程

打開文件涉及到裏一系列的結構調整,這裏分步驟進行說明:

首先建立一個文件管理結構,如下圖所示,該進程已經打開了兩個文件,接下來我們再打開一個新文件。

第一步:找到文件;

從上文中能定位到我們文件的inode節點,找到了inode節點也就找到了文件。

第二步:建立file object;

建立一個新的file object對象,放入file object對象列表,並把它指向inode節點。

第三步:建立file descriptor

file descriptor就是進程控制塊task_struct中files中維護的fd_array。因爲是數組,所以file descriptor實際上已經預先分配好空間了,這裏這是需要把某個空閒的file descriptor與file object關聯起來。這個file descriptor在數組中的索引號就是open文件時得到的文件fd。

12 open與dup

同一個文件是可以open多次的,結構如下圖所示。每次open都會建立一個新的file descriptor與file object。然後指向同一個文件的inode節點。下圖中,假設open的文件與fd1指向的是同一個文件,則新創建的file object 2與fd1的file object 2會指向同一個inode2節點。

Linux還提供了dup功能,用於複製file descriptor。使用dup不會建立新的非file object,所以新建立的file descriptor會與原filedescriptor同時指向同一個file object。下圖中,我們通過dup(fd1)得到了fd2,則fd2與fd1指向了同一個file object2。

兩次open後由於會生成新的object,所以文件讀寫屬性、文件讀寫位置(f_pos)等信息都是獨立的。使用dup複製file descriptor後,由於沒有獨立的object,所以修改某個fd的屬性或文件讀寫位置後,另一個fd也會隨之變化。

13 Fork對打開文件的影響

Dup的操作與fork一個子進程時的操作類似。

下圖是已有父進程的文件結構:

使用fork後的結構如下。同樣是沒有創建新的file object,因此當對parent process中的fd1進行文件指針的移動時(如讀寫),child process中的fd1也會受影響。也即是說opened files list不是進程的一部分,因此不會被複制。

Opened files list應該是一個全局性的資源鏈表,進程維護的是一個指針列表fd table,所以被複制的只是指針列表fd table,而不是opened files list。

14 文件操作函數解析

通過上面的分析,可以對各個函數的作用域與使用方式有更清晰的瞭解。下面列出了常用的文件操作:

函數名 作用對象 說明
creat dentry, inode 創建文件時會創建新的dentry與inode
open file object 如果文件不存在,且有O_CREAT參數,則會先調用creat
close file object 刪除file object,但不會刪除文件。
state/lstate inode 讀取inode的內容。如果目標是symbolic link,stat會讀取symbolic link指向的內容;lstat則會讀取symbolic link文件本身。
chmod file object 改變file object中的f_mode
chown/lchown file object 改變file object中的f_uid與f_gid
truncate inode 改變文件長度。
read file object 讀文件會改變file object中的f_pos
write file object,inode 寫文件改變file object中的f_pos的同時也會改變文件內容與更新修改時間。
dup file object 建立一個新的file descriptor,指向同一個file object項
seek/lseek file object 改變file object中的f_pos
link dentry 創建新的dentry項,指向同一個inode節點。
unlink dentry 刪除一個dentry項。如果該dentry指向的inode節點沒有被其他dentry項使用,則刪除inode節點與磁盤文件。
rename dentry 修改dentry相中的d_name
readlink ———– read無法讀取symbolic link 文件的內容,需要使用readlink讀取
symlink dentry, inode 作用與creat類似,但創建的文件屬性爲symbolic link。

注:磁盤文件與inode節點一一對應,所以在表中不再單獨列出磁盤文件。

參考文件:

Advanced Programming in the UNIX Environment (3rd) W. Richard Stevens & Stephen A. Rago
Understanding the Linux Kernel (3rd) Daniel P. Bovet & Marco Cesati

本文乃fireaxe原創,使用GPL發佈,可以自由拷貝,轉載。但轉載請保持文檔的完整性,並註明原作者及原鏈接。內容可任意使用,但對因使用該內容引起的後果不做任何保證。
作者:[email protected]
博客:fireaxe.blog.chinaunix.net

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章