Linux Kernel 核心中文手冊(9)--文件系統

The File System (文件系統)
 
    本章描述 Linux 如何維護它支持的文件系統中的文件。描述了虛擬文件系統
( Virtual File System VFS )並解釋了 Linux 核心中真實的文件系統如何被支

 
    Linux 的一個最重要的特點之一使它可以支持許多不同的文件系統。這讓它非
常靈活,可以和許多其他操作系統共存。在寫作本章的時候, Linux 可一直支持
 15 種文件系統: ext 、 ext2 、 xia 、 minix 、 umsdos 、 msdos 、
vfat 、 proc 、 smb 、 ncp 、 iso9660 、 sysv 、 hpfs 、 affs 和 ufs ,
而且不容置疑,隨着時間流逝,會加入更多的文件系統。
 


    在 Linux 中,象 Unix 一樣,系統可以使用的不同的文件系統不是通過設備
標識符(例如驅動器編號或設備名稱)訪問,而是連接成一個單一的樹型的結構,
用一個統一的單個實體表示文件系統。 Linux 在文件系統安裝的時候把它加到這
個單一的文件系統樹上。所有的文件系統,不管什麼類型,都安裝在一個目錄,安
裝的文件系統的文件掩蓋了這個目錄原來存在的內容。這個目錄叫做安裝目錄或安
裝點。當這個文件系統卸載的時候,安裝目錄自己的文件又可以顯現出來。
 
    當磁盤初始化的時候(比如用 fdisk ),利用一個分區結構把物理磁盤劃分
成一組邏輯分區。每一個分區可以放一個文件系統,例如一個 EXT2 文件系統。文
件系統在物理設備的塊上通過目錄、軟鏈接等把文件組織成邏輯的樹型結構。可以
包括文件系統的設備是塊設備。系統中的第一個 IDE 磁盤驅動器的第一個分區,
 IDE 磁盤分區 /dev/hda1 ,是一個塊設備。 Linux 文件系統把這些塊設備看成
簡單的線性的塊的組合,不知道也不去關心底層的物理磁盤的尺寸。把對設備的特
定的塊的讀的請求映射到對於設備有意義的術語:這個塊保存在硬盤上的磁道、扇
區和柱面,這是每一個塊設備驅動程序的任務。一個文件系統不管它保存在什麼設
備上,都應該用同樣的方式工作,有同樣的觀感。另外,使用 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 文件系統。本例中(實際是我家中的 Linux 系
統), /E 是次 IDE 控制器上的 master IDE 磁盤。第一個 IDE 控制器是 PCI
控制器,而第二個是 ISA 控制器,也控制着 IDE CDROM ,這些也都無關緊要。我
可以用一個 modem 和 PPP 網絡協議撥號到我工作的網絡,這時,我可以遠程安裝
我的 Alpha AXP Linux 系統的文件系統到 /mnt/remote 。
 
    文件系統中的文件包含了數據的集合:包含本章源的文件是一個 ASCII 文件
,叫做 filesystems.tex 。一個文件系統不僅保存它包括的文件的數據,也保存
文件系統的結構。它保存了 Linux 用戶和進程看到的所有的信息,例如文件、目
錄、軟鏈接、文件保護信息等等。另外,它必須安全地保存這些信息,操作系統的
基本的一致性依賴於它的文件系統。沒有人可以使用一個隨機丟失數據和文件的操
作系統(不知道是否有,雖然我曾經被擁有的律師比 Linux 開發者還多的操作系
統傷害過)。
 
    Minix 是 Linux 的第一個文件系統,有相當的侷限,性能比較差。它的文件
名不能長於 14 個字符(這仍然比 8.3 文件名要好),最大的文集大小是 64M 字


節。第一眼看去, 64M 字節好像足夠大,但是設置中等的數據庫需要更大的文件
大小。第一個專爲 Linux 設計的文件系統,擴展文件系統或 EXT ( Extend File
 System ),在 1992 年 4 月引入,解決了許多問題,但是仍然感到性能低。所
以, 1993 年,增加了擴展文件系統第二版,或 EXT2 。這種文件系統在本章稍後
詳細描述。
 
    當 EXT 文件系統增加到 Linux 的時候進行了一個重要的開發。真實的文件系
統通過一個接口層從操作系統和系統服務中分離出來,這個接口叫做虛擬文件系統
或 VFS 。 VFS 允許 Linux 支持許多(通常是不同的)文件系統,每一個都向
VFS 表現一個通用的軟件接口。 Linux 文件系統的所有細節都通過軟件進行轉換
,所以所有的文件系統對於 Linux 核心的其餘部分和系統中運行的程序顯得一樣
。 Linux 的虛擬文件系統層允許你同時透明地安裝許多不同的文件系統。
 
 
 
    Linux 虛擬文件系統的實現使得對於它的文件的訪問儘可能的快速和有效。它
也必須保證文件和文件數據正確地存放。這兩個要求相互可能不平等。 Linux VFS
 在安裝和使用每一個文件系統的時候都在內存中高速緩存信息。在文件和目錄創
建、寫和刪除的時候這些高速緩存的數據被改動,必須非常小心才能正確地更新文
件系統。如果你能看到運行的核心中的文件系統的數據結構,你就能夠看到文件系
統讀寫數據塊,描述正在訪問的文件和目錄的數據結構會被創建和破壞,同時設備
驅動程序會不停地運轉,獲取和保存數據。這些高速緩存中最重要的是 Buffer


Cache ,在文件系統訪問它們底層的塊設備的時候結合進來。當塊被訪問的時候它
們被放到 Buffer Cache ,根據它們的狀態放在不同的隊列中。 Buffer Cache 不
僅緩存數據緩衝區,它也幫助管理塊設備驅動程序的異步接口。
 
