一篇搞懂Linux內(nèi)核源碼分析--虛擬文件系統(tǒng)(VFS)
1、通用文件模型
Linux內(nèi)核支持裝載不同的文件系統(tǒng)類(lèi)型,不同的文件系統(tǒng)有各自管理文件的方式。Linux中標(biāo)準(zhǔn)的文件系統(tǒng)為Ext文件系統(tǒng)族,當(dāng)然,開(kāi)發(fā)者不能為他們使用的每種文件系統(tǒng)采用不同的文件存取方式,這與操作系統(tǒng)作為一種抽象機(jī)制背道而馳。
為支持各種文件系統(tǒng),Linux內(nèi)核在用戶(hù)進(jìn)程(或C標(biāo)準(zhǔn)庫(kù))和具體的文件系統(tǒng)之間引入了一個(gè)抽象層,該抽象層稱(chēng)之為“虛擬文件系統(tǒng)(VFS)”。
VFS一方面提供一種操作文件、目錄及其他對(duì)象的統(tǒng)一方法,使用戶(hù)進(jìn)程不必知道文件系統(tǒng)的細(xì)節(jié)。另一方面,VFS提供的各種方法必須和具體文件系統(tǒng)的實(shí)現(xiàn)達(dá)成一種妥協(xié),畢竟對(duì)幾十種文件系統(tǒng)類(lèi)型進(jìn)行統(tǒng)一管理并不是件容易的事。
為此,VFS中定義了一個(gè)通用文件模型,以支持文件系統(tǒng)中對(duì)象(或文件)的統(tǒng)一視圖。
Linux對(duì)Ext文件系統(tǒng)族的支持是最好的,因?yàn)閂FS抽象層的組織與Ext文件系統(tǒng)類(lèi)似,這樣在處理Ext文件系統(tǒng)時(shí)可以提高性能,因?yàn)樵贓xt和VFS之間轉(zhuǎn)換幾乎不會(huì)損失時(shí)間。
內(nèi)核處理文件的關(guān)鍵是inode,每個(gè)文件(和目錄)都有且只有一個(gè)對(duì)應(yīng)的inode(struct inode實(shí)例),其中包含元數(shù)據(jù)和指向文件數(shù)據(jù)的指針,但inode并不包含文件名。系統(tǒng)中所有的inode都有一個(gè)特定的編號(hào),用于唯一的標(biāo)識(shí)各個(gè)inode。文件名可以隨時(shí)更改,但是索引節(jié)點(diǎn)對(duì)文件是唯一的,并且隨文件的存在而存在。
對(duì)于每個(gè)已經(jīng)掛載的文件系統(tǒng),VFS在內(nèi)核中都生成一個(gè)超級(jí)塊結(jié)構(gòu)(struct super_block實(shí)例),超級(jí)塊代表一個(gè)已經(jīng)安裝的文件系統(tǒng),用于存儲(chǔ)文件系統(tǒng)的控制信息,例如文件系統(tǒng)類(lèi)型、大小、所有inode對(duì)象、臟的inode鏈表等。
inode和super block在存儲(chǔ)介質(zhì)中都是有實(shí)際映射的,即存儲(chǔ)介質(zhì)中也存在超級(jí)塊和inode。但是由于不同類(lèi)型的文件系統(tǒng)差異,超級(jí)塊和inode的結(jié)構(gòu)不盡相同。而VFS的作用就是通過(guò)具體的設(shè)備驅(qū)動(dòng)獲得某個(gè)文件系統(tǒng)中的超級(jí)塊和inode節(jié)點(diǎn),然后將其中的信息填充到內(nèi)核中的struct super_block和struct inode中,以此來(lái)試圖對(duì)不同文件系統(tǒng)進(jìn)行統(tǒng)一管理。
由于塊設(shè)備速度較慢(于內(nèi)存而言),可能需要很長(zhǎng)時(shí)間才能找到與一個(gè)文件名關(guān)聯(lián)的inode。Linux使用目錄項(xiàng)(dentry)緩存來(lái)快速訪問(wèn)此前的查找操作結(jié)果。在VFS讀取了一個(gè)目錄或文件的數(shù)據(jù)之后,則創(chuàng)建一個(gè)dentry實(shí)例(struct dentry),以緩存找到的數(shù)據(jù)。
dentry結(jié)構(gòu)的主要用途就是建立文件名和相關(guān)的inode之間的聯(lián)系。一個(gè)文件系統(tǒng)中的dentry對(duì)象都被放在一個(gè)散列表中,同時(shí)不再使用的dentry對(duì)象被放到超級(jí)塊指向的一個(gè)LRU鏈表中,在某個(gè)時(shí)間點(diǎn)會(huì)刪除比較老的對(duì)象以釋放內(nèi)存。
另外簡(jiǎn)單提一下兩個(gè)數(shù)據(jù)結(jié)構(gòu):
每種注冊(cè)到內(nèi)核的文件系統(tǒng)類(lèi)型以struct file_system_type結(jié)構(gòu)表示,每種文件系統(tǒng)類(lèi)型中都有一個(gè)鏈表,指向所有屬于該類(lèi)型的文件系統(tǒng)的超級(jí)塊。
當(dāng)一個(gè)文件系統(tǒng)掛載到內(nèi)核文件系統(tǒng)的目錄樹(shù)上,會(huì)生成一個(gè)掛載點(diǎn),用來(lái)管理所掛載的文件系統(tǒng)的信息。該掛載點(diǎn)用一個(gè)struct vfsmount結(jié)構(gòu)表示,這個(gè)結(jié)構(gòu)后面會(huì)提到。
上面的這些結(jié)構(gòu)的關(guān)系大致如下:

其中紅色字體的鏈表為內(nèi)核中的全局鏈表。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈(zèng)送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)

2. 掛載文件系統(tǒng)
在用戶(hù)程序中,使用mount系統(tǒng)調(diào)用來(lái)掛載文件系統(tǒng),相應(yīng)的使用umount卸載文件系統(tǒng)。當(dāng)然,內(nèi)核必須支持將要掛載的文件系統(tǒng)類(lèi)型,在內(nèi)核啟動(dòng)時(shí)或者在安裝內(nèi)核模塊時(shí),可以注冊(cè)特定的文件系統(tǒng)類(lèi)型到內(nèi)核,注冊(cè)的函數(shù)為register_filesystem()。
mount命令最常用的方式是mount [-t fstype] something somewhere
其中something是將要被掛載的設(shè)備或目錄,somewhere指明要掛載到何處。-t選項(xiàng)指明掛載的文件系統(tǒng)類(lèi)型。由于something指向的設(shè)備是一個(gè)已知設(shè)備,即其上的文件系統(tǒng)類(lèi)型是確定的,所以-t選項(xiàng)必須設(shè)置正確才能掛載成功。
每個(gè)裝載的文件系統(tǒng)都對(duì)應(yīng)一個(gè)vfsmount結(jié)構(gòu)的實(shí)例。
由于裝載過(guò)程是向內(nèi)核文件系統(tǒng)目錄樹(shù)中添加裝載點(diǎn),這些裝載點(diǎn)就存在一種父子關(guān)系,這和父目錄與子目錄的關(guān)系類(lèi)似。例如,我的根文件系統(tǒng)類(lèi)型是squashfs,裝載到根目錄“/”,生成一個(gè)掛載點(diǎn),之后我又在/tmp目錄掛載了ramfs文件系統(tǒng),在根文件系統(tǒng)中的tmp目錄生成了一個(gè)掛載點(diǎn),這兩個(gè)掛載點(diǎn)就是父子關(guān)系。這種關(guān)系存儲(chǔ)在struct vfsmount結(jié)構(gòu)中。
在下圖中,根文件系統(tǒng)為squashfs,根目錄為“/”,然后創(chuàng)建/tmp目錄,并掛載為ramfs,之后又創(chuàng)建了/tmp/usbdisk/volume9和/tmp/usbdisk/volume1兩個(gè)目錄,并將/tmp/dev/sda1和/tmp/dev/sdb1兩個(gè)分區(qū)掛載到這兩個(gè)目錄上。其中/tmp/dev/sda1設(shè)備上有如下文件:
gccbacktrace/?
----> gcc_backtrace.c?
---->man_page.log?
---->readme.txt?
notes-fs.txt?
smb.conf
掛載完成后,VFS中相關(guān)的數(shù)據(jù)結(jié)構(gòu)的關(guān)系如圖所示。

