第14章 檔案系統
在第4章、第5章和第13章中,我們介紹了檔案I/O,特別是普通磁碟檔案。在本節和接下來的章節中,我們會深入探究一系列檔案相關的主題:
- 本章主要探究檔案系統
- 第15章主要闡述各種與檔案相關的屬性,包括時間戳、所屬權和許可權
- 第16章和17章介紹Linux2.6的兩個新特性:擴充套件屬性(extended attributes) 和 訪問控制列表(ACLs)。擴充套件屬性可將任意元資料與一檔案關聯,而ACL則是傳統UNIX檔案許可權模型的擴充套件。
- 第18章討論目錄和連結。
本章主要與檔案系統有關,檔案系統是檔案和目錄的集合。我們將介紹一系列檔案系統相關的概念,有時使用傳統的Linux ext2檔案系統作為具體的例子。此外,本章還會簡要介紹一些Linux支援的日誌檔案系統。
在本章結尾,將會討論用於掛載(mount)和解除安裝(unmount)檔案系統的系統呼叫,以及用來獲取已掛載檔案系統資訊的庫函式。
14.1 Device Special Files (Devices)(裝置專用檔案)
本章會頻繁地提到 磁碟裝置(disk devices),所以我們首先對裝置檔案的概念做一個簡單的介紹。
裝置專用檔案(device special file) 對應於系統中的一個裝置。在核心中,每種 裝置型別 都有對應的一個 裝置驅動器 ,用於處理裝置的所有I/O請求。裝置驅動器(device driver)是核心程式碼的單元,可執行一系列操作,通常與相關硬體的輸入/輸出動作相對應。由裝置驅動程式提供的API是固定的,包含的操作對應於系統呼叫open()、close()、read()、write()、mmap()以及ioctl()。每個裝置驅動程式所提供的介面一致,這隱藏了每個裝置在操作方面的差異,從而滿足了I/O操作的通用型(請參見4.2節)
某些裝置時真實存在的,比如滑鼠、鍵盤和磁帶裝置。而另一些裝置是虛擬的,亦即並不存在相應的硬體,但核心(通過裝置驅動程式)提供一種抽象裝置,其所攜帶的API與真實裝置一般無異。
可將裝置劃分為以下兩種型別。
- 字元裝置(character devices) 基於每個字元來處理資料。終端和鍵盤都是字元裝置的例子
- 塊裝置(Block devices) 每次處理一塊資料。塊的大小取決於裝置型別,但通常是512位元組的倍數。磁碟和磁帶裝置都屬於塊裝置。
像其他檔案一樣,裝置檔案出現在檔案系統中,通常在 /dev 目錄下。超級使用者可以通過使用 mknod 命令建立裝置檔案,在特權程式中使用mknode()系統呼叫可以完成相同的任務。
本書不會對mknode()(make file-system i-node建立,檔案系統i節點)系統呼叫做詳細介紹,因為該系統呼叫的用法一目瞭然,並且如今它的唯一用處是用於建立裝置檔案,這不是一般應用所需要用到的。當然,也可以使用mknod()建立FIFO(參見44.7節),但最好使用mkfifo()函式來完成該任務。早先,某些UNIX實現會使用mknod()來建立目錄,但如今已為mkdir()系統呼叫所取代。然而,還有一些UNIX實現(Linux不在此列),為了保持向後相容性,仍然在mknod()中保留了這一能力,詳情見mknode(2)手冊頁。
在早期的Linux版本中,/dev包含系統中所有可能的裝置條目,即使這些裝置沒有真正地連線到系統上。這意味著/dev會包含數以千計的未用裝置項,從而導致兩個缺點:其一,對於需要掃描該目錄內容的應用而言,降低了程式的可執行速度;其二,根據該目錄下的內容無法發現系統中實際存在哪些裝置。Linux2.6運用了udev程式解決了上述問題。該程式所依賴的sysfs檔案系統是裝載於/sys下的偽檔案系統,將裝置和其他核心物件的相關資訊匯出至使用者空間。
Device IDs
每個裝置檔案都有一個 major ID number 和 minor ID number。major ID用於識別裝置的類別,用於核心為該裝置型別查詢合適的驅動器。minor ID用於唯一標識該類別中的特定類別。裝置檔案的major ID和minor ID通過使用 ls -l 命令展示。
裝置的major ID和minor ID記錄在裝置檔案的i-node上(在14.4中介紹i-nodes)。每個裝置驅動器都會將與之關聯的major ID進行註冊,使用這種方法建立裝置特有檔案和裝置驅動器之間的關係。核心是不會使用裝置檔名來查詢驅動器程式的。
在Linux 2.4以及更早的版本中,系統的裝置總數受限於這一事實:裝置的major ID和minor ID只能用8位數來表示,加之major ID固定不變,且為統一分配(由Linux命名和編號機構分配),使得上述問題更為嚴重。Linux 2.6採用了更多位數來存放major ID和minor ID(分別是12位和20位),從而緩解了這個問題。
14.2 Disks and Partitions
常規檔案和目錄一般駐紮在硬體磁碟裝置中。(檔案和目錄還可能存在於其他裝置,例如CD-ROMS,flash記憶體卡和虛擬磁碟中,但是對於當前的討論,我們主要關心硬體磁碟裝置)。下面幾節會介紹磁碟的組織方式以及如何對其分割槽。
14.3 檔案系統
檔案系統是對常規檔案和目錄的組織集合。用於建立檔案系統的命令是 mkfs。Linux的強項之一便是支援種類繁多的檔案系統,如下所示。
- 傳統的ext2檔案系統
- 各種原生(native)UNIX檔案系統,比如,Minix、System V 以及BSD檔案系統。
- 微軟的FAT,FAT32以及NTFS檔案系統。
- Apple Macintosh的HFS。
- 一系列網路檔案系統
- 一系列日誌檔案系統,包括ext3、ext4、Reiserfs、JFS、XFS以及Btrfs。
從Linux的專有檔案 /proc/filesystems 中key檢視當前為核心所知的檔案系統型別。
ext2 檔案系統
多年來,ext2是Linux上使用最廣泛的檔案系統,也是原始Linux檔案系統–ext的繼任者。近來,隨著各種日誌檔案系統的興起,對ext2的使用也日趨減少。有時,在介紹通用檔案系統概念時,以一款特定的檔案系統實現為例會容易一些,出於這一目的,本章將以ext2為例來介紹檔案系統。
檔案系統結構
檔案系統中用於分配空間的基本單元是 邏輯塊(logical block),它是檔案系統駐紮的磁碟裝置上多個連續的物理塊。例如,在ext2檔案系統上,邏輯塊的大小為1024,、2048或4096位元組。
Figure 14-1 描述了磁碟分割槽和檔案系統之間的關係,以及一般檔案系統的組成。
檔案系統包含以下部分:
- 引導塊(Boot block): 總是作為檔案系統的首塊。引導塊不為檔案系統所用,只是包含用來引導作業系統的資訊。作業系統雖然只需要一個引導塊,但所有檔案系統都設有引導塊(其中的絕大多數都未使用)
- 超級塊(Super block): 緊隨引導塊之後的一個獨立塊,包含與檔案系統有關的引數資訊,其中包括:
- i-node表的容量
- 檔案系統中邏輯塊的大小
- 以邏輯塊計,檔案系統的大小
不同的檔案系統駐紮在相同的物理裝置上可以具有不同的型別和大小,以及不同的引數設定(例如塊大小)。這是將磁碟分成多個分割槽的原因。
- I節點表(I-node table): 檔案系統中的每個檔案或目錄在i-node table中都有唯一的條目。這些條目記錄了關於檔案的各種資訊。在下節中會對I-node進行詳細的介紹。i-node table有時也稱為i-list。
- 資料塊(Data blocks): 檔案系統中的大部分空間都用於資料塊,檔案和目錄駐紮在檔案系統的資料塊中。
14.4 I-nodes
檔案系統的i-node table中為每個駐紮在檔案系統中的檔案維護中一個i-node(index node的簡寫)。對I-nodes的標識,是通過i-node table中的順序位置,以數字表示。ls -li 命令所展示的第一個欄位是檔案的i-node number (或簡寫為i-number)。i-node中包含以下資訊:
- 檔案型別(例如常規檔案、目錄、符號連結、字元裝置)
- 檔案的屬主(也稱為user ID 或 UID)
- 檔案的屬組(也稱為group ID 或 GID)
- 3類使用者的訪問許可權:屬主(有時也稱為使用者)、屬組以及其他使用者(屬主和屬組使用者之外的使用者)
- 3個時間戳:對檔案的最後訪問時間(ls -lu 所顯示地時間)、對檔案的最後修改時間(也是 ls -l所預設的顯示時間),以及檔案狀態的最後改變時間(ls -lc 所顯示的最後改變i節點資訊的時間)。值得注意的是,與其他UNIX實現一樣,大多數Linux檔案系統不會記錄檔案的建立時間。
- 指向檔案的硬連結數量
- 檔案的大小,以位元組為單位
- 實際分配給檔案的塊數量,以512位元組塊為單位。這一數字可能會簡單等同於檔案的位元組大小,因為考慮檔案中包含空洞的情形,分配給檔案的塊數可能會低於根據檔案正常大小(以位元組為單位)所計算出的塊數。
- 指向檔案資料塊的指標。
ext2中的I-nodes和資料塊指標
像其他UNIX檔案系統一樣,ext2檔案系統沒有連續及有序地儲存檔案的資料塊。為了定位這些這些檔案資料塊,核心在i-node中維護了一組指標。如圖14-2
無需連續儲存檔案塊,使得檔案系統對磁碟空間的利用更為高效。特別是,還能降低空閒磁碟空間的碎片化程度,即因眾多不連續空閒磁碟碎片(因其空間太小而無法使用)而導致地磁碟空間浪費。換言之,對空閒磁碟的高效利用,是以分配磁碟空間中檔案的碎片化為代價的。
在ext2下,每個i-node包含15個指標。前面的12個指標(Figure 14-2中編號為0到11的指標)指向檔案前12個塊在檔案系統中的位置,接下來是一個指向指標塊的指標,提供了檔案的第13個以及後續資料塊的位置。指標塊中指標的數量取決於檔案系統中塊的大小。每個指標需要佔用4個位元組,因此指標的數量在256(塊容量為1024位元組)~1024(塊容量為4096位元組)之間。這樣就考慮了大型檔案的情況。即便對於巨型檔案,第14個指標(Figure 14-2中編號為13)是一個雙重間接指標-指向指標塊,其塊中指標進而指向指標塊,此塊中指標最終才指向檔案的資料塊。只要有體量巨大的檔案,就會隨之產生更深一層的遞進:Figure 14-2中i-node的最後一個指標屬於三重間接指標。
這一貌似複雜的系統,其設計意圖是為了滿足多重需求。首先,該系統在維持i節點結構大小固定的同事,支援任意大小的檔案。其次,檔案系統既可以以不連續的方式來儲存檔案塊,又可以通過lseek()隨機訪問檔案,而核心只需計算所遵循的指標。最後,對於大多數系統中佔絕對多數的小檔案而言,這種設計滿足了對檔案資料塊的快速訪問:通過i-node的直接指標訪問,一擊必中。
上述設計同樣考慮了巨型檔案的處理,對於大小為4906位元組的塊而言,理論上,檔案大小可略高於102410241024*4096位元組,或4TB(4096GB)。(之所以說“略高於”,是因為指標指向塊的方式可以為直接、間接或雙重間接。與三重間接指標所指向的範圍比,多出來的那些空間實在是微不足道。)
該設計的另一個優點在於檔案可以有黑洞(如4.7節所述)。檔案系統值需將i節點和間接指標塊中的相應指標打上標記(值0),表明這些指標並未指向實際的磁碟塊即可,而無需為檔案黑洞分配空位元組資料塊。
14.5 虛擬檔案系統(VFS)
Linux 所支援的各種檔案系統,其實現細節均不相同。舉例來說,這些差異包括檔案塊的分配方式,以及目錄的組織方式。如果每個與檔案打交道的程式都需要理解各種檔案系統的具體細節,那麼編寫與各類檔案系統互動的程式將是不可能完成的任務。虛擬檔案系統(virtual file system,VFS,有時也稱為virtual file switch,虛擬檔案交換)是一種核心特性,通過檔案系統操作建立抽象層來解決上述問題(參見圖14-3)。
VFS背後的原理其實很直白:
- VFS針對檔案系統定義了一套通用介面。所有與檔案互動的程式都會按照這一介面來操作。
- 每種檔案系統都會提供VFS介面的實現。
這樣一來,程式只需理解VFS介面,而無需過問具體檔案系統的實現細節。
VFS介面的操作與涉及檔案系統和目錄的所有常規系統呼叫相對應,這些系統呼叫有open()、read()、wirte()、lseek()、close()、truncate()、stat()、mount()、umount()、mmap()、mkdir()、link()、unlink()、symlink()以及rename() 。
VFS的抽象層建模精確仿照傳統的UNIX檔案系統模型。
14.6 日誌檔案系統
ext2檔案系統的缺點是:系統崩潰之後,為確保檔案系統的完整性,重啟時必須對檔案系統的一致性進行檢查(fsck)。由於系統每次崩潰時,對檔案的更新可能只完成了一部分,而檔案系統元資料(目錄項,i-node資訊以及檔案資料塊指標)也將處於不一致狀態,一旦這個問題得不到修復,那麼檔案系統會遭到進一步破壞,因此上述舉措實屬必要。如有可能,就必須進行修復,否則將會丟棄那些無法獲取的資訊。
問題在於,一致性檢查需要遍歷整個檔案系統。如果檔案系統較小,只需要幾秒鐘或者幾分鐘便可完成。而在大型檔案系統上,上述操作可能要數小時。
採用日誌檔案系統,則無需在系統崩潰後對檔案進行漫長的一致性檢查。在實際更新元資料之前,日誌檔案系統會將這些更新操作記錄於專用的磁碟日誌檔案中。對元資料更新的記錄是按其相關性分組(以事務的方式記錄)進行的。在事務處理過程中,一旦系統崩潰,系統重啟時可利用日誌重做任何不完整的更新,同時為檔案系統恢復一致性狀態。系統崩潰之後,即便是超大型的日誌檔案系統,通常也會在幾秒之內復原,因而對於有高可用性需求的系統及其具又吸引力。日誌檔案系統最大的缺點是增加了檔案更新的時間。
某些日誌檔案系統只會確保檔案元資料的一致性。由於不記錄檔案資料,因此一旦系統崩潰,可能會造成資料丟失。ext3、ext4和Reiserfs檔案系統提供了記錄資料更新的選項,但若記錄的東西過多,則會降低檔案I/O的效能。
14.10 虛擬記憶體檔案系統:tmpfs
本章中,我們目前所討論的所有檔案系統都是駐紮在磁碟上的。但是,Linux還支援駐紮在記憶體中的 虛擬檔案系統(virtual file systems) 。對於應用來說,虛擬檔案系統看上去就跟其他檔案系統一樣,在虛擬檔案系統上也可以對檔案和目錄使用相同的操作(open()、read()、write()、link()、mkdir())。但是一個很重要的不同是:虛擬檔案系統的檔案操作更加快速,因為不涉及磁碟訪問。
為Linux開發了各種基於記憶體的檔案系統。其中最複雜的要屬 tmpfs 檔案系統,它在Linux2.4中首次出現。tmpfs檔案系統與其他基於記憶體的檔案系統的不同之處在於 虛擬(virtual) 記憶體檔案系統。也就是說,tmpfs不但使用RAM,而且當RAM用完時還會利用交換空間(swap space)。
tmpfs檔案系統是一個可選的Linux核心元件,通過CONFIG_TMPFS選項進行配置。
使用以下命令來建立tmpfs檔案系統:
mount -t tmpfs source target
source 可以是任意的名字。
14.12 總結
裝置都由/dev下的檔案來表示。每個裝置都有相應的裝置驅動程式,用以執行一套標準的操作,與之對應的系統呼叫包括open()、read()、write()和close()。裝置既可以是實際存在的,也可以是虛擬的,這分別表明了硬體裝置的存在與否。無論如何,核心都會提供一種裝置驅動程式,並實現與真實裝置相同的API。
可以將硬碟劃分為一個或多個分割槽,每個分割槽都可包含一個檔案系統。檔案系統是對常規檔案和目錄的組織集合。Linux實現的檔案系統多種多樣,其中包括傳統的ext2檔案系統。ext2檔案系統在概念上類似於早期的UNIX檔案系統,由引導塊、超級塊、i-node table和包含檔案資料塊的資料區域組成。每個檔案在檔案系統i-node table上都有一條對應記錄,記錄了與檔案相關的各種資訊,其中包括檔案型別、大小、連結數、所有權、許可權、時間戳,以及指向檔案資料塊的指標。
Linux 還提供了若干日誌檔案系統,其中包括Reiserfs、ext3、ext4、XFS、JFS以及Btrfs。在實際更新檔案之前,日誌檔案系統會記錄元資料更新(還可有選擇地記錄資料更新和檔案系統更新)。這也意味著,一旦系統崩潰,系統可以重放(replay)日誌檔案,並迅速將檔案系統恢復到一致狀態。日誌檔案系統的最大優點在於系統崩潰後,無需像常規UNIX檔案系統那樣對檔案系統進行漫長的一致性檢查。
Linux系統上的所有檔案系統都掛載於單根目錄樹之下,其數根目錄"/"。目錄樹中掛載檔案系統的位置被稱為檔案系統掛載點。
特權級程序可以使用mount()和umount()系統呼叫來掛載、解除安裝檔案系統。可使用statvfs()來獲取與已掛載檔案系統有關的資訊。