9.1 The Second Extended File System (EXT2)
 
    EXT2 被髮明( Remy Card )作爲 Linux 一個可擴展和強大的文件系統。它
至少在 Linux 社區中是最成功的文件系統,是所有當前的 Linux 發佈版的基礎。
 EXT2 文件系統,象所有多數文件系統一樣,建立在文件的數據存放在數據塊中的
前提下。這些數據塊都是相同長度,雖然不同的 EXT2 文件系統的塊長度可以不同
,但是對於一個特定的 EXT2 文件系統,它的塊長度在創建的時候就確定了(使用
 mke2fs )。每一個文件的長度都按照塊取整。如果塊大小是 1024 字節,一個
1025 字節的文件會佔用兩個 1024 字節的塊。不幸的是這一意味着平均你每一個
文件浪費半個塊。通常計算中你會用磁盤利用來交換 CPU 對於內存的使用,這種
情況下, Linux 象大多數操作系統一樣,爲了較少 CPU 的負載,使用相對低效率
的磁盤利用率來交換。不是文件系統中所有的塊都包含數據,一些塊必須用於放置
描述文件系統結構的信息。 EXT2 用一個 inode 數據結構描述系統中的每一個文
件,定義了系統的拓撲結構。一個 inode 描述了一個文件中的數據佔用了哪些塊
以及文件的訪問權限、文件的修改時間和文件的類型。 EXT2 文件系統中的每一個
文件都用一個 inode 描述,而每一個 inode 都用一個獨一無二的數字標識。文件
系統的 inode 都放在一起,在 inode 表中。 EXT2 的目錄是簡單的特殊文件(它
們也使用 inode 描述),包括它們目錄條目的 inode 的指針。


 
    圖 9.1 顯示了一個 EXT2 文件系統佔用了一個塊結構的設備上一系列的塊。
只要提到文件系統,塊設備都可以看作一系列能夠讀寫的塊。文件系統不需要關心
自身要放在物理介質的哪一個塊上,這是設備驅動程序的工作。當一個文件系統需
要從包括它的塊設備上讀取信息或數據的時候,它請求對它支撐的設備驅動程序讀
取整數數目的塊。 EXT2 文件系統把它佔用的邏輯分區劃分成塊組( Block Group
 )。每一個組除了當作信息和數據塊來存放真實的文件和目錄之外,還複製對於
文件系統一致性至關重要的信息。這種複製的信息對於發生災難,文件系統需要恢
復的時候是必要的。下面對於每一個塊組的內容進行了詳細的描述。
 
9.1.1 The EXT2 Inode ( EXT2 I 節點)
 
 
 
    在 EXT2 文件系統中, I 節點是建設的基石:文件系統中的每一個文件和目
錄都用一個且只用一個 inode 描述。每一個塊組的 EXT2 的 inode 都放在 inode
 表中,還有一個位圖,讓系統跟蹤分配和未分配的 I 節點。圖 9.2 顯示了一個
 EXT2 inode 的格式,在其他信息中,它包括一些域:
 
參見 include/linux/ext2_fs_i.h
 
    mode 包括兩組信息:這個 inode 描述了什麼和用戶對於它的權限。對於


EXT2 ,一個 inode 可以描述一個文件、目錄、符號鏈接、塊設備、字符設備或
FIFO 。
 
    Owner Information 這個文件或目錄的數據的用戶和組標識符。這允許文件系
統正確地進行文件訪問權限控制
 
    Size 文件的大小(字節)
 
    Timestamps 這個 inode 創建的時間和它上次被修改的時間。
 
    Datablocks 指向這個 inode 描述的數據的塊的指針。最初的 12 個是指向這
個 inode 描述的數據的物理塊,最後的 3 個指針包括更多級的間接的數據塊。例
如,兩級的間接塊指針指向一個指向數據塊的塊指針的塊指針。的這意味着小於或
等於 12 數據塊大小的文件比更大的文件的訪問更快。
 
    你應該注意 EXT2 inode 可以描述特殊設備文件。這些不是真正的文件,程序
可以用於訪問設備。 /dev 下所有的設備文件都是爲了允許程序訪問 Linux 的設
備。例如 mount 程序用它希望安裝的設備文件作爲參數。
 
9.1.2 The EXT2 Superblock ( EXT2 超級塊)
 
    超級塊包括這個文件系統基本大小和形狀的描述。它裏面的信息允許文件系統


管理程序用於維護文件系統。通常文件系統安裝時只有塊組 0 中的超級塊被讀取
,但是每一個塊組中都包含一個複製的拷貝,用於系統崩潰的時候。除了其他一些
信息,它包括:
 
參見 include/linux/ext2_fs_sb.h
 
Magic Number 允許安裝軟件檢查這是否是一個 EXT2 文件系統的超級塊。對於當
前版本的 EXT2 是 0xEF53 。
 
    Revision Level major 和 minor 修訂級別允許安裝代碼確定這個文件系統是
否支持只有在這種文件系統特定修訂下才有的特性。這也是特性兼容域,幫助安裝
代碼確定哪些新的特徵可以安全地使用在這個文件系統上。
 
Mount Count and Maximum Mount Count 這些一起允許系統確定這個文件系統是否
需要完全檢查。每一次文件系統安裝的時候 mount count 增加,當它等於
maximum mount count 的時候,會顯示告警信息“ maximal 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 The EXT2 Group Descriptor ( EXT2 組描述符)
 
