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