mount系統(tǒng)調(diào)用在內(nèi)核中的入口點(diǎn)是sys_mount函數(shù),該函數(shù)將裝載的選項(xiàng)從用戶(hù)態(tài)復(fù)制一份,然后調(diào)用do_mount()函數(shù)進(jìn)行掛載,這個(gè)函數(shù)做的事情就是通過(guò)特定文件系統(tǒng)讀取超級(jí)塊和inode信息,然后建立VFS的數(shù)據(jù)結(jié)構(gòu)并建立上圖中的關(guān)系。
在父文件系統(tǒng)中的某個(gè)目錄上掛載另一個(gè)文件系統(tǒng)后,該目錄原來(lái)的內(nèi)容就被隱藏了。例如,/tmp/samba/是非空的,然后,我將/tmp/dev/sda1掛載到/tmp/samba上,那這時(shí)/tmp/samba/目錄下就只能看到/tmp/dev/sda1設(shè)備上的文件,直到將該設(shè)備卸載,原來(lái)目錄中的文件才會(huì)顯示出來(lái)。這是通過(guò)struct vfsmount中的mnt_mountpoint和mnt_root兩個(gè)成員來(lái)實(shí)現(xiàn)的,這兩個(gè)成員分別保存了在父文件系統(tǒng)中掛載點(diǎn)的dentry和在當(dāng)前文件系統(tǒng)中掛載點(diǎn)的dentry,在卸載當(dāng)前掛載點(diǎn)之后,可以找回掛載目錄在父文件系統(tǒng)中的dentry對(duì)象。
3. 一個(gè)進(jìn)程中與文件系統(tǒng)相關(guān)的信息
其中fs成員指向進(jìn)程當(dāng)前工作目錄的文件系統(tǒng)信息。files成員指向了進(jìn)程打開(kāi)的文件的信息。nsproxy指向了進(jìn)程所在的命名空間,其中包含了虛擬文件系統(tǒng)命名空間。

從上圖可以看到,fs中包含了文件系統(tǒng)的掛載點(diǎn)和掛載點(diǎn)的dentry信息。而files指向了一系列的struct file結(jié)構(gòu),其中struct path結(jié)構(gòu)用于將struct file和vfsmount以及dentry聯(lián)系起來(lái)。struct file保存了內(nèi)核所看到的文件的特征信息,進(jìn)程打開(kāi)的文件列表就存放在task_struct->files->fd_array[]數(shù)組以及fdtable中。
task_struct結(jié)構(gòu)還存放了其打開(kāi)文件的文件描述符fd的信息,這是用戶(hù)進(jìn)程需要用到的,用戶(hù)進(jìn)程在通過(guò)文件名打開(kāi)一個(gè)文件后,文件名就沒(méi)有用處了,之后的操作都是對(duì)文件描述符fd的,在內(nèi)核中,fget_light()函數(shù)用于通過(guò)整數(shù)fd來(lái)查找對(duì)應(yīng)的struct file對(duì)象。由于每個(gè)進(jìn)程都維護(hù)了自己的fd列表,所以不同進(jìn)程維護(hù)的fd的值可以重復(fù),例如標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤對(duì)應(yīng)的fd分別為0、1、2。
struct file的mapping成員指向?qū)儆谖募嚓P(guān)的inode實(shí)例的地址空間映射,通常它設(shè)置為inode->i_mapping。在讀寫(xiě)一個(gè)文件時(shí),每次都從物理設(shè)備上獲取文件的話(huà),速度會(huì)很慢,在內(nèi)核中對(duì)每個(gè)文件分配一個(gè)地址空間,實(shí)際上是這個(gè)文件的數(shù)據(jù)緩存區(qū)域,在讀寫(xiě)文件時(shí)只是操作這塊緩存,通過(guò)內(nèi)核有相應(yīng)的同步機(jī)制將臟的頁(yè)寫(xiě)回物理設(shè)備。super_block中維護(hù)了一個(gè)臟的inode的鏈表。
struct file的f_op成員指向一個(gè)struct file_operations實(shí)例(圖中畫(huà)錯(cuò)了,不是f_pos),該結(jié)構(gòu)保存了指向所有可能文件操作的指針,如read/write/open等。
4、打包文件系統(tǒng)
在制作好了文件系統(tǒng)的目錄之后,可通過(guò)特定于文件系統(tǒng)類(lèi)型的工具對(duì)目錄進(jìn)行打包,即制作文件系統(tǒng)。例如squashfs文件系統(tǒng)的打包工具為mksquashfs。除了打包之外,打包工具還針對(duì)特定文件系統(tǒng)生成超級(jí)塊和inode節(jié)點(diǎn)信息,最終生成的文件系統(tǒng)鏡像可以被內(nèi)核解釋并掛載。
附錄 VFS相關(guān)數(shù)據(jù)結(jié)構(gòu)
inode:
super_block:
super_block:
vfsmount:
vfsmount:
