Linux檔案系統的實現

ii_chengzi發表於2020-02-16

Linux檔案管理從使用者的層面介紹了Linux管理檔案的方式。Linux有一個樹狀結構來組織檔案。樹的頂端為根目錄(/),節點為目錄,而末端的葉子為包含資料的檔案。當我們給出一個檔案的完整路徑時,我們從根目錄出發,經過沿途各個目錄,最終到達檔案。

我們可以對檔案進行許多操作,比如開啟和讀寫。在Linux檔案管理相關命令中,我們看到許多對檔案進行操作的命令。它們大都基於對檔案的開啟和讀寫操作。比如cat可以開啟檔案,讀取資料,最後在終端顯示:

對於Linux下的程式設計師來說,瞭解檔案系統的底層組織方式,是深入進行系統程式設計所必備的。即使是普通的Linux使用者,也可以根據相關的內容,設計出更好的系統維護方案。

儲存裝置分割槽

檔案系統的最終目的是把大量資料有組織的放入永續性(persistant)的儲存裝置中,比如硬碟和磁碟。這些儲存裝置與記憶體不同。它們的儲存能力具有永續性,不會因為斷電而消失;儲存量大,但讀取速度慢。

觀察常見儲存裝置。最開始的區域是MBR,用於Linux開機啟動(參考Linux開機啟動)。剩餘的空間可能分成數個分割槽(partition)。每個分割槽有一個相關的分割槽表(Partition table),記錄分割槽的相關資訊。這個分割槽表是儲存在分割槽之外的。分割槽表說明了對應分割槽的起始位置和分割槽的大小。

我們在Windows系統常常看到C分割槽、D分割槽等。Linux系統下也可以有多個分割槽,但都被掛載在同一個檔案系統樹上。

資料被存入到某個分割槽中。一個典型的Linux分割槽(partition)包含有下面各個部分:

分割槽的第一個部分是啟動區(Boot block),它主要是為計算機開機服務的。Linux開機啟動後,會首先載入MBR,隨後MBR從某個硬碟的啟動區載入程式。該程式負責進一步的作業系統的載入和啟動。為了方便管理,即使某個分割槽中沒有安裝作業系統,Linux也會在該分割槽預留啟動區。

啟動區之後的是超級區(Super block)。它儲存有檔案系統的相關資訊,包括檔案系統的型別,inode的數目,資料塊的數目。

隨後是多個inodes,它們是實現檔案儲存的關鍵。在Linux系統中,一個檔案可以分成幾個資料塊儲存,就好像是分散在各地的龍珠一樣。為了順利的收集齊龍珠,我們需要一個“雷達”的指引:該檔案對應的inode。每個檔案對應一個inode。這個inode中包含多個指標,指向屬於該檔案各個資料塊。當作業系統需要讀取檔案時,只需要對應inode的”地圖”,收集起分散的資料塊,就可以收穫我們的檔案了。

最後一部分,就是真正儲存資料的資料塊們(data blocks)了。

inode簡介

上面我們看到了儲存裝置的巨集觀結構。我們要深入到分割槽的結構,特別是檔案在分割槽中的儲存方式。

檔案是檔案系統對資料的分割單元。檔案系統用目錄來組織檔案,賦予檔案以上下分級的結構。在硬碟上實現這一分級結構的關鍵,是使用inode來虛擬普通檔案和目錄檔案物件。

Linux檔案管理中,我們知道,一個檔案除了自身的資料之外,還有一個附屬資訊,即檔案的後設資料(metadata)。這個後設資料用於記錄檔案的許多資訊,比如檔案大小,擁有人,所屬的組,修改日期等等。後設資料並不包含在檔案的資料中,而是由作業系統維護的。事實上,這個所謂的後設資料就包含在inode中。我們可以用$ls -l filename來檢視這些後設資料。正如我們上面看到的,inode所佔據的區域與資料塊的區域不同。每個inode有一個唯一的整數編號(inode number)表示。

