第九章 文件系統(tǒng)
本章主要描敘linux核心對文件系統(tǒng)的支持, 虛擬文件系統(tǒng)(VFS)以及Linux核心對實際文件系統(tǒng)的支持?!?
Linux的最重要特征之一就是支持多種文件系統(tǒng)。這樣它更加靈活并可以和許多其它種操作系統(tǒng)共存。在本文寫作時Linux已經(jīng)支持15種文件系統(tǒng):ext,ext2,xia,minix,umsdos,msdos,vfat,PRoc,smb,ncp,iso9660,sysv,hpfs,affs以及ufs。毫無疑問,今后支持的文件系統(tǒng)類型還將增加?!?
Linux和Unix并不使用設(shè)備標志符(如設(shè)備號或驅(qū)動器名稱)來訪問獨立文件系統(tǒng),而是通過一個將整個文件系統(tǒng)表示成單一實體的層次樹結(jié)構(gòu)來訪問它。Linux每安裝(mount)一個文件系統(tǒng)時都會其加入到文件系統(tǒng)層次樹中。不管是文件系統(tǒng)屬于什么類型,都被連接到一個目錄上且此文件系統(tǒng)上的文件將取代此目錄中已存在的文件。這個目錄被稱為安裝點或者安裝目錄。當卸載此文件系統(tǒng)時這個安裝目錄中原有的文件將再次出現(xiàn)?!?
當磁盤初始化時(使用fdisk),磁盤中將添加一個描敘物理磁盤邏輯構(gòu)成的分區(qū)結(jié)構(gòu)。每個分區(qū)可以擁有一個獨立文件系統(tǒng)如EXT2。文件系統(tǒng)將文件組織成包含目錄,軟連接等存在于物理塊設(shè)備中的邏輯層次結(jié)構(gòu)。包含文件系統(tǒng)的設(shè)備叫塊設(shè)備。Linux文件系統(tǒng)認為這些塊設(shè)備是簡單的線性塊集合,它并不關(guān)心或理解底層的物理磁盤結(jié)構(gòu)。這個工作由塊設(shè)備驅(qū)動來完成,由它將對某個特定塊的請求映射到正確的設(shè)備上去;此塊所在硬盤的對應(yīng)磁道、扇區(qū)及柱面數(shù)都被保存起來。不管哪個設(shè)備持有這個塊,文件系統(tǒng)都必須使用相同的方式來尋找并操縱此塊。Linux文件系統(tǒng)不管(至少對系統(tǒng)用戶來說)系統(tǒng)中有哪些不同的控制器控制著哪些不同的物理介質(zhì)且這些物理介質(zhì)上有幾個不同的文件系統(tǒng)。文件系統(tǒng)甚至還可以不在本地系統(tǒng)而在通過網(wǎng)絡(luò)連接的遠程硬盤上。設(shè)有一個根目錄內(nèi)容如下的SCSI硬盤:
A E boot etc lib opt tmp usr
C F cdrom fd proc root var sbin
D bin dev home mnt lost+found
此時不管是用戶還是程序都無需知道他們現(xiàn)在操縱的這些文件中的/C實際上是位于系統(tǒng)第一個IDE硬盤上并已安裝VFAT文件系統(tǒng)。在此例中/E表示系統(tǒng)中第二個IDE控制器上的主IDE硬盤。至于第一個IDE控制器是PCI控制器和第二個則是控制IDE CDROM的ISA控制器無關(guān)緊要。當使用modem通過PPP網(wǎng)絡(luò)協(xié)議來撥入網(wǎng)絡(luò)時,可以將Alpha AXP Linux文件系統(tǒng)安裝到/mnt/remote目錄下。
文件系統(tǒng)中的文件是數(shù)據(jù)的集合;包含本章內(nèi)容的文件是一個名叫filesystems.tex的ASCII文件。文件系統(tǒng)不僅包含著文件中的數(shù)據(jù)而且還有文件系統(tǒng)的結(jié)構(gòu)。所有Linux用戶和程序看到的文件、目錄、軟連接及文件保護信息等都存儲在其中。此外文件系統(tǒng)中必須包含安全信息以便保持操作系統(tǒng)的基本完整性。沒人愿意使用一個動不動就丟失數(shù)據(jù)和文件的操作系統(tǒng)?!?
Linux最早的文件系統(tǒng)是Minix,它受限甚大且性能低下。其文件名最長不能超過14個字符(雖然比8.3 文件名要好)且最大文件大小為64M字節(jié)。64M字節(jié)看上去很大,但實際上一個中等的數(shù)據(jù)庫將超過這個尺寸?!〉谝粋€專門為Linux設(shè)計的文件系統(tǒng)被稱為擴展文件系統(tǒng)(Extended File System)或EXT。它出現(xiàn)于1992年四月,雖然能夠解決一些問題但性能依舊不好。1993年擴展文件系統(tǒng)第二版或EXT2被設(shè)計出來并添加到Linux中。它是本章將詳細討論的文件系統(tǒng)?!?
將EXT文件系統(tǒng)添加入Linux產(chǎn)生了重大影響。每個實際文件系統(tǒng)從操作系統(tǒng)和系統(tǒng)服務(wù)中分離出來,它們之間通過一個接口層:虛擬文件系統(tǒng)或VFS來通訊?!?
VFS使得Linux可以支持多個不同的文件系統(tǒng),每個表示一個VFS的通用接口。由于軟件將Linux文件系統(tǒng)的所有細節(jié)進行了轉(zhuǎn)換, 所以Linux核心的其它部分及系統(tǒng)中運行的程序?qū)⒖吹浇y(tǒng)一的文件系統(tǒng)?!inux的虛擬文件系統(tǒng)允許用戶同時能透明地安裝許多不同的文件系統(tǒng)?!?
虛擬文件系統(tǒng)的設(shè)計目標是為Linux用戶提供快速且高效的文件訪問服務(wù)。同時它必須保證文件及其數(shù)據(jù)的正確性。這兩個目標相互間可能存在沖突。當安裝一個文件系統(tǒng)并使用時, Linux VFS為其緩存相關(guān)信息。此緩存中數(shù)據(jù)在創(chuàng)建、寫入和刪除文件與目錄時如果被修改,則必須謹慎地更新文件系統(tǒng)中對應(yīng)內(nèi)容。 如果能夠在運行核心內(nèi)看到文件系統(tǒng)的數(shù)據(jù)結(jié)構(gòu), 那么就可以看到那些正被文件系統(tǒng)讀寫的數(shù)據(jù)塊。描敘文件與目錄的數(shù)據(jù)結(jié)構(gòu)被不斷的創(chuàng)建與刪除而設(shè)備驅(qū)動將不停地讀取與寫入數(shù)據(jù)。這些緩存中最重要的是Buffer Cache,它被集成到獨立文件系統(tǒng)訪問底層塊設(shè)備的例程中。當進行塊存取時數(shù)據(jù)塊首先將被放入Buffer Cache里并根據(jù)其狀態(tài)保存在各個隊列中。此Buffer Cache不僅緩存數(shù)據(jù)而且?guī)椭芾韷K設(shè)備驅(qū)動中的異步接口。
9.1 第二代擴展文件系統(tǒng)(EXT2)
圖9.1 EXT2文件系統(tǒng)的物理分布
第二代擴展文件系統(tǒng)由Rey Card設(shè)計,其目標是為Linux提供一個強大的可擴展文件系統(tǒng)。它同時也是Linux界中設(shè)計最成功的文件系統(tǒng)?!?
象很多文件系統(tǒng)一樣, EXT2建立在數(shù)據(jù)被保存在數(shù)據(jù)塊中的文件內(nèi)這個前提下。這些數(shù)據(jù)塊長度相等且這個長度可以變化,某個EXT2文件系統(tǒng)的塊大小在創(chuàng)建(使用mke2fs)時設(shè)置。 每個文件的大小和剛好大于它的塊大小正數(shù)倍相等。如果塊大小為1024字節(jié)而一個1025字節(jié)長的文件將占據(jù)兩個1024字節(jié)大小的塊。這樣你不得不浪費差不多一般的空間。我們通常需要在CPU的內(nèi)存利用率和磁盤空間使用上進行折中。而大多數(shù)操作系統(tǒng),包括Linux在內(nèi),為了減少CPU的工作負載而被迫選擇相對較低的磁盤空間利用率。并不是文件中每個塊都包含數(shù)據(jù),其中有些塊被用來包含描敘此文件系統(tǒng)結(jié)構(gòu)的信息。EXT2通過一個inode結(jié)構(gòu)來描敘文件系統(tǒng)中文件并確定此文件系統(tǒng)的拓撲結(jié)構(gòu)。 inode結(jié)構(gòu)描敘文件中數(shù)據(jù)占據(jù)哪個塊以及文件的存取權(quán)限、文件修改時間及文件類型。EXT2文件系統(tǒng)中的每個文件用一個inode來表示且每個inode有唯一的編號。文件系統(tǒng)中所有的inode都被保存在inode表中。 EXT2目錄僅是一個包含指向其目錄入口指針的特殊文件(也用inode表示)?!?
圖9.1給出了占用一系列數(shù)據(jù)塊的EXT2文件系統(tǒng)的布局。對文件系統(tǒng)而言文件僅是一系列可讀寫的數(shù)據(jù)塊。文件系統(tǒng)并不需要了解數(shù)據(jù)塊應(yīng)該放置到物理介質(zhì)上什么位置,這些都是設(shè)備驅(qū)動的任務(wù)。無論何時只要文件系統(tǒng)需要從包含它的塊設(shè)備中讀取信息或數(shù)據(jù),它將請求底層的設(shè)備驅(qū)動讀取一個基本塊大小整數(shù)倍的數(shù)據(jù)塊。EXT2文件系統(tǒng)將它所使用的邏輯分區(qū)劃分成數(shù)據(jù)塊組。每個數(shù)據(jù)塊組將那些對文件系統(tǒng)完整性最重要的信息復(fù)制出來, 同時將實際文件和目錄看作信息與數(shù)據(jù)塊。為了發(fā)生災(zāi)難性事件時文件系統(tǒng)的修復(fù),這些復(fù)制非常有必要。以下一節(jié)將著重描敘每個數(shù)據(jù)塊組的內(nèi)容?!?
9.1.1 The EXT2 Inode
圖9.2 EXT2 Inode
在EXT2文件系統(tǒng)中inode是基本塊;文件系統(tǒng)中的每個文件與目錄由唯一的inode來描敘。每個數(shù)據(jù)塊組的EXT2 inode被保存在inode表中, 同時還有一個位圖被系統(tǒng)用來跟蹤已分配和未分配的inode。圖 9.2給出了EXT2 inode的格式,它包含以下幾個域:
mode
它包含兩類信息;inode描敘的內(nèi)容以及用戶使用權(quán)限。EXT2中的inode可以表示一個文件、目錄、符號連接、塊設(shè)備、字符設(shè)備或FIFO。
Owner Information
表示此文件或目錄所有者的用戶和組標志符。文件系統(tǒng)根據(jù)它可以進行正確的存取?!?
Size
以字節(jié)計算的文件尺寸。
Timestamps
inode創(chuàng)建及最后一次被修改的時間?!?
Datablocks
指向此inode描敘的包含數(shù)據(jù)的塊指針。前12個指針指向包含由inode描敘的物理塊, 最后三個指針包含多級間接指針。例如兩級間接指針指向一塊指針,而這些指針又指向一些數(shù)據(jù)塊。這意味著訪問文件尺寸小于或等于12個數(shù)據(jù)塊的文件將比訪問大文件快得多?!?
EXT2 inode還可以描敘特殊設(shè)備文件。雖然它們不是真正的文件, 但可以通過它們訪問設(shè)備。所有那些位于/dev中的設(shè)備文件可用來存取Linux設(shè)備。例如mount程序可把設(shè)備文件作為參數(shù)?!?
9.1.2 EXT2 超塊
超塊中包含了描敘文件系統(tǒng)基本尺寸和形態(tài)的信息。文件系統(tǒng)管理器利用它們來使用和維護文件系統(tǒng)?!⊥ǔ0惭b文件系統(tǒng)時只讀取數(shù)據(jù)塊組0中的超塊,但是為了防止文件系統(tǒng)被破壞, 每個數(shù)據(jù)塊組都包含了復(fù)制拷貝。超塊包含如下信息:
Magic Number
文件系統(tǒng)安裝軟件用來檢驗是否是一個真正的EXT2文件系統(tǒng)超塊。當前EXT2版本中為0xEF53?!?
Revision Level
這個主從修訂版本號讓安裝代碼能判斷此文件系統(tǒng)是否支持只存在于某個特定版本文件系統(tǒng)中的屬性。同時它還是特性兼容標志以幫助安裝代碼判斷此文件系統(tǒng)的新特性是否可以安全使用?!?
Mount Count and Maximum Mount Count
系統(tǒng)使用它們來決定是否應(yīng)對此文件系統(tǒng)進行全面檢查。每次文件系統(tǒng)安裝時此安裝記數(shù)將遞增,當它等于最大安裝記數(shù)時系統(tǒng)將顯示一條警告信息“maxumal mount count reached, running e2fsck is recommended”。
Block Group Number
超塊的拷貝?!?
Block Size
以字節(jié)記數(shù)的文件系統(tǒng)塊大小,如1024字節(jié)?!?
Blocks per Group
每個組中塊數(shù)目。當文件系統(tǒng)創(chuàng)建時此塊大小被固定下來?!?
Free Blocks
文件系統(tǒng)中空閑塊數(shù)?!?
Free Inodes
文件系統(tǒng)中空閑Inode數(shù)。
First Inode
文件系統(tǒng)中第一個inode號。EXT2根文件系統(tǒng)中第一個inode將是指向'/'目錄的目錄入口。
9.1.3 EXT2 組標志符
每個數(shù)據(jù)塊組都擁有一個描敘它結(jié)構(gòu)。象超塊一樣,所有數(shù)據(jù)塊組中的組描敘符被復(fù)制到每個數(shù)據(jù)塊組中以防文件系統(tǒng)崩潰。每個組描敘符包含以下信息:
Blocks Bitmap
對應(yīng)此數(shù)據(jù)塊組的塊分配位圖的塊號。在塊分配和回收時使用?!?
Inode Bitmap
對應(yīng)此數(shù)據(jù)塊組的inode分配位圖的塊號。在inode分配和回收時使用?!?
Inode Table
對應(yīng)數(shù)據(jù)塊組的inode表的起始塊號。每個inode用下面的EXT2 inode結(jié)構(gòu)來表示?!?
Free blocks count, Free Inodes count, Used directory count
組描敘符放置在一起形成了組描敘符表。每個數(shù)據(jù)塊組在超塊拷貝后包含整個組描敘符表。EXT2文件系統(tǒng)僅使用第一個拷貝(在數(shù)據(jù)塊組0中)。其它拷貝都象超塊拷貝一樣用來防止主拷貝被破壞?!?
9.1.4 EXT2 目錄
圖9.3 EXT2目錄
在EXT2文件系統(tǒng)中目錄是用來創(chuàng)建和包含文件系統(tǒng)中文件存取路徑的特殊文件。圖9.3給出了內(nèi)存中的目錄入口布局。
目錄文件是一組目錄入口的鏈表,它們包含以下信息:
inode
對應(yīng)每個目錄入口的inode。它被用來索引儲存在數(shù)據(jù)塊組的Inode表中的inode數(shù)組?!≡趫D9.3中file文件的目錄入口中有一個對inode號11的引用。
name length
以字節(jié)記數(shù)的目錄入口長度?!?
name
目錄入口的名稱
每個目錄的前兩個入口總是"."和".."。它們分別表示當前目錄和父目錄?!?
9.1.5 在EXT2文件系統(tǒng)中搜尋文件
Linux文件名的格式與Unix類似,是一系列以"/"隔開的目錄名并以文件名結(jié)尾。/home/rusling/.cshrc中/home和/rusling都是目錄名而文件名為.cshrc。象Unix系統(tǒng)一樣,Linux并不關(guān)心文件名格式本身,它可以由任意可打印字符組成。為了尋找EXT2文件系統(tǒng)中表示此文件的inode,系統(tǒng)必須將文件名從目錄名中分離出來。
我們所需要的第一個inode是根文件系統(tǒng)的inode,它被存放在文件系統(tǒng)的超塊中。為讀取某個EXT2 inode, 我們必須在適當數(shù)據(jù)塊組的inode表中進行搜尋。如果根inode號為42則我們需要數(shù)據(jù)塊組0 inode表的第42個inode。此根inode對應(yīng)于一個EXT2目錄,即根inode的mode域?qū)⑺钄⒊赡夸浨移鋽?shù)據(jù)塊包含EXT2目錄入口。home目錄是許多目錄的入口同時此目錄給我們提供了大量描敘/home目錄的inode。我們必須讀取此目錄以找到rusling目錄入口,此入口又提供了許多描敘/home/rusling目錄的inode。最后讀取由/home/rusling目錄描敘的inode指向的目錄入口以找出.cshrc文件的inode號并從中取得包含在文件中信息的數(shù)據(jù)塊?!?
9.1.6 改變EXT2文件系統(tǒng)中文件的大小
文件系統(tǒng)普遍存在的一個問題是碎塊化。一個文件所包含的數(shù)據(jù)塊遍布整個文件系統(tǒng),這使得對文件數(shù)據(jù)塊的順序訪問越來越慢。EXT2文件系統(tǒng)試圖通過分配一個和當前文件數(shù)據(jù)塊在物理位置上鄰接或者至少位于同一個數(shù)據(jù)塊組中的新塊來解決這個問題。只有在這種分配策略失敗時才在其它數(shù)據(jù)塊組中分配空間?!?
當進程準備寫某文件時, Linux文件系統(tǒng)首先檢查數(shù)據(jù)是否已經(jīng)超出了文件最后一個被分配的塊空間。如果是則必須為此文件分配一個新數(shù)據(jù)塊。進程將一直等待到此分配完成;然后將其余數(shù)據(jù)寫入此文件。EXT2塊分配例程所作的第一件事是對此文件系統(tǒng)的EXT2超塊加鎖。這是因為塊分配和回收將導(dǎo)致超塊中某些域的改變,Linux文件系統(tǒng)不能在同一時刻為多個進程進行此類服務(wù)。如果另外一個進程需要分配更多的數(shù)據(jù)塊時它必須等到此進程完成分配操作為止。 在超塊上等待的進程將被掛起直到超塊的控制權(quán)被其當前使用者釋放。對超塊的訪問遵循先來先服務(wù)原則,一旦進程取得了超塊的控制則它必須保持到操作結(jié)束為止。如果系統(tǒng)中空閑塊不多則此分配的將失敗,進程會釋放對文件系統(tǒng)超塊的控制。
如果EXT2文件系統(tǒng)被設(shè)成預(yù)先分配數(shù)據(jù)塊則我們可以從中取得一個。預(yù)先分配塊實際上并不存在,它們僅僅包含在已分配塊的位圖中。我們試圖為之分配新數(shù)據(jù)塊文件所對應(yīng)的VFS inode包含兩個EXT2特殊域:prealloc_block和prealloc_count,它們分別代表第一個預(yù)先分配數(shù)據(jù)塊的塊號以及各自的數(shù)目。如果沒有使用預(yù)先分配塊或塊預(yù)先分配數(shù)據(jù)塊策略,則EXT2文件系統(tǒng)必須分配一個新塊。它首先檢查此文件最后一個塊后的數(shù)據(jù)塊是否空閑。從邏輯上來說這是讓其順序訪問更快的最有效塊分配策略。如果此塊已被使用則它會在理想塊周圍64個塊中選擇一個。這個塊雖然不是最理想但和此文件的其它數(shù)據(jù)塊都位于同一個數(shù)據(jù)塊組中?!?
如果此塊還是不空閑則進程將在所有其它數(shù)據(jù)塊組中搜尋,直到找到一空閑塊。塊分配代碼將在某個數(shù)據(jù)塊組中尋找一個由8個空閑數(shù)據(jù)塊組成的簇。如果找不到那么它將取更小的尺寸。如果使用了塊預(yù)先分配則它將更新相應(yīng)的prealloc_block和prealloc_count?!?
找到空閑塊后塊分配代碼將更新數(shù)據(jù)塊組中的位圖并在buffer cache中為它分配一個數(shù)據(jù)緩存。這個數(shù)據(jù)緩存由文件系統(tǒng)支撐設(shè)備的標志符以及已分配塊的塊號來標志。緩存中的數(shù)據(jù)被置0且緩存被標記成dirty以顯示其內(nèi)容還沒有寫入物理磁盤。最后超塊也被標記為dirty以表示它已被更新并解鎖了。如果有進程在等待這個超塊則隊列中的第一個進程將得到運行并取得對超塊的獨占控制。如果數(shù)據(jù)塊被填滿則進程的數(shù)據(jù)被寫入新數(shù)據(jù)塊中,以上的整個過程將重復(fù)且另一個數(shù)據(jù)塊被分配?!?
9.2 虛擬文件系統(tǒng)(VFS)
圖9.4 虛擬文件系統(tǒng)的邏輯示意圖
圖9.4給出了Linux核心中虛擬文件系統(tǒng)和實際文件系統(tǒng)間的關(guān)系。此虛擬文件系統(tǒng)必須能夠管理在任何時刻mount到系統(tǒng)的不同文件系統(tǒng)。它通過維護一個描敘整個虛擬文件系統(tǒng)和實際已安裝文件系統(tǒng)的結(jié)構(gòu)來完成這個工作。
容易讓人混淆的是VFS使用了和EXT2文件系統(tǒng)類似的方式:超塊和inode來描敘文件系統(tǒng)。象EXT2 inode一樣 VFS inode描敘系統(tǒng)中的文件和目錄以及VFS中的內(nèi)容和拓撲結(jié)構(gòu)。從現(xiàn)在開始我將用VFS inode和VFS超塊來將它們和EXT2 inode和超塊進行區(qū)分。
文件系統(tǒng)初始化時將其自身注冊到VFS中。它發(fā)生在系統(tǒng)啟動和操作系統(tǒng)初始化時。這些實際文件系統(tǒng)可以構(gòu)造到核心中也可以設(shè)計成可加載模塊。文件系統(tǒng)模塊可以在系統(tǒng)需要時進行加載,例如VFAT就被實現(xiàn)成一個核心模塊,當mount VFAT文件系統(tǒng)時它將被加載。mount一個基于塊設(shè)備且包含根文件系統(tǒng)的文件系統(tǒng)時,VFS必須讀取其超塊。每個文件系統(tǒng)類型的超塊讀取例程必須了解文件系統(tǒng)的拓撲結(jié)構(gòu)并將這些信息映射到VFS超塊結(jié)構(gòu)中。VFS在系統(tǒng)中保存著一組已安裝文件系統(tǒng)的鏈表及其VFS超塊。每個VFS 超塊包含一些信息以及一個執(zhí)行特定功能的函數(shù)指針。例如表示一個已安裝EXT2文件系統(tǒng)的超塊包含一個指向EXT2相關(guān)inode讀例程的指針。這個EXT2 inode讀例程象所有文件系統(tǒng)相關(guān)讀例程一樣填充了VFS inode中的域。每個VFS超塊包含此文件系統(tǒng)中第一個VFS inode的指針。對于根文件系統(tǒng)此inode表示的是"/"目錄。這種信息映射方式對EXT2文件系統(tǒng)非常有效但是對其它文件系統(tǒng)要稍差?!?
系統(tǒng)中進程訪問目錄和文件時將使用系統(tǒng)調(diào)用遍歷系統(tǒng)的VFS inode。
例如鍵入ls或cat命令則會引起虛擬文件系統(tǒng)對表示此文件系統(tǒng)的VFS inode的搜尋。由于系統(tǒng)中每個文件與目錄都使用一個VFS inode來表示,所以許多inode會被重復(fù)訪問。這些inode被保存在inode cache中以加快訪問速度。如果某個inode不在inode cache中則必須調(diào)用一個文件系統(tǒng)相關(guān)例程來讀取此inode。對這個inode 的讀將把此它放到inode cache中以備下一次訪問。不經(jīng)常使用的VFS inode將會從cache中移出?!?
所有Linux文件系統(tǒng)使用一個通用buffer cache來緩沖來自底層設(shè)備的數(shù)據(jù)以便加速對包含此文件系統(tǒng)的物理 設(shè)備的存取。
這個buffer cache與文件系統(tǒng)無關(guān)并被集成到Linux核心分配與讀寫數(shù)據(jù)緩存的機制中。讓Linux文件系統(tǒng)獨立于底層介質(zhì)和設(shè)備驅(qū)動好處很多。所有的塊結(jié)構(gòu)設(shè)備將其自身注冊到Linux核心中并提供基于塊的一致性異步接口。象SCSI設(shè)備這種相對復(fù)雜的塊設(shè)備也是如此。當實際文件系統(tǒng)從底層物理磁盤讀取數(shù)據(jù)時,塊設(shè)備驅(qū)動將從它們所控制的設(shè)備中讀取物理塊。buffer cache也被集成到了塊設(shè)備接口中?!‘斘募到y(tǒng)讀取數(shù)據(jù)塊時它們將被保存在由所有文件系統(tǒng)和Linux核心共享的全局buffer cache中。這些buffer由其塊號和讀取設(shè)備的設(shè)備號來表示。所以當某個數(shù)據(jù)塊被頻繁使用則它很可能能從buffer cache而不是磁盤中讀取出來,后者顯然將花費更長的時間。有些設(shè)備支持通過預(yù)測將下一次可能使用的數(shù)據(jù)提前讀取出來?!?
VFS還支持一種目錄cache以便對經(jīng)常使用的目錄對應(yīng)的inode進行快速查找。我們可以做一個這樣的實驗,首先我們對一個最近沒有執(zhí)行過列目錄操作的目錄進行列目錄操作。第一次列目錄時你可能發(fā)現(xiàn)會有較短的停頓但第二次操作時結(jié)果會立刻出現(xiàn)。目錄cache不存儲目錄本身的inode;這些應(yīng)該在inode cache中,目錄cache 僅僅保存全目錄名和其inode號之間的映射關(guān)系?!?
9.2.1 VFS 超塊
每個已安裝的文件系統(tǒng)由一個VFS超塊表示;它包含如下信息:
Device
表示文件系統(tǒng)所在塊設(shè)備的設(shè)備標志符。例如系統(tǒng)中第一個IDE硬盤的設(shè)備標志符為0x301?!?
Inode pointers
這個mounted inode指針指向文件系統(tǒng)中第一個inode。而covered inode指針指向此文件系統(tǒng)安裝目錄的inode。根文件系統(tǒng)的VFS超塊不包含covered指針。
Blocksize
以字節(jié)記數(shù)的文件系統(tǒng)塊大小,如1024字節(jié)。
Superblock Operations
指向此文件系統(tǒng)一組超塊操縱例程的指針。這些例程被VFS用來讀寫inode和超塊?!?
File System type
這是一個指向已安裝文件系統(tǒng)的file_system_type結(jié)構(gòu)的指針。
File System specific
指向文件系統(tǒng)所需信息的指針?!?
9.2.2 The VFS Inode
和EXT2文件系統(tǒng)相同,VFS中的每個文件、目錄等都用且只用一個VFS inode表示。每個VFS inode中的信息通過文件系統(tǒng)相關(guān)例程從底層文件系統(tǒng)中得到。VFS inode僅存在于核心內(nèi)存并且保存只要對系統(tǒng)有用,它們就會被保存在在VFS inode cache中。每個VFS inode包含下列域:
device
包含此文件或此VFS inode代表的任何東西的設(shè)備的設(shè)備標志符。
inode number
文件系統(tǒng)中唯一的inode號。在虛擬文件系統(tǒng)中device和inode號的組合是唯一的。
mode
和EXT2中的相同, 表示此VFS inode的存取權(quán)限?!?
user ids
所有者的標志符。
times
VFS inode 創(chuàng)建、修改和寫入時間。
block size
以字節(jié)計算的文件塊大小,如1024字節(jié)。
inode operations
指向一組例程地址的指針。這些例程和文件系統(tǒng)相關(guān)且對此inode執(zhí)行操作,如截斷此inode表示的文件?!?
count
使用此VFS inode的系統(tǒng)部件數(shù)。一個count為0的inode可以被自由的丟棄或重新使用?!?
lock
用來對某個VFS inode加鎖,如用于讀取文件系統(tǒng)時?!?
dirty
表示這個VFS inode是否已經(jīng)被寫過,如果是則底層文件系統(tǒng)需要更新?!?
file system specific information
9.2.3 注冊文件系統(tǒng)
圖9.5 已注冊文件系統(tǒng)
當重新建立Linux核心時安裝程序會詢問是否需要所有可支持的文件系統(tǒng)。核心重建時文件系統(tǒng)啟動代碼包含了所有那些編入核心的文件系統(tǒng)的初始化例程?!?
Linux文件系統(tǒng)可構(gòu)造成模塊, 此時它們會僅在需要時加載或者使用insmod來載入。當文件系統(tǒng)模塊被加載時, 它將向核心注冊并在卸載時撤除注冊。每個文件系統(tǒng)的初始化例程還將向虛擬文件系統(tǒng)注冊,它用一個包含文件系統(tǒng)名稱和指向其VFS超塊讀例程的指針的file_system_type結(jié)構(gòu)表示。每個file_system_type結(jié)構(gòu)包含下列信息:
Superblock read routine
此例程載文件系統(tǒng)的一個實例被安裝時由VFS調(diào)用?!?
File System name
文件系統(tǒng)的名稱如ext2?!?
Device needed
文件系統(tǒng)是否需要設(shè)備支持。并不是所有的文件系統(tǒng)都需要設(shè)備來保存它。例如/proc文件系統(tǒng)不需要塊設(shè)備支持?!?
你可以通過查閱/proc/filesystems可找出已注冊的文件系統(tǒng),如:
ext2
nodev proc
iso9660
9.2.4 安裝文件系統(tǒng)
當超級用戶試圖安裝一個文件系統(tǒng)時,Linux核心首先使系統(tǒng)調(diào)用中的參數(shù)有效化。盡管mount程序會做一些基本的檢查, 但是它并不知道核心構(gòu)造時已經(jīng)支持那些文件系統(tǒng),同時那些建議的安裝點的確存在??慈缦碌囊粋€mount命令:
$ mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom
mount命令將傳遞三個參數(shù)給核心:文件系統(tǒng)名,包含文件系統(tǒng)的物理塊設(shè)備以及此新文件系統(tǒng)要安裝到的已存在的目錄名?!?
虛擬文件系統(tǒng)首先必須做的是找到此文件系統(tǒng)。它將通過由鏈指針file_systems指向的file_system_type結(jié) 構(gòu)來在所有已知文件系統(tǒng)中搜尋。
如果找到了一個相匹配的文件系統(tǒng)名,那么它就知道核心支持此文件系統(tǒng)并可得到讀取此文件系統(tǒng)超塊相關(guān)例程的指針。如果找不到,但文件系統(tǒng)使用了可動態(tài)加載核心模塊,則操作仍可繼續(xù)。此時核心將請求核心后臺進程加載相應(yīng)的文件系統(tǒng)模塊?!?
接下來如果由mount傳遞的物理設(shè)備還沒有安裝, 則必須找到新文件系統(tǒng)將要安裝到的那個目錄的VFS inode?!∵@個VFS inode可能在inode cache中也可能在支撐這個安裝點所在文件系統(tǒng)的塊設(shè)備中。一旦找到這個inode則將對它進行檢查以確定在此目錄中是否已經(jīng)安裝了其它類型的文件系統(tǒng)。多個文件系統(tǒng)不能使用相同目錄作為安裝點?!?
此時VFS安裝代碼必須分配一個VFS超塊并將安裝信息傳遞到此文件系統(tǒng)的超塊讀例程中。系統(tǒng)中所有的VFS 超塊都被保存在由super_block結(jié)構(gòu)構(gòu)成的super_blocks數(shù)組中, 并且對應(yīng)此安裝應(yīng)有一個這種結(jié)構(gòu)。超塊讀 例程將基于這些從物理設(shè)備中讀取的信息來填充這些VFS超塊域。對于EXT2文件系統(tǒng)此信息的轉(zhuǎn)化過程十分 簡便,僅需要讀取EXT2超塊并填充VFS超塊。但其它文件系統(tǒng)如MS-DOS文件系統(tǒng)就不那么容易了。不管哪種文件系統(tǒng),對VFS超塊的填充意味著文件系統(tǒng)必須從支持它的塊設(shè)備中讀取描敘它的所有信息。如果塊設(shè)備驅(qū)動不能從中讀取或不包含這種類型文件系統(tǒng)則mount命令會失敗?!?
圖9.6 一個已安裝的文件系統(tǒng)
每個文件系統(tǒng)用一個vfsmount結(jié)構(gòu)來描敘。如圖9.6所示。它們被排入由vfsmntlist指向的的鏈表中?!?
另外一個指針:vfsmnttail指向鏈表的最后一個入口, 同時mru_vfsmnt指針指向最近使用最多的文件系統(tǒng)。 每個vfsmount結(jié)構(gòu)中由以下部分組成:包含此文件系統(tǒng)的塊設(shè)備的設(shè)備號,此文件系統(tǒng)安裝的目錄以及文件 系統(tǒng)安裝時分配的VFS超塊指針。VFS超塊指向這種類型文件系統(tǒng)和此文件系統(tǒng)根inode的file_system_type結(jié)構(gòu)。一旦此文件系統(tǒng)被加載, 這個inode將一直駐留在VFS inod cache中?!?
9.2.5 在虛擬文件系統(tǒng)中搜尋文件
為了在虛擬文件系統(tǒng)中找到某個文件的VFS inode,VFS必須依次解析此文件名字中的間接目錄直到找到此VFS inode。每次目錄查找包括一個對包含在表示父目錄VFS inode中的查找函數(shù)的調(diào)用。由于我們總是讓每個文件系統(tǒng)的根可用并且由此系統(tǒng)的VFS 超塊指向它,所以這是一個可行方案。每次在實際文件系統(tǒng)中尋找inode 時,文件系統(tǒng)將在目錄cache中尋找相應(yīng)目錄。如果在目錄cache中無相應(yīng)入口則文件系統(tǒng)必須從底層文件系統(tǒng)或inode cache中取得此VFS inode。
9.2.6 Creating a File in the Virtual File System
9.2.7 卸載文件系統(tǒng)
如果已安裝文件系統(tǒng)中有些文件還在被系統(tǒng)使用則不能卸載此文件系統(tǒng)。例如有進程使用/mnt/cdrom或其子目錄時將不能卸載此文件系統(tǒng)。如果將要卸載的文件系統(tǒng)中有些文件還在被使用,那么在VFS inode cache中有與其對應(yīng)的VFS inode。通過在inode鏈表中查找此文件系統(tǒng)占用設(shè)備的inode來完成此工作。對應(yīng)此已安裝文件系統(tǒng)的VFS超塊為dirty,表示它已被修改過所以必須寫回到磁盤的文件系統(tǒng)中。一旦寫入磁盤,VFS超塊占用的內(nèi)存將歸還到核心的空閑內(nèi)存池中。最后對應(yīng)的vfsmount結(jié)構(gòu)將從vfsmntlist中釋放?!?
9.2.8 The VFS Inode Cache
操縱已安裝文件系統(tǒng)時,它們的VFS inode將被連續(xù)讀寫。虛擬文件系統(tǒng)通過維護一個inode cache來加速對所有已安裝文件系統(tǒng)的訪問。每次VFS inode都可從inode cache中讀取出來以加速對物理設(shè)備的訪問?!?
VFS inode cache以散列表形式實現(xiàn),其入口時指向具有相同散列值的VFS inode鏈表。每個inode的散列值可通過包含此文件系統(tǒng)的底層物理設(shè)備標志符和inode號計算出來。每當虛擬文件系統(tǒng)訪問一個inode時,系統(tǒng)將首先在VFS inode cache中查找。為了在cache中尋找inode,系統(tǒng)先計算出其散列值然后將其作為inode散列表的索引。這樣將得到指向一系列相同散列值的inode鏈表。然后依次讀取每個inode直到找到那個具有相同inode號以及設(shè)備標志符的inode為止?!?
如果在cache中找到了此inode則它的count值遞增以表示用戶增加了一個,同時文件操作將繼續(xù)進行。否則必須找到一個空閑VFS inode以便文件系統(tǒng)能從內(nèi)存中讀取此inode。VFS有許多種選擇來取得空閑inode。如果系統(tǒng)可以分配多個VFS inode則它將按如下步驟進行:首先分配核心頁面并將其打碎成新的空閑inode并將其放入inode鏈表中。系統(tǒng)所有的VFS inode都被放到由first_inode指向的鏈表和inode散列表中。如果系統(tǒng)已經(jīng)擁有所有inode, 則它必須找到便于重新使用的inode。那些inode最好count記數(shù)為0;因為這種inode沒有誰在使用。很重要的VFS inode,如文件系統(tǒng)的根inode,其count 域總是大于0,所以它所使用的inode是不能被重新使用的。一旦找到可重用inode則應(yīng)清除之: 其VFS inode可能為dirty,必須要寫入到文件系統(tǒng)中或者需要加鎖,此時系統(tǒng)必須等到解鎖時才能繼續(xù)運行?!?
找到新的VFS inode后必須調(diào)用文件系統(tǒng)相關(guān)例程使用從底層實際文件系統(tǒng)中讀出的內(nèi)容填充它。在填充過程 中,此新VFS inode的count記數(shù)為1并被加鎖以排斥其它進程對它的使用直到此inode包含有效信息為止?!?
為了取得真正需要的VFS inode,文件系統(tǒng)可能需要存取幾類其它inode。我們讀取一個目錄時雖然只需要最后一級目錄但是所有的中間目錄也被讀了出來。由于使用了VFS inode cache,較少使用的inode將被丟棄而較多使用的inode將保存在cache中?!?
9.2.9 目錄 Cache
為了加速對常用目錄的訪問,VFS維護著一個目錄入口cache。
當在實際文件系統(tǒng)尋找目錄時,有關(guān)此目錄的細節(jié)將被存入目錄cache中。當再次尋找此目錄時,例如在此目錄中列文件名或打開文件,則這些信息就可以在目錄cache中找到。在實際實現(xiàn)中只有短目錄入口(最多15個字 符)被緩存,這是因為那些較短目錄名的目錄正是使用最頻繁的。例如/usr/X11R6/bin這個短目錄經(jīng)常被X server所使用?!?
目錄cache也由散列表組成,每個入口指向具有相同散列值的目錄cache人口鏈表。散列函數(shù)使用包含此文件系統(tǒng)的設(shè)備號以及目錄名稱來計算在此散列表中的偏移值或者索引值, 這樣能很快找到被緩存的目錄。 如果在cache中的搜尋消耗的時間太長或者甚至沒有找到則使用此cache用處不大?!?
為了保證cache的有效性和及時更新,VFS保存著一個最近最少使用(LRU)的目錄cache人口鏈表。當首次查找此目錄時其目錄入口被首次放入cache中并添加到第一級LRU鏈表的尾部。在已經(jīng)充滿的cache 中它代替位于LRU鏈表最前端的現(xiàn)存入口。此目錄入口被再次使用時它將被放到第二級LRU cache鏈表的最后。此時需要將位于第二級LRU cache鏈表的最前端的那個替換掉。入口在鏈表前端的唯一原因是它們已經(jīng)很久沒被訪問過了。如果被訪問過那么它們將位于此鏈表的尾部附近。位于第二級LRU cache鏈表中的入口要比位于第一級LRU cache鏈表中的安全一些?!?
9.3 The Buffer Cache
圖9.7 Buffer Cache示意圖
操縱已安裝文件系統(tǒng)將產(chǎn)生大量對此塊設(shè)備的讀寫請求。這些塊讀寫請求都是通過標準核心例程調(diào)用以buffer_head結(jié)構(gòu)形式傳遞到設(shè)備驅(qū)動中。它們提供了設(shè)備驅(qū)動所需的所有信息:表示設(shè)備的設(shè)備標志符以及請求的塊號。所有塊設(shè)備都被看成相同塊大小的線性塊集合。為了加速對物理塊設(shè)備的訪問,Linux 使用了一個塊buffer cache。系統(tǒng)中全部的塊緩沖,包括那些沒使用過的新緩沖都保存在此buffer cache中。這個cache被多個物理塊設(shè)備共享;任何時刻此cache中都有許多屬于不同系統(tǒng)塊設(shè)備且狀態(tài)不同的塊緩沖。如果有效數(shù)據(jù)可以從buffer cache中找到則將節(jié)省大量訪問物理設(shè)備的時間。任何對塊設(shè)備讀寫的塊緩沖都被放入此cache中。隨時間的變化有些塊緩沖可能將會被此cache中刪除以為更需要它的緩沖騰出空間,如果它被頻繁使用則可以一直保存在此cache中?!?
此cache中的塊緩沖由設(shè)備標志符以及緩沖對應(yīng)的塊號來唯一的表示。它由兩個功能部分組成。其一是空閑塊緩沖鏈表。它為每個可支持的塊大小提供了一個鏈表并且系統(tǒng)中的空閑塊緩沖在創(chuàng)建或者被丟棄時都被排入此鏈表中。當前可支持的塊大小為512、1024、2048、4096與8192字節(jié)。其二是cache自身。它是用一組指向具有相同散列索引值的緩沖鏈的散列表。這個散列索引值通過其自身的設(shè)備標志符與數(shù)據(jù)塊設(shè)備的塊號來產(chǎn)生。圖9.7給出了一個帶有一些入口的散列表。塊緩沖要么在空閑鏈表中要么在此buffer cache中。如果在buffer cache中則它們按照最近最少使用(LRU)鏈表來排列。 對于每種緩沖類型都有一個LRU鏈表,系統(tǒng)使用它們來對某種緩沖進行操作,如將帶新數(shù)據(jù)的緩沖寫入到磁盤上。緩沖的類型表示其當前狀態(tài),Linux現(xiàn)在支持以下集中類型:
clean
未使用的新緩沖
locked
等待寫入且加鎖的緩沖
dirty
dirty緩沖。它們包含新的有效數(shù)據(jù),但目前沒被調(diào)度執(zhí)行寫操作?!?
shared
共享緩沖
unshared
以前被共享但現(xiàn)在沒有被共享的緩沖
當文件系統(tǒng)需要從其底層物理設(shè)備讀取一個緩沖塊時,它將首先在buffer cache里尋找。如果在此buffer cache中找不到則它將從適當大小的空閑鏈表中取得一個clean狀態(tài)的節(jié)點, 同時將新緩沖添加到buffer cache 中去。如果所需的緩沖位于buffer cache中,那么它可能已經(jīng)或沒有更新。如果沒有被更新或者它為新塊則文件系統(tǒng)必須請求相應(yīng)的數(shù)據(jù)驅(qū)動從磁盤中讀取該數(shù)據(jù)塊。
為了讓此buffer cache運行更加有效并且在使用此buffer cache的塊設(shè)備之間合理的分配cache入口,系統(tǒng)必須對其進行維護。Linux使用bdflush核心后臺進行來對此cache執(zhí)行許多瑣碎工作,但有時作為使用cache 的結(jié)構(gòu)自動進行?!?
9.3.1 bdflush 核心后臺進程
bdflush是對過多的dirty緩沖系統(tǒng)提供動態(tài)響應(yīng)的簡單核心后臺進程;這些緩沖塊中包含必須被寫入到硬盤上的數(shù)據(jù)。它在系統(tǒng)啟動時作為一個核心線程運行,其名字叫"kflushd"。你可以使用ps命令看到此系統(tǒng)進程。通常情況下此進程一直在睡眠直到系統(tǒng)中的dirty緩沖數(shù)目增大到一定數(shù)目。當分配與丟棄緩沖時,系統(tǒng)中dirty緩沖的數(shù)目將做一個統(tǒng)計。如果其數(shù)目超過某個數(shù)值則喚醒bdflush進程。缺省的閥值為60%, 但是如果系統(tǒng)急需緩沖則任何時刻都可能喚醒bdflush。使用update命令可以看到和改變這個數(shù)值?!?
# update -d
bdflush version 1.4
0: 60 Max fraction of LRU list to examine for dirty blocks
1: 500 Max number of dirty blocks to write each time bdflush activated
2: 64 Num of clean buffers to be loaded onto free list by refill_freelist
3: 256 Dirty block threshold for activating bdflush in refill_freelist
4: 15 Percentage of cache to scan for free clusters
5: 3000 Time for data buffers to age before flushing
6: 500 Time for non-data (dir, bitmap, etc) buffers to age before flushing
7: 1884 Time buffer cache load average constant
8: 2 LAV ratio (used to determine threshold for buffer fratricide).
但有數(shù)據(jù)寫入緩沖使之變成dirty時,所有的dirty緩沖被連接到一個BUF_DIRTY LRU鏈表中,bdflush會將適當數(shù)目的緩沖塊寫到磁盤上。這個數(shù)值的缺省值為500?!?
9.3.2 update進程
update命令不僅僅是一個命令;它還是一個后臺進程。當作為超級用戶運行時(在系統(tǒng)初始化時)它將周期性調(diào)用系統(tǒng)服務(wù)例程將老的dirty緩沖沖刷到磁盤上去。它所完成的這個工作與bdflush類似。當一個dirty緩沖完成此操作后, 它將把本應(yīng)寫入到各自磁盤上的時間標記到其中。update每次運行時它將在系統(tǒng)的所有dirty緩沖中查找那些沖刷時間已過期的。這些過期緩沖都被寫入到磁盤?!?
9.4 /proc文件系統(tǒng)
/proc文件系統(tǒng)真正顯示了Linux虛擬文件系統(tǒng)的能力。事實上它并不存在-不管時/proc目錄還是其子目錄和文件都不真正的存在。但是我們是如何能夠執(zhí)行cat /proc/devices命令的?/proc文件系統(tǒng)象一個真正的文 件系統(tǒng)一樣將向虛擬文件系統(tǒng)注冊。然而當有對/proc中的文件和目錄的請求發(fā)生時, VFS系統(tǒng)將從核心中的數(shù)據(jù)中臨時構(gòu)造這些文件和目錄。例如核心的/proc/devices文件是從描敘其設(shè)備的內(nèi)核數(shù)據(jù)結(jié)構(gòu)中產(chǎn)生出來。/proc文件系統(tǒng)提供給用戶一個核心內(nèi)部工作的可讀窗口。幾個Linux子系統(tǒng),如在modules一章描敘的Linux核心模塊都在/proc文件系統(tǒng)中創(chuàng)建入口。
9.5 設(shè)備特殊文件
和所有Unix版本一樣Linux將硬件設(shè)備看成特殊的文件。如/dev/null表示一個空設(shè)備。設(shè)備文件不使用文件 系統(tǒng)中的任何數(shù)據(jù)空間,它僅僅是對設(shè)備驅(qū)動的訪問入口點。EXT2文件系統(tǒng)和Linux VFS都將設(shè)備文件實現(xiàn)成特殊的inode類型。有兩種類型的設(shè)備文件:字符與塊設(shè)備特殊文件。在核心內(nèi)部設(shè)備驅(qū)動實現(xiàn)了類似文件的操作過程:我們可以對它執(zhí)行打開、關(guān)閉等工作。字符設(shè)備允許以字符模式進行I/O操作而塊設(shè)備的I/O操作需要通過buffer cache。當對一個設(shè)備文件發(fā)出的I/O請求將被傳遞到相應(yīng)的設(shè)備驅(qū)動。常常這種設(shè)備文件并不是一個真正的設(shè)備驅(qū)動而僅僅是一個偽設(shè)備驅(qū)動,如SCSI設(shè)備驅(qū)動層。設(shè)備文件通過表示設(shè)備類型的主類型標志符和表示單元或主類型實例的從類型來引用。例如在系統(tǒng)中第一個IDE控制器上的IDE硬盤的主設(shè)備號為3而其第一個分區(qū)的從標志符為1。所以執(zhí)行l(wèi)s -l /dev/hda1將有如下結(jié)果:
$ brw-rw---- 1 root disk 3, 1 Nov 24 15:09 /dev/hda1
在核心內(nèi)部每個設(shè)備由唯一的kdev_t結(jié)構(gòu)來表示,其長度為兩字節(jié),首字節(jié)包含從設(shè)備號而尾字節(jié)包含主設(shè)備號?!∩侠械暮诵腎DE設(shè)備為0x0301。表示塊或者字符設(shè)備的EXT2 inode在其第一個直接塊指針包含了設(shè)備的主從設(shè)備號。當VFS讀取它時,表示它的VFS inode結(jié)構(gòu)的i_rdev域被設(shè)置成相應(yīng)的設(shè)備標志符。