每一個塊組都有一個數據結構描述。象超級塊,所有得虧組的組描述符在每一塊組
都進行復制。每一個組描述符包括以下信息:
 
參見 include/linux/ext2_fs.h ext2_group_desc
 
Blocks Bitmap 這個塊組的塊分配位圖的塊編號,用在塊的分配和回收過程中
 
Inode Bitmap 這個塊組的 inode 位圖的塊編號。用在 inode 的分配和回收過程


中。
 
Inode Table 這個塊組的 inode table 的起始塊的塊編號。每一個 EXT2 inode
數據結構表示的 inode 在下面描述
 
Free blocks count , Free Inodes count , Used directory count
 
    組描述符依次排列,它們一起組成了組描述符表( group descriptor
table )。每一個塊組包括塊組描述符表和它的超級塊的完整拷貝。只有第一個拷
貝(在塊組 0 )實際被 EXT2 文件系統使用。其他拷貝,象超級塊的其他拷貝一
樣,只有在主拷貝損壞的時候才使用。
 
9.1.4 EXT2 Directories ( EXT2 目錄)
 
    在 EXT2 文件系統中,目錄是特殊文件,用來創建和存放對於文件系統中的文
件的訪問路徑。圖 9.3 顯示了內存中一個目錄條目的佈局。一個目錄文件,是一
個目錄條目的列表,每一個目錄條目包括以下信息:
 
參見 include/linux/ext2_fs.h ext2_dir_entry
 
這個目錄條目的 inode 。這是個放在塊組的 inode 表中的 inode 數組的索引。
圖 9.3 叫做 file 的文件的目錄條目引用的 inode 是 i1 。


 
Name length 這個目錄條目的字節長度
 
Name 這個目錄條目的名字
 
每一個目錄中的前兩個條目總是標準的“ . ”和“ .. ” , 分別表示“本目錄”
和“父目錄”。
 
9.1.5 Finding a File in a EXT2 File System (在一個 EXT2 文件系統中查找
一個文件)
 
    Linux 的文件名和所有的 Unix 文件名的格式一樣。它是一系列目錄名,用“
 / ”分隔,以文件名結尾。一個文件名稱的例子是 /home/rusling/.cshrc ,其
中 /home 和 /rusling 是目錄名,文件名是 .cshrc 。象其它 Unix 系統一樣,
 Linux 不關心文件名本身的格式:它可以任意長度,由可打印字符組成。爲了在
 EXT2 文件系統中找到代表這個文件的 inode ,系統必須逐個解析目錄中的文件
名直到得到這個文件。
 
    我們需要的第一個 inode 是這個文件系統的根的 inode 。我們通過文件系統
的超級塊找到它的編號。爲了讀取一個 EXT2 inode 我們必須在適當的塊組中的
inode 表中查找。舉例,如果根的 inode 編號是 42 ,那麼我們需要塊組 0 中的
 inode 表中的第 42 個 inode 。 Root inode 是一個 EXT2 目錄,換句話說


root inode 的模式描述它是一個目錄,它的數據塊包括 EXT2 目錄條目。
 
    Home 是這些目錄條目之一,這個目錄條目給了我們描述 /home 目錄的 inode
 編號。我們必須讀取這個目錄(首先讀取它的 inode ,然後讀取從這個 inode
描述的數據塊讀取目錄條目),查找 rusling 條目,給出描述 /home/rusling 目
錄的 inode 編號。最後,我們讀取描述 /home/rusling 目錄的 inode 指向的目
錄條目,找到 .cshrc 文件的 inode 編號,這樣,我們得到了包括文件裏信息的
數據塊。
 
9.1.6 Changing the size of a File in an EXT2 File System (在 EXT2 文件
系統中改變一個文件的大小)
 
    文件系統的一個常見問題是它趨於更多碎片。包含文件數據的塊分佈在整個文
件系統,數據塊越分散,對於文件數據塊的順序訪問越沒有效率。 EXT2 文件系統
試圖克服這種情況,它分配給一個文件的新塊物理上和它的當前數據塊接近或者至
少和它的當前數據塊在一個塊組裏面。只有這個失敗了它才分配其它塊組中的數據
塊。
 
    無論何時一個進程試圖象一個文件寫入數據, Linux 文件系統檢查數據是否
會超出文件最後分配塊的結尾。如果是,它必須爲這個文件分配一個新的數據塊。
直到這個分配完成,該進程無法運行,它必須等待文件系統分配新的數據塊並把剩
下的數據寫入,然後才能繼續。 EXT2 塊分配例程所要做的第一個事情是鎖定這個


文件系統的 EXT2 超級塊。分配和釋放塊需要改變超級塊中的域, Linux 文件系
統不能允許多於一個進程同一時間都進行改變。如果另一個進程需要分配更多的數
據塊,它必須等待,直到這個進程完成。等待超級塊的進程被掛起,不能運行,直
到超級塊的控制權被它的當前用戶釋放。對於超級塊的訪問的授權基於一個先來先
服務的基礎( first come first serve ),一旦一個進程擁有了超級塊的控制,
它一直維持控制權到它完成。鎖定超級塊之後,進程檢查文件系統是否有足夠的空
閒塊。如果沒有足夠的空閒塊,分配更多的嘗試會失敗,進程交出這個文件系統超
級塊的控制權。
 
    如果文件系統中有足夠的空閒塊,進程會試圖分配一塊。如果這個 EXT2 文件
系統已經建立了預分配的數據塊,我們就可以取用。預分配的塊實際上並不存在,
它們只是分配塊的位圖中的保留塊。 VFS inode 用兩個 EXT2 特有的域表示我們
試圖分配新數據塊的文件: prealloc_block and prealloc_count ,分別是預分
配塊中第一塊的編號和預分配塊的數目。如果沒有預分配塊或者預分配被禁止,
EXT2 文件系統必須分配一個新的數據塊。 EXT2 文件系統首先查看文件最後一個
數據塊之後數據塊是否空閒。邏輯上,這是可分配的效率最高的塊,因爲可以讓順
序訪問更快。如果這個塊不是空閒,繼續查找,在隨後的 64 塊中找理想的數據塊
。這個塊,雖然不是最理想,但是至少和文件的其它數據塊相當接近,在一個塊組
中。
 
參見 fs/ext2/balloc.c ext2_new_block()
 


    如果這些塊都沒有空閒的,進程開始順序查看所有其它塊組直到它找到空閒的
塊。塊分配代碼在這些塊組中查找 8 個空閒數據塊的簇。如果無法一次找到 8 個
,它會降低要求。如果希望進行塊預分配,並允許,它會相應地更新
prealloc_block 和 prealloc_count 。
 
    不管在哪裏找到了空閒的數據塊,塊分配代碼會更新塊組的塊位圖,並從
buffer cache 中分配一個數據緩衝區。這個數據緩衝區使用支撐文件系統的設備
標識符和分配塊的塊編號來唯一標識。緩衝區中的數據被置爲 0 ,緩衝區標記爲
“ dirty ”表示它的內容還沒有寫到物理磁盤上。最後,超級塊本身也標記位“
 dirty ”,顯示它進行了改動,然後它的鎖被釋放。如果有進程在等待超級塊,
那麼隊列中第一個進程就允許運行,得到超級塊的排它控制權,進行它的文件操作
。進程的數據寫到新的數據塊,如果數據塊填滿,整個過程重複進行,再分配其它
數據塊
 
9.2 The Virtual File System (虛擬文件系統 VFS )
 
    圖 9.4 顯示了 Linux 核心的虛擬文件系統和它的真實的文件系統之間的關係
。虛擬文件系統必須管理任何時間安裝的所有的不同的文件系統。爲此它管理描述
整個文件系統(虛擬)和各個真實的、安裝的文件系統的數據結構。
 
    相當混亂的是, VFS 也使用術語超級塊和 inode 來描述系統的文件,和
EXT2 文件系統使用的超級塊和 inode 的方式非常相似。象 EXT2 的 inode ,


VFS 的 inode 描述系統中的文件和目錄:虛擬文件系統的內容和拓撲結構。從現
在開始,爲了避免混淆,我會用 VFS inode 和 VFS 超級塊以便同 EXT2 的 inode
 和超級塊區分開來。
 