在儲存後設資料,inode是“檔案”從抽象到具體的關鍵。正如上一節中提到的,inode儲存由一些指標,這些指標指向儲存裝置中的一些資料塊,檔案的內容就儲存在這些資料塊中。當Linux想要開啟一個檔案時,只需要找到檔案對應的inode,然後沿著指標,將所有的資料塊收集起來,就可以在記憶體中組成一個檔案的資料了。

資料塊在1, 32, 0, …

inode並不是組織檔案的唯一方式。最簡單的組織檔案的方法,是把檔案依次順序的放入儲存裝置,DVD就採取了類似的方式。但如果有刪除操作,刪除造成的空餘空間夾雜在正常檔案之間,很難利用和管理。

複雜的方式可以使用連結串列,每個資料塊都有一個指標,指向屬於同一檔案的下一個資料塊。這樣的好處是可以利用零散的空餘空間,壞處是對檔案的操作必須按照線性方式進行。如果想隨機存取,那麼必須遍歷連結串列,直到目標位置。由於這一遍歷不是在記憶體進行,所以速度很慢。

FAT系統是將上面連結串列的指標取出,放入到記憶體的一個陣列中。這樣,FAT可以根據記憶體的索引,迅速的找到一個檔案。這樣做的主要問題是,索引陣列的大小與資料塊的總數相同。因此,儲存裝置很大的話,這個索引陣列會比較大。

inode既可以充分利用空間,在記憶體佔據空間不與儲存裝置相關,解決了上面的問題。但inode也有自己的問題。每個inode能夠儲存的資料塊指標總數是固定的。如果一個檔案需要的資料塊超過這一總數,inode需要額外的空間來儲存多出來的指標。

inode示例

在Linux中,我們通過解析路徑,根據沿途的目錄檔案來找到某個檔案。目錄中的條目除了所包含的檔名,還有對應的inode編號。當我們輸入$cat /var/test.txt時,Linux將在根目錄檔案中找到var這個目錄檔案的inode編號,然後根據inode合成var的資料。隨後,根據var中的記錄,找到text.txt的inode編號,沿著inode中的指標,收集資料塊,合成text.txt的資料。整個過程中,我們參考了三個inode:根目錄檔案,var目錄檔案,text.txt檔案的inodes。

在Linux下,可以使用$stat filename,來查詢某個檔案對應的inode編號。

在儲存裝置中實際上儲存為:

當我們讀取一個檔案時,實際上是在目錄中找到了這個檔案的inode編號,然後根據inode的指標,把資料塊組合起來,放入記憶體供進一步的處理。當我們寫入一個檔案時,是分配一個空白inode給該檔案,將其inode編號記入該檔案所屬的目錄,然後選取空白的資料塊,讓inode的指標指像這些資料塊,並放入記憶體中的資料。

檔案共享

在Linux的程式中,當我們開啟一個檔案時,返回的是一個檔案描述符。這個檔案描述符是一個陣列的下標,對應陣列元素為一個指標。有趣的是,這個指標並沒有直接指向檔案的inode,而是指向了一個檔案表格,再通過該表格,指向載入到記憶體中的目標檔案的inode。如下圖,一個程式開啟了兩個檔案。

可以看到,每個檔案表格中記錄了檔案開啟的狀態(status flags),比如只讀,寫入等,還記錄了每個檔案的當前讀寫位置(offset)。當有兩個程式開啟同一個檔案時,可以有兩個檔案表格,每個檔案表格對應的開啟狀態和當前位置不同,從而支援一些檔案共享的操作,比如同時讀取。

要注意的是程式fork之後的情況,子程式將只複製檔案描述符的陣列,而和父程式共享核心維護的檔案表格和inode。此時要特別小心程式的編寫。

總結

這裡概括性的總結了Linux的檔案系統。Linux以inode的方式,讓資料形成檔案。

瞭解Linux的檔案系統,是深入瞭解操作系Linux原理的重要一步。

相關文章