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