參見 fs/*
 
    當每一個文件系統初始化的時候,它自身向 VFS 登記。這發生在系統啓動操
作系統初始化自身的時候。真實的文件系統自身建立在內核中或者是作爲可加載的
模塊。文件系統模塊在系統需要的時候加載,所以,如果 VFAT 文件系統用核心模
塊的方式實現,那麼它只有在一個 VFAT 文件系統安裝的時候才加載。當一個塊設
備文件系統安裝的時候,(包括 root 文件系統), VFS 必須讀取它的超級塊。
每一個文件系統類型的超級塊的讀取例程必須找出這個文件系統的拓撲結構,並把
這些信息映射到一個 VFS 超級塊的數據結構上。 VFS 保存系統中安裝的文件系統
的列表和它們的 VFS 超級塊列表。每一個 VFS 超級塊包括了文件系統的信息和完
成特定功能的例程的指針。例如,表示一個安裝的 EXT2 文件系統的超級塊包括一
個 EXT2 相關的 inode 的讀取例程的指針。這個 EXT2 inode 讀取例程,象所有
的和文件系統相關的 inode 讀取例程一樣,填充 VFS inode 的域。每一個 VFS
超級塊包括文件系統中的一個 VFS inode 的指針。對於 root 文件系統,這是表
示“ / ”目錄的 inode 。這種信息映射對於 EXT2 文件系統相當高效,但是對於
其他文件系統相對效率較低。
 
 


 
    當系統的進程訪問目錄和文件的時候,調用系統例程,遊歷系統中的 VFS
inode 。例如再一個目錄中輸入 ls 或者 cat 一個文件,讓 VFS 查找代表這個文
件系統的 VFS inode 。映爲系統中的每一個文件和目錄都用一個 VFS inode 代表
,所以一些 inode 會被重複訪問。這些 inode 保存在 inode cache ,這讓對它
們的訪問更快。如果一個 inode 不在 inode cache 中,那麼必須調用一個和文件
系統相關的例程來讀取適當的 inode 。讀取這個 inode 的動作讓它被放到了
inode cache ,以後對這個 inode 的訪問會讓它保留在 cache 中。較少使用的
VFS inode 會從這個高速緩存中刪除。
 
參見 fs/inode.c
 
    所有的 Linux 文件系統使用一個共同的 buffer cache 來緩存底層的設備的
數據緩衝區,這樣可以加速對於存放文件系統的物理設備的訪問,從而加快對文件
系統的訪問。這個 buffer cache 獨立於文件系統,集成在 Linux 核心分配、讀
和寫數據緩衝區的機制中。讓 Linux 文件系統獨立於底層的介質和支撐的設備驅
動程序有特殊的好處。所有的塊結構的設備向 Linux 核心登記,並表現爲一個統
一的,以塊爲基礎的,通常是異步的接口。甚至相對複雜的塊設備比如 SCSI 設備
也是這樣。當真實的文件系統從底層的物理磁盤讀取數據的,引起塊設備驅動程序
從它們控制的設備上讀取物理塊。在這個塊設備接口中集成了 buffer cache 。當
文件系統讀取了塊的時候,它們被存放到了所有的文件系統和 Linux 核心共享的
全局的 buffer cache 中。其中的 buffer (緩衝區)用它們的塊編號和被讀取設


備的一個唯一的標識符來標記。所以,如果相同的數據經常需要,它會從
buffer cache 中讀取,而不是從磁盤讀取(會花費更多時間)。一些設備支持超
前讀( read ahead ),數據塊會預先讀取,以備以後可能的讀取。
 
參見 fs/buffer.c
 
    VFS 也保存了一個目錄查找的緩存,所以一個常用的目錄的 inode 可以快速
找到。作爲一個試驗,試着對於你最近沒有列表的目錄列表。第一次你列表的時候
,你會注意到短暫的停頓,當時第二次你列表的時候,結果會立即出來。目錄緩存
本身不存儲目錄裏的 inode ,這是 inode cache 負責的,目錄緩存只是存儲目錄
項目全稱和它們的 inode 編號。
 
參見 fs/dcache.c
 
9.2.1 The VFS Superblock ( VFS 超級塊)
 
每一個安裝的文件系統都用 VFS 超級塊表示。除了其它信息, VFS 超級塊包括:
 
 
參見 include/linux/fs.h
 
Device 這是包含文件系統的塊設備的設備標識符。例如, /dev/hda1 ,系統中的


第一個 IDE 磁盤,設備標識符是 0x301
 
Inode pointers 其中的 mounted inode 指針指向該文件系統的第一個 inode 。
 Covered inode 指針指向文件系統安裝到的目錄的 inode 。對於 root 文件系統
,它的 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 包括一些域:


 
參見 include/linux/fs.h
 
device 存放這個文件(或這個 VFS inode 代表的其它實體)的設備的設備標識符

 
Inode nunber 這個 inode 的編號,在這個文件系統中唯一。 Device 和 inode
number 的組合在整個虛擬文件系統中是唯一的。
 
Mode 象 EXT2 一樣,這個域描述這個 VFS inode 代表的東西和對它的訪問權限。
 
 
User ids 屬主標識符
 
Times 創建、修改和寫的時間
 
Block size 這個文件的塊的字節大小,例如 1024 字節
 
Inode operations 指向一組例程地址的指針。這些例程和文件系統相關,執行對
於這個 inode 的操作,例如 truncate 這個 inode 代表的文件
 
Count 系統組件當前使用這個 VFS inode 的數目。 Count 0 意味着這個 inode


是空閒,可以廢棄或者重用。
 
Lock 這個域用於鎖定這個 VFS inode 。例如當從文件系統讀取它的時候
 
Dirty 顯示這個 VFS inode 是否被寫過,如果這樣,底層的文件系統需要更新。
 
 
File system specific information
 
9.2.3 Registering the File Systems (登記文件系統)
 
 
 
    當你建立 Linux 核心的時候,你會被提問是否需要每一個支持的文件系統。
當核心建立的時候,文件系統初始化代碼包括對於所有內建的文件系統的初始化例
程的調用。 Linux 文件系統也可以建立成爲模塊,這種情況下,它們可以在需要
的時候加載或者手工使用 insmod 加載。當家在一個文件系統模塊的時候,它自身
向核心登記,當卸載的時候,它就註銷。每一個文件系統的初始化例程都向虛擬文
件系統註冊自身,並用一個 file_system_type 數據結構代表,這裏麪包括文件系
統的名稱和一個指向它的 VFS 超級塊的讀取例程的指針。圖 9.5 顯示
file_system_type 數據結構被放到了由 file_systems 指針指向的一個列表中。
每一個 file_system_type 數據結構包括以下信息:


 
參見 fs/filesystems.c sys_setup()
 
參見 include/linux/fs.h file_system_type
 
Superblock read routine 在這個文件系統的一個實例安裝的時候,由 VFS 調用
這個例程
 
File Systme name 文件系統的名稱,例如 ext2
 
Device needed 是否這個文件系統需要一個設備支持?並非所有的文件系統需要一
個設備來存放。例如 /proc 文件系統,不需要一個塊設備
 
你可以檢查 /proc/filesystems 來查看登記了哪些文件系統,例如:
 
ext2
nodev proc
 
iso9660
 
9.2.4 Mounting a File System (安裝一個文件系統)
 


    當超級用戶試圖安裝一個文件系統的時候, Linux 核心必須首先驗證系統調
用中傳遞的參數。雖然 mount 可以執行一些基本的檢查,但是它不知道這個核心
建立是可以支持的文件系統或者提議的安裝點是否存在。考慮以下的 mount 命令

 
$ mount –t iso9660 –o ro /dev/cdrom /mnt/cdrom
 
    這個 mount 命令會傳遞給核心三部分信息:文件系統的名稱、包括這個文件
系統的物理塊設備和這個新的文件系統要安裝在現存的文件系統拓撲結構中的哪一
個地方。
 
    虛擬文件系統要做的第一件事情是找到這個文件系統。它首先查看
file_systems 指向的列表中的每一個 file_system_type 數據結構,查看所有已
知的文件系統。如果它找到了一個匹配的名稱,它就直到這個核心支持這個文件系
統類型,並得到了文件系統相關例程的地址,去讀取這個文件系統的超級塊。如果
它不能找到一個匹配的文件系統名稱,如果核心內建了可以支持核心模塊按需加載
(參見第 12 章),就可以繼續。這種情況下,在繼續之前,核心會請求核心守護
進程加載適當的文件系統模塊。
 
參見 fs/super.c do_mount()
 
參見 fs/super.c get_fs_type()


 
 
 
    第二步,如果 mount 傳遞的物理設備還沒有安裝,必須找到即將成爲新的文
件系統的安裝點的目錄的 VFS inode 。這個 VFS inode 可能在 inode cache 或
者必須從支撐這個安裝點的文件系統的塊設備上讀取。一旦找到了這個 inode ,
就檢查它是否是一個目錄,而且沒有其他文件系統安裝在那裏。同一個目錄不能用
作多於一個文件系統的安裝點。
 
    這時,這個 VFS 安裝代碼必須分配以一個 VFS 超級塊並傳遞安裝信息給這個
文件系統的超級塊讀取例程。系統所有的 VFS 超級塊都保存在 super_block 數據
結構組成的 super_blocks 向量表中,必須爲這次安裝分配一個結構。超級塊讀取
例程必須根據它從物理設備讀取得信息填充 VFS 超級塊的域。對於 EXT2 文件系
統而言,這種信息的映射或者轉換相當容易,它只需要讀取 EXT2 的超級塊並填到
 VFS 超級塊。對於其他文件系統,例如 MS DOS 文件系統,並不是這麼簡單的任
務。不管是什麼文件系統,填充 VFS 超級塊意味着必須從支持它的塊設備讀取描
述該文件系統的信息。如果塊設備不能讀取或者它不包含這種類型的文件系統,
mount 命令會失敗。
 
    每一個安裝的文件系統用一個 vfsmount 數據結構描述,參見圖 9.6 。它們
在 vfsmntlist 指向的一個列表中排隊。另一個指針, vfsmnttail 指向列表中最
後一個條目,而 mru_vfsmnt 指針指向最近使用的文件系統。每一個 vfsmount 結


構包括存放這個文件系統的塊設備的設備編號,文件系統安裝的目錄和一個指向這
個文件系統安裝時所分配的 VFS 超級塊的指針。 VFS 超級塊指向這一類型的文件
系統的 file_system_type 數據結構和這個文件系統的 root inode 。這個 inode
 在這個文件系統加載過程中一直駐留在 VFS inode cache 中。
 
參見 fs/super.c add_vfsmnt()
 
9.2.5 Finding a File in the Virtual File System (在虛擬文件系統中查找一
個文件)
 
    爲了找到一個文件在虛擬文件系統中的 VFS inode , VFS 必須依次名稱,一
次一個目錄,找到中間的每一個的目錄的 VFS inode 。每一個目錄查找都要調用
和文件系統相關的查找例程(地址放在代表父目錄的 VFS inode 中)。因爲在文
件系統的 VFS 超級塊中總是有文件系統的 root inode ,並在超級塊中用指針指
示,所以整個過程可以繼續。每一次查找真實文件系統中的 inode 的時候,都要
檢查這個目錄的目錄緩存。如果目錄緩存中沒有這個條目,真實文件系統要麼從底
層的文件系統要麼從 inode cache 中獲得 VFS inode 。
 
9.2.6 Unmounting a File System (卸載一個文件系統)
 
    我的工作手冊通常把裝配描述成爲拆卸的反過程,但是對於卸載文件系統有些
不同。如果系統有東西在使用文件系統的一個文件,那麼這個文件系統就不能被卸


載。例如,如果一個進程在使用 /mnt/cdrom 目錄或它的子目錄,你就不能卸載
/mnt/cdrom 。如果有東西在使用要卸載的文件系統,那麼它的 VFS inode 會在
VFS inode cache 中。卸載代碼檢查整個 inode 列表,查找屬於這個文件系統所
佔用的設備的 inode 。如果這個安裝的文件系統的 VFS 超級塊是 dirty ,就是
它被修改過了,那麼它必須被寫回到磁盤上的文件系統。一旦它寫了磁盤,這個
VFS 超級塊佔用的內存就被返回到核心的空閒內存池中。最後,這個安裝的
vmsmount 數據結構也從 vfsmntlist 中刪除並釋放。
 
參見 fs/super.c do_umount()
 
參見 fs/super.c remove_vfsmnt()  
 
The VFS Inode Cache
 
 
 
當遊歷安裝的文件系統的時候,它們的 VFS inode 不斷地被讀取,有時是寫入。
虛擬文件系統維護一個 inode cache ,用於加速對於所有安裝的文件系統的訪問
。每一次從 inode cache 讀出一個 VFS inode ,系統就可以省去對於物理設備的
訪問。
 
參見 fs/inode.c


 
    VFS inode cache 用散列表( hash table )的方式實現,條目是指針,指向
既有同樣 hash value 的 VFS inode 列表。一個 inode 的 hash value 從它的
inode 編號和包括這個文件系統的底層的物理設備的設備編號中計算出來。不論何
時虛擬文件系統需要訪問一個 inode ,它首先查看 VFS inode cache 。爲了在
inode hash table 中查找一個 inode ,系統首先計算它的 hash value ,然後用
它作爲 inode hash table 的索引。這樣得到了具有相同 hash value 的 inode
的列表的指針。然後它一次讀取所有的 inode 直到它找到和它要找的 inode 具有
相同的 inode 編號和相同的設備標識符的 inode 爲止。
 
    如果可以在 cache 中找到這個 inode ,它的 count 就增加,表示它有了另
一個用戶,文件系統的訪問繼續進行。否則必須找到一個空閒的 VFS inode 讓文
件系統把 inode 讀入到內存。如何得到一個空閒的 inode , VFS 有一系列選擇
。如果系統可以分配更多的 VFS inode ,它就這樣做:它分配核心頁並把它們分
成新的、空閒的 inode ,放到 inode 列表中。系統中所有的 VFS inode 除了在
 inode hash table 中之外也在一個由 first_inode 指向的列表。如果系統已經
擁有了它允許有的所有的 inode ,它必須找到一個可以重用的 inode 。好的候選
是哪些使用量( count )是 0 的 inode :這表示系統當前沒有使用它們。真正
重要的 VFS inode ,例如文件系統的 root inode ,已經有了一個大於 0 的使用
量,所以永遠不會選做重用。一旦定位到一個重用的候選,它就被清除。這個 VFS
 inode 可能是髒的,這時系統必須等待它被解鎖然後才能繼續。在重用之前這個
 VFS inode 的候選必須被清除。


 
    雖然找到了一個新的 VFS inode ,還必須調用一個和文件系統相關的例程,
用從底層的真正的文件系統中毒取得信息填充這個 inode 。當它填充的時候,這
個新的 VFS inode 的使用量爲 1 ,並被鎖定,所以在它填入了有效的信息之前除
了它沒有其它進程可以訪問。
 
    爲了得到它實際需要的 VFS inode ,文件系統可能需要訪問其它一些
inode 。這發生在你讀取一個目錄的時候:只有最終目錄的 inode 是需要的,但
是中間目錄的 inode 也必須讀取。當 VFS inode cache 使用過程並填滿時,較少
使用的 inode 會被廢棄,較多使用的 inode 會保留在高速緩存中。
 
 
 
 
The Directory Cache (目錄緩存)
 
    爲了加速對於常用目錄的訪問, VFS 維護了目錄條目的一個高速緩存。當真
正的文件系統查找目錄的時候,這些目錄的細節就被增加到了目錄緩存中。下一次
查找同一個目錄的時候,例如列表或打開裏邊的文件,就可以在目錄緩存中找到。
只有短的目錄條目(最多 15 字符)被緩存,不過這是合理的,因爲較短的目錄名
稱是最常用的。例如:當 X 服務器啓動的時候, /usr/X11R6/bin 非常頻繁地被
訪問。


 
參見 fs/dcache.c
 
    目錄緩存中包含一個 hash table ,每一個條目都指向一個具有相同的
hash value 的目錄緩存條目的列表。 Hash 函數使用存放這個文件系統的設備的
設備編號和目錄的名稱來計算在 hash table 中的偏移量或索引。它允許快速找到
緩存的目錄條目。如果一個緩存在查找的時候花費時間太長,或根本找不到,這樣
的緩存是沒有用的。
 
    爲了保持這些 cache 有效和最新, VFS 保存了一個最近最少使用( LRU )
目錄緩存條目的列表。當一個目錄條目第一次被放到了緩存,就是當它第一次被查
找的時候,它被加到了第一級 LRU 列表的最後。對於充滿的 cache ,這會移去
LRU 列表前面存在的條目。當這個目錄條目再一次被訪問的時候,它被移到了第二
個 LRU cache 列表的最後。同樣,這一次它移去了第二級 LRU cache 列表前面的
二級緩存目錄條目。這樣從一級和二級 LRU 列表中移去目錄條目是沒有問題的。
這些條目之所以在列表的前面只是因爲它們最近沒有被訪問。如果被訪問,它們會
在列表的最後。在二級 LRU 緩存列表中的條目比在一級 LRU 緩存列表中的條目更
加安全。因爲這些條目不僅被查找而且曾經重複引用。
 
 
 
The Buffer Cache


 
 
    當使用安裝的文件系統的時候,它們會對塊設備產生大量的讀寫數據塊的請求
。所有的塊數據讀寫的請求都通過標準的核心例程調用,以 buffer_head 數據結
構的形式傳遞給設備驅動程序。這些數據結構給出了設備驅動程序需要的所有信息
:設備標識符唯一標識了設備,塊編號告訴了驅動程序讀去哪一塊。所有的塊設備
被看成同樣大小的塊的線性組合。爲了加速對於物理塊設備的訪問, Linux 維護
了一個塊緩衝區的緩存。系統中所有的塊緩衝區動保存在這個 buffer cache ,甚
至包括那些新的、未使用的緩衝區。這個緩存區被所有的物理塊設備共享:任何時
候緩存區中都有許多塊緩衝區,可以屬於任何一個系統塊設備,通常具有不同的狀
態。如果在 buffer cache 中有有效的數據,這就可以節省系統對於物理設備的訪
問。任何用於從 / 向塊設備讀取 / 寫入數據的塊緩衝區都進入這個 buffer
cache 。隨着時間的推移,它可能從這個緩存區中刪除,爲其它更需要的緩衝區讓
出空間,或者如果它經常被訪問,就可能一直留在緩存區中。
 
    這個緩存區中的塊緩衝區用這個緩衝區所屬的設備標識符和塊編號唯一標識。
這個 buffer cache 由兩個功能部分組成。第一部分是空閒的塊緩衝區列表。每一
個同樣大小的緩衝區(系統可以支持的)一個列表。系統的空閒的塊緩衝區當第一
次創建或者被廢棄的時候就在這些列表中排隊。當前支持的緩衝區大小是 512 、
 1024 、 2048 、 4096 和 8192 字節。第二個功能部分是緩存區( cache )本
身。這是一個 hash table ,是一個指針的向量表,用於鏈接具有相同 hash
index 的 buffer 。 Hash index 從數據塊所屬的設備標識符和塊編號產生出來。


圖 9.7 顯示了這個 hash table 和一些條目。塊緩衝區要麼在空閒列表之一,要
麼在 buffer cache 中。當它們在 buffer cache 的時候,它們也在 LRU 列表中
排隊。每一個緩衝區類型一個 LRU 列表,系統使用這些類型在一種類型的緩衝區
上執行操作。例如,把有新數據的緩衝區寫到磁盤上。緩衝區的類型反映了它的狀
態, Linux 當前支持以下類型:
 
clean 未使用,新的緩衝區( buffer )
 
locked 鎖定的緩衝區,等待被寫入
 
dirty 髒的緩衝區。包含新的有效的數據,將被寫到磁盤,但是直到現在還沒有調
度到寫
 
shared 共享的緩衝區
 
unshared 曾經共享的緩衝區,但是現在沒有共享
 
    不論何時文件系統需要從它的底層的物理設備讀取一個緩衝區的時候,它都試
圖從 buffer cache 中得到一個塊。如果它不能從 buffer cache 中得到一個緩衝
區,它就從適當大小的空閒列表中取出一個乾淨的緩衝區,這個新的緩衝區會進入
到 buffer cache 中。如果它需要的緩衝區已經在 buffer cache 中,那麼它可能
是也可能不是最新。如果它不是最新,或者它是一個新的塊緩衝區,文件系統必須


請求設備驅動程序從磁盤上讀取適當的數據塊。
 
    象所有的高速緩存一樣, buffer cache 必須被維護,這樣它纔能有效地運行
,並在使用 buffer cache 的塊設備之間公平地分配緩存條目。 Linux 使用核心
守護進程 bdflush 在這個緩存區上執行大量的整理工作,不過另一些是在使用緩
存區的過程中自動進行的。
 
9.3.1 The bdflush Kernel Daemon (核心守護進程 bdflsuh )
 
    核心守護進程 bdflush 是一個簡單的核心守護進程,對於有許多髒的緩衝區
(包含必須同時寫到磁盤的數據的緩衝區)的系統提供了動態的響應。它在系統啓
動的時候作爲一個核心線程啓動,相當容易混淆,它叫自己位“ kflushd ”,而
這是你用 ps 顯示系統中的進程的時候你會看得的名字。這個進程大多數時間在睡
眠,等待系統中髒的緩衝區的數目增加直到太巨大。當緩衝區分配和釋放的時候,
就檢查系統中髒的緩衝區的數目,然後喚醒 bdflush 。缺省的閾值是 60% ,但是
如果系統非常需要緩衝區, bdflush 也會被喚醒。這個值可以用 updage 命令檢
查和設置:
 
#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).
 
    不管什麼時候寫入了數據,成爲了髒的緩衝區,所有的髒的緩衝區都鏈接在
BUF_DIRTY LRU 列表中, bdflush 會嘗試把合理數目的緩衝區寫到它們的磁盤中


。這個數目也可以用 update 命令檢查和設置,缺省是 500 (見上例)。
 
9.3.2 The update Process ( update 進程)
 
update 命令不僅僅是一個命令,它也是一個守護進程。當以超級用戶身份(系統
初始化)運行的時候,它會定期把所有舊的髒緩衝區寫到磁盤上。它通過調用系統
服務例程執行這些任務,或多或少和 bdflush 的任務相同。當生成了一個髒緩衝
區的時候,都標記上它應該被寫到它自己的磁盤上的系統時間。每一次 update 運
行的時候,它都查看系統中所有的髒的緩衝區,查找具有過期的寫時間的緩衝區。
每一個過期的緩衝區都被寫到磁盤上。
 
參見 fs/buffer.c sys_bdflush()
 
The /proc File System
 
    /proc 文件系統真實地體現了 Linux 虛擬文件系統的能力。它實際上並不存
在(這也是 Linux 另外一個技巧), /proc 和它的子目錄以及其中的文件都不存
在。但是爲什麼你可以 cat /proc/devices ? /proc 文件系統,象一個真正的文
件系統一樣,也向虛擬文件系統登記自己,但是當它的文件和目錄被打開, VFS
執行調用請求它的 inode 的時候, /proc 文件系統才利用核心中的信息創建這些
文件和目錄。例如,核心的 /proc/devices 文件是從核心描述它的設備的數據結
構中產生出來的。


 
/proc 文件系統代表了一個用戶可讀的窗口,進入核心的內部工作空間。一些
Linux 子系統,例如第 12 章描述的 Linux 核心模塊,都在 /proc 文件系統中創
建條目。
 
Device Special Files
 
    Linux ,象所有版本的 Unix 一樣,把它的硬件設備表示成爲特殊文件。例如
, /dev/null 是空設備。設備文件不在文件系統中佔用任何數據空間,它只是設
備驅動程序的一個訪問點。 EXT2 文件系統和 Linux 的 VFS 都把設備文件作爲特
殊類型的 inode 。有兩種類型的設備文件:字符和塊特殊文件。在覈心內部本身
,設備驅動程序都實現文件的基本操作:你可以打開、關閉等等。字符設備允許字
符模式的 I/O 操作,而塊設備要求所有的 I/O 通過 buffer cache 。當對於一個
設備文件執行一個 I/O 請求的時候,它被轉到系統中適當的設備驅動程序。通常
這不是一個真正的設備驅動程序,而是一個用於子系統的僞設備驅動程序(
pseudo-device driver )例如 SCSI 設備驅動程序層。設備文件用主設備編號(
標識設備類型)和次類型來引用(用於標識單元,或者主類型的實例)。例如,對
於系統中的第一個 IDE 控制器上的 IDE 磁盤,主設備編號是 3 , IDE 磁盤的第
一個分區的次設備編號應該是 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 域被設成正確的設備標識符。
 
參見 include/linux/kdev_t.h

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章