VFS是Linux非常核心的一個概念,linux下的大部分操作都要用到VFS的相關(guān)功能。這里從使用者的角度,對VFS進行了簡單說明。使用者不但需要知道Linux下有哪些文件操作的函數(shù),還需要對VFS的結(jié)構(gòu)有一個比較清晰的了解,才能更好的使用它。例如hard link 與symbolic,如果沒有VFS結(jié)構(gòu)的相了解,就無法搞清楚如何使用它們。
本文首先是建立了一個簡單的目錄模型,然后介紹該目錄在VFS的結(jié)構(gòu),最終總結(jié)出如何使用各個文件操作函數(shù)。
本著簡單使用的原則,主要使用了分析加猜測的方法。鑒于本人水平有限,文中不免會有些錯誤。歡迎各位讀者理性閱讀,大膽批判。您的批判是我進步的動力。
目錄
1 目錄模型
2 VFS的概念
3 VFS的構(gòu)建
4 VFS的結(jié)構(gòu)
5 Dentry cache
6 無denty時定位文件
7 有dentry時定位文件
8 Symbolic link
9 hard link
10 進程對文件的管理
11 open的過程
12 open與dup
13 Fork對打開文件的影響
14 文件操作函數(shù)解析
1 目錄模型
以下面的目錄為例。
dir為第一級目錄,dir中有subdir0與subdir1兩個子目錄與一個文件file0。“subdir0”中有兩個文件file1與file0。subdir1中有一個文件file3。
2 VFS的概念
VFS是Linux中的一個虛擬文件文件系統(tǒng),也稱為虛擬文件系統(tǒng)交換層(Virtual Filesystem Switch)。它為應(yīng)用程序員提供一層抽象,屏蔽底層各種文件系統(tǒng)的差異。如下圖所示:
不同的文件系統(tǒng),如Ext2/3、XFS、FAT32等,具有不同的結(jié)構(gòu),假如用戶調(diào)用open等文件IO函數(shù)去打開文件,具體的實現(xiàn)會非常不同。為了屏蔽這種差異,Linux引入了VFS的概念。相當(dāng)于是Linux自建了一個新的貯存在內(nèi)存中的文件系統(tǒng)。所有其他文件系統(tǒng)都需要先轉(zhuǎn)換成VFS的結(jié)構(gòu)才能為用戶所調(diào)用。
3 VFS的構(gòu)建
所謂VFS的構(gòu)建就是加載實際文件系統(tǒng)的過程,也就是mount被調(diào)用的過程。如下圖所示,以mount一個ext2的文件系統(tǒng)為例。
這是一個經(jīng)過簡化的Ext2磁盤結(jié)構(gòu),只是用于說明用它構(gòu)建VFS的基本過程。
mount命令的一般形式為:mount /dev/sdb1 /mnt/mysdb1
/dev/sdb1是設(shè)備名,/mnt/mysdb1是掛載點。
VFS文件系統(tǒng)的基本結(jié)構(gòu)是dentry結(jié)構(gòu)體與inode結(jié)構(gòu)體。
Dentry代表一個文件目錄中的一個點,可以是目錄也可以是文件。
Inode代表一個在磁盤上的文件,它與磁盤文件一一對應(yīng)。
Inode與dentry不一定一一對應(yīng),一個inode可能會對應(yīng)多個dentry項。(hard link)
Mount時,linux首先找到磁盤分區(qū)的super block,然后通過解析磁盤的inode table與file data,構(gòu)建出自己的dentry列表與indoe列表。
需要注意的是,VFS實際上是按照Ext的方式進行構(gòu)建的,所以兩者非常相似(畢竟Ext是Linux的原生文件系統(tǒng))。
比如inode節(jié)點,Ext與VFS中都把文件管理結(jié)構(gòu)稱為inode,但實際上它們是不一樣的。Ext的inode節(jié)點在磁盤上;VFS的inode節(jié)點在內(nèi)存里。Ext-inode中的一些成員變量其實是沒有用的,如引用計數(shù)等。保留它們的目的是為了與vfs-node保持一致。這樣在用ext-inode節(jié)點構(gòu)造vfs-inode節(jié)點時,就不需要一個一個賦值,只需一次內(nèi)存拷貝即可。
如果是非EXT格式的磁盤,就沒有這么幸運了,所以mount非EXT磁盤會慢一些。
4 VFS的結(jié)構(gòu)
構(gòu)建出VFS文件系統(tǒng)后,下一步是把第一節(jié)中提到的目錄模型映射到VFS結(jié)構(gòu)體系中。
上文提到了VFS主要由denty與inode構(gòu)成。Dentry用于維護VFS的目錄結(jié)構(gòu),每個dentry項就代表著我們用ls時看的的一項(每個目錄和每個文件都對應(yīng)著一個dentry項)。Inode為文件節(jié)點,它與文件一一對應(yīng)。Linux中,目錄也是一種文件,所以dentry也會對應(yīng)一個inode節(jié)點。
下圖是第一節(jié)中的目錄模型在VFS中的結(jié)構(gòu)。
5 Dentry cache
每個文件都要對應(yīng)一個inode節(jié)點與至少一個dentry項。假設(shè)我們有一個100G的硬盤,上面寫滿了空文件,那個需要多少內(nèi)存才能重建VFS呢?
文件最少要占用1個block(一般是4K)。假一個dentry與一個inode需要100byte,則dentry與inode需要占用1/40的空間。1G硬盤則需要2.5G空間。最近都開始換裝1T硬盤了,需要 25G的內(nèi)存才能放下inode與dentry,相信沒有幾臺電腦可以承受。
為了避免資源浪費,VFS采用了dentry cache的設(shè)計。
當(dāng)有用戶用ls命令查看某一個目錄或用open命令打開一個文件時,VFS會為這里用的每個目錄項與文件建立dentry項與inode,即“按需創(chuàng)建”。然后維護一個LRU(Least Recently Used)列表,當(dāng)Linux認為VFS占用太多資源時,VFS會釋放掉長時間沒有被使用的dentry項與inode項。
需要注意的是:這里的建立于釋放是從內(nèi)存占用的角度看。從Linux角度看,dentry與inode是VFS中固有的東西。所不同的只是VFS是否把dentry與inode讀到了內(nèi)存中。對于Ext2/3文件系統(tǒng),構(gòu)建dentry與inode的過程非常簡單,但對于其他文件系統(tǒng),則會慢得多。
了解了Dentry cache的概念,才能明白為何下面會有兩種定位文件的方式。
6 無denty時定位文件
因為上面提到的Denty Cache,VFS并不能保證隨時都有dentry項與inode項可用。下面是無dentry項與inode項時的定位方式。
為了簡化問題,這里假設(shè)已經(jīng)找到了dir的dentry項(找到dentry的過程會在后面講解)。
首先,通過dir對應(yīng)的dentry0找到inode0節(jié)點,有了inode節(jié)點就可以讀取目錄中的信息。其中包含了該目錄包含的下一級目錄與文件文件列表,包括name與inode號。實際上用ls命令查看的就是這些信息?!發(fā)s -i”會顯示出文件的inode號。
> ls -i
975248 subdir0 975247 subdir1 975251 file0
然后,根據(jù)通過根據(jù)subdir0對應(yīng)的inode號重建inode2,并通過文件數(shù)據(jù)(目錄也是文件)與inode2重建subdir0的dentry節(jié)點:dentry1。
> ls -i
975311 file1 975312 file2
接著,根據(jù)file1對應(yīng)的inode號重建inode4,并通過文件數(shù)據(jù)與inode4重建file1的dentry節(jié)點。
最后,就可以通過inode4節(jié)點訪問文件了。
注意:文件對應(yīng)的inode號是確定的,只是inode結(jié)構(gòu)體需要重新構(gòu)造。
7 有dentry時定位文件
一旦在Dentry cache中建立了dentry項,下次訪問就很方便了。
Dentry中的一個關(guān)鍵變量是d_subdirs,它保存了下一級目錄的列表,用于快速定位文件。
首先,在代表dir目錄的dentry0的d_subdirs中查找名字為“subdir0”的dentry項,找到了dentry1。
然后在dentry1中查找名字為“file1”的dentry項,然后找到了file1對應(yīng)的dentry項,
最后通過file1對應(yīng)的dentry項獲得file1對應(yīng)的inode4。
與無dentry項時比較,有dentry項時的操作精簡了許多。
8 Symbolic link
建立symboliclink的命令為:ln -s 源文件目標文件
Linux中的symbolic link類似于Windows系統(tǒng)中的快捷方式。如下圖所示,symlink1是指向file1的symbolic link。symlink1本身也是文件,因此有自己獨立的inode節(jié)點。symlink中實際存儲的是源文件的相對路徑。
大部分文件操作會直接對symbolic link指向的目標進行操作,比如open(“symlikn1”),實際上打開的是file3。
如果file3不在會發(fā)生什么事情呢?open函數(shù)照樣會按照symlink1中的文件路徑打開文件。但file3不存在,因此會報錯說文件不存在。
9 hard link
Linux除了symbolic link,還有hard link的概念。
Hard link建立實際上是dentry項的一個拷貝,它們都指向同一個inode節(jié)點。當(dāng)我們使用write改寫file1的內(nèi)容時,hardlink1的內(nèi)容也會被改寫,因為所以實際上它們是同一個文件。
如下圖所示,hardlink1是file1的一個hard link。它們都指向同一個inode1節(jié)點。Inode1中有一個計數(shù)器,用于記錄有幾個dentry項指向它。刪除任意一個dentry項都不會導(dǎo)致inode1的刪除。只有所有指向inode1的dentry都被刪除了,inode1才會被刪除。
他們實際從某種意義上講,所有dentry項都是hard link。
10 進程對文件的管理
進程控制塊task_struct中有兩個變量與文件有關(guān):fs與files。
files中存儲著root與pwd兩個指向dentry項的指針。用戶定路徑時,絕對路徑會通過root進行定位;相對路徑會的通過pwd進行定位。(一個進程的root不一定是文件系統(tǒng)的根目錄。比如ftp進程的根目錄不是文件系統(tǒng)的根目錄,這樣才能保證用戶只能訪問ftp目錄下的內(nèi)容)
fs是一個file object列表,其中每一個節(jié)點對應(yīng)著一個被打開了的文件。當(dāng)進程定位到文件時,會構(gòu)造一個file object,并通過f_inode關(guān)聯(lián)到inode節(jié)點。文件關(guān)閉時(close),進程會釋放對應(yīng)對應(yīng)file object。File object中的f_mode是打開時選擇的權(quán)限,f_pos為讀寫位置。當(dāng)打開同一個文件多次時,每次都會構(gòu)造一個新的file object。每個file object中有獨立的f_mode與f_pos。
11 open的過程
打開文件涉及到里一系列的結(jié)構(gòu)調(diào)整,這里分步驟進行說明:
首先建立一個文件管理結(jié)構(gòu),如下圖所示,該進程已經(jīng)打開了兩個文件,接下來我們再打開一個新文件。
第一步:找到文件;
從上文中能定位到我們文件的inode節(jié)點,找到了inode節(jié)點也就找到了文件。
第二步:建立file object;
建立一個新的file object對象,放入file object對象列表,并把它指向inode節(jié)點。
第三步:建立file descriptor
file descriptor就是進程控制塊task_struct中files中維護的fd_array。因為是數(shù)組,所以file descriptor實際上已經(jīng)預(yù)先分配好空間了,這里這是需要把某個空閑的file descriptor與file object關(guān)聯(lián)起來。這個file descriptor在數(shù)組中的索引號就是open文件時得到的文件fd。
12 open與dup
同一個文件是可以open多次的,結(jié)構(gòu)如下圖所示。每次open都會建立一個新的file descriptor與file object。然后指向同一個文件的inode節(jié)點。下圖中,假設(shè)open的文件與fd1指向的是同一個文件,則新創(chuàng)建的file object 2與fd1的file object 2會指向同一個inode2節(jié)點。
Linux還提供了dup功能,用于復(fù)制file descriptor。使用dup不會建立新的非file object,所以新建立的file descriptor會與原filedescriptor同時指向同一個file object。下圖中,我們通過dup(fd1)得到了fd2,則fd2與fd1指向了同一個file object2。
兩次open后由于會生成新的object,所以文件讀寫屬性、文件讀寫位置(f_pos)等信息都是獨立的。使用dup復(fù)制file descriptor后,由于沒有獨立的object,所以修改某個fd的屬性或文件讀寫位置后,另一個fd也會隨之變化。
13 Fork對打開文件的影響
Dup的操作與fork一個子進程時的操作類似。
下圖是已有父進程的文件結(jié)構(gòu):
使用fork后的結(jié)構(gòu)如下。同樣是沒有創(chuàng)建新的file object,因此當(dāng)對parent process中的fd1進行文件指針的移動時(如讀寫),child process中的fd1也會受影響。也即是說opened files list不是進程的一部分,因此不會被復(fù)制。Opened files list應(yīng)該是一個全局性的資源鏈表,進程維護的是一個指針列表fd table,所以被復(fù)制的只是指針列表fd table,而不是opened files list。
14 文件操作函數(shù)解析
通過上面的分析,可以對各個函數(shù)的作用域與使用方式有更清晰的了解。下面列出了常用的文件操作:
函數(shù)名作用對象說明creatdentry, inode創(chuàng)建文件時會創(chuàng)建新的dentry與inodeopenfile object如果文件不存在,且有O_CREAT參數(shù),則會先調(diào)用creatclosefile object刪除file object,但不會刪除文件。state/lstateinode讀取inode的內(nèi)容。如果目標是symbolic link,stat會讀取symbolic link指向的內(nèi)容;lstat則會讀取symbolic link文件本身。chmodfile object改變file object中的f_modechown/lchownfile object改變file object中的f_uid與f_gidtruncateinode改變文件長度。readfile object讀文件會改變file object中的f_poswritefile object,inode寫文件改變file object中的f_pos的同時也會改變文件內(nèi)容與更新修改時間。dupfile object建立一個新的file descriptor,指向同一個file object項seek/lseekfile object改變file object中的f_poslinkdentry創(chuàng)建新的dentry項,指向同一個inode節(jié)點。unlinkdentry刪除一個dentry項。如果該dentry指向的inode節(jié)點沒有被其他dentry項使用,則刪除inode節(jié)點與磁盤文件。renamedentry修改dentry相中的d_namereadlink———–read無法讀取symbolic link 文件的內(nèi)容,需要使用readlink讀取symlinkdentry, inode作用與creat類似,但創(chuàng)建的文件屬性為symbolic link。
注:磁盤文件與inode節(jié)點一一對應(yīng),所以在表中不再單獨列出磁盤文件。
參考文件:
Advanced Programming in the UNIX Environment (3rd) W. Richard Stevens & Stephen A. Rago
Understanding the Linux Kernel (3rd) Daniel P. Bovet & Marco Cesati
本文乃fireaxe原創(chuàng),使用GPL發(fā)布,可以自由拷貝,轉(zhuǎn)載。但轉(zhuǎn)載請保持文檔的完整性,并注明原作者及原鏈接。內(nèi)容可任意使用,但對因使用該內(nèi)容引起的后果不做任何保證。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net
?
評論
查看更多