簡直不要太硬了!一文帶你徹底理解檔案系統
所有的應用程式都需要儲存
和檢索
資訊。程序執行時,它能夠在自己的儲存空間記憶體儲一定量的資訊。然而,儲存容量受虛擬地址空間
大小的限制。對於一些應用程式來說,儲存空間的大小是充足的,但是對於其他一些應用程式,比如航空訂票系統、銀行系統、企業記賬系統來說,這些容量又顯得太小了。
第二個問題是,當程序終止時資訊會丟失。對於一些應用程式(例如資料庫),資訊會長久保留。在這些程序終止時,相關的資訊應該保留下來,是不能丟失的。甚至這些應用程式崩潰後,資訊也應該保留下來。
第三個問題是,通常需要很多程序在同一時刻訪問這些資訊。解決這種問題的方式是把這些資訊單獨保留在各自的程序中。
因此,對於長久儲存的資訊我們有三個基本需求:
- 必須要有可能儲存的大量的資訊
- 資訊必須能夠在程序終止時保留
- 必須能夠使多個程序同時訪問有關資訊
磁碟(Magnetic disk)
一直是用來長久儲存資訊的裝置。近些年來,固態硬碟
逐漸流行起來。
固態硬碟不僅沒有易損壞的移動部件,而且能夠提供快速的隨機訪問。相比而言,雖然磁帶和光碟也被廣泛使用,但是它們的效能相對較差,通常應用於備份。我們會在後面探討磁碟,現在姑且把磁碟當作一種大小固定塊的線性序列好了,並且支援如下操作
- 讀塊 k
- 寫塊 k
事實上磁碟支援更多的操作,但是隻要有了讀寫操作,原則上就能夠解決長期儲存的問題。
然而,磁碟還有一些不便於實現的操作,特別是在有很多程式或者多使用者使用的大型系統上(如伺服器)。在這種情況下,很容易產生一些問題,例如
你如何找到這些資訊?
你如何保證一個使用者不會讀取另外一個使用者的資料?
你怎麼知道哪些塊是空閒的?等等問題
我們可以針對這些問題提出一個新的抽象 - 檔案
。程序和執行緒的抽象、地址空間和檔案都是作業系統的重要概念。如果你能真正深入瞭解這三個概念,那麼你就走上了成為作業系統專家的道路。
檔案(Files)
是由程序建立的邏輯資訊單元。一個磁碟會包含幾千甚至幾百萬個檔案,每個檔案是獨立於其他檔案的。事實上,如果你能把每個檔案都看作一個獨立的地址空間,那麼你就可以真正理解檔案的概念了。
程序能夠讀取已經存在的檔案,並在需要時重新建立他們。儲存在檔案中的資訊必須是持久的
,這也就是說,不會因為程序的建立和終止而受影響。一個檔案只能在當用戶明確刪除的時候才能消失。儘管讀取和寫入都是最基本的操作,但還有許多其他操作,我們將在下面介紹其中的一些。
檔案由作業系統進行管理,有關檔案的構造、命名、訪問、使用、保護、實現和管理方式都是作業系統設計的主要內容。從總體上看,作業系統中處理檔案的部分稱為 檔案系統(file system)
,這就是我們所討論的。
從使用者角度來說,使用者通常會關心檔案是由什麼組成的,如何給檔案進行命名,如何保護檔案,以及可以對檔案進行哪些操作等等。儘管是用連結串列還是用點陣圖記錄記憶體空閒區並不是使用者所關心的主題,而這些對系統設計人員來說至關重要。下面我們就來探討一下這些主題
檔案
檔案命名
檔案是一種抽象機制,它提供了一種方式用來儲存資訊以及在後面進行讀取。可能任何一種機制最重要的特性就是管理物件的命名方式。在建立一個檔案後,它會給檔案一個命名。當程序終止時,檔案會繼續存在,並且其他程序可以使用名稱訪問該檔案
。
檔案命名規則對於不同的作業系統來說是不一樣的,但是所有現代作業系統都允許使用 1 - 8 個字母的字串作為合法檔名。
某些檔案區分大小寫字母,而大多數則不區分。UNIX
屬於第一類;歷史悠久的 MS-DOS
屬於第二類(順便說一句,儘管 MS-DOS 歷史悠久,但 MS-DOS 仍在嵌入式系統中非常廣泛地使用,因此它絕不是過時的);因此,UNIX 系統會有三種不同的命名檔案:maria
、Maria
、MARIA
。在 MS-DOS ,所有這些命名都屬於相同的檔案。
這裡可能需要在檔案系統上預留一個位置。Windows 95 和 Windows 98 都使用了 MS-DOS 檔案系統,叫做 FAT-16
,因此繼承了它的一些特徵,例如有關檔名的構造方法。Windows 98 引入了對 FAT-16 的一些擴充套件,從而導致了 FAT-32
的生成,但是這兩者很相似。另外,Windows NT,Windows 2000,Windows XP,Windows Vista,Windows 7 和 Windows 8 都支援 FAT
檔案系統,這種檔案系統有些過時。然而,這些較新的作業系統還具有更高階的本機檔案系統(NTFS)
,有不同的特性,那就是基於 Unicode
編碼的檔名。事實上,Windows 8 還配備了另一種檔案系統,簡稱 ReFS(Resilient File System)
,但這個檔案系統一般應用於 Windows 8 的伺服器版本。下面除非我們特殊宣告,否則我們在提到 MS-DOS 和 FAT 檔案系統的時候,所指的就是 Windows 的 FAT-16 和 FAT-32。這裡要說一下,有一種類似 FAT 的新型檔案系統,叫做 exFAT
。它是微軟公司對快閃記憶體和大檔案系統開發的一種優化的 FAT 32 擴充套件版本。ExFAT 是現在微軟唯一能夠滿足 OS X
讀寫操作的檔案系統。
許多作業系統支援兩部分的檔名,它們之間用 .
分隔開,比如檔名 prog.c
。原點後面的檔案稱為 副檔名(file extension)
,副檔名通常表示檔案的一些資訊。例如在 MS-DOS 中,檔名是 1 - 8 個字元,加上 1 - 3 個字元的可選副檔名組成。在 UNIX 中,如果有副檔名,那麼副檔名的長度將由使用者來決定,一個檔案甚至可以包括兩個或更多的副檔名,例如 homepage.html.zip
,html 表示一個 web 網頁而 .zip 表示檔案homepage.html
已經採用 zip 程式壓縮完成。一些常用的副檔名以及含義如下圖所示
副檔名 | 含義 |
---|---|
bak | 備份檔案 |
c | c 源程式檔案 |
gif | 符合圖形交換格式的影象檔案 |
hlp | 幫助檔案 |
html | WWW 超文字標記語言文件 |
jpg | 符合 JPEG 編碼標準的靜態圖片 |
mp3 | 符合 MP3 音訊編碼格式的音樂檔案 |
mpg | 符合 MPEG 編碼標準的電影 |
o | 目標檔案(編譯器輸出格式,尚未連結) |
pdf 格式的檔案 | |
ps | PostScript 檔案 |
tex | 為 TEX 格式化程式準備的輸入檔案 |
txt | 文字檔案 |
zip | 壓縮檔案 |
在 UNIX 系統中,副檔名只是一種約定,作業系統並不強制採用。
名為 file.txt
的檔案是文字檔案,這個檔名更多的是提醒所有者,而不是給計算機傳遞資訊。但是另一方面,C 編譯器可能要求它編譯的檔案以.c
結尾,否則它會拒絕編譯。然而,作業系統並不關心這一點。
對於可以處理多種型別的程式,約定就顯得及其有用。例如 C 編譯器可以編譯、連結多種檔案,包括 C 檔案和組合語言檔案。這時副檔名就很有必要,編譯器利用它們區分哪些是 C 檔案,哪些是彙編檔案,哪些是其他檔案。因此,副檔名對於編譯器判斷哪些是 C 檔案,哪些是彙編檔案以及哪些是其他檔案變得至關重要。
與 UNIX 相反,Windows 就會關注副檔名並對副檔名賦予了新的含義。使用者(或程序)
可以在作業系統中註冊副檔名
,並且規定哪個程式能夠擁有副檔名。當用戶雙擊某個檔名時,擁有該檔名的程式就啟動並執行檔案。例如,雙擊 file.docx 啟動了 Word 程式,並以 file.docx 作為初始檔案。
檔案結構
檔案的構造有多種方式。下圖列出了常用的三種構造方式
上圖中的 a 是一種無結構的位元組序列,作業系統不關心序列的內容是什麼,作業系統能看到的就是位元組(bytes)
。其檔案內容的任何含義只在使用者程式中進行解釋。UNIX 和 Windows 都採用這種辦法。
把檔案看成位元組序列提供了最大的靈活性。使用者程式可以向檔案中寫任何內容,並且可以通過任何方便的形式命名。作業系統不會為為使用者寫入內容提供幫助,當然也不會干擾阻塞你。對於想做特殊操作的使用者來說,後者是十分重要的。所有的 UNIX 版本(包括 Linux 和 OS X)和 Windows 都使用這種檔案模型。
圖 b 表示在檔案結構上的第一部改進。在這個模型中,檔案是具有固定長度記錄的序列,每個記錄都有其內部結構。 把檔案作為記錄序列的核心思想是:讀操作返回一個記錄,而寫操作重寫或者追加一個記錄。第三種檔案結構如上圖 c 所示。在這種組織結構中,檔案由一顆記錄樹
構成,記錄樹的長度不一定相同,每個記錄樹都在記錄中的固定位置包含一個key
欄位。這棵樹按 key 進行排序,從而可以對特定的 key 進行快速查詢。
在記錄樹的結構中,可以取出下一個記錄,但是最關鍵的還是根據 key 搜尋指定的記錄。如上圖 c 所示,使用者可以讀出指定的 pony
記錄,而不必關心記錄在檔案中的確切位置。使用者也可以在檔案中新增新的記錄。但是使用者不能決定新增到何處位置,新增到何處位置是由作業系統
決定的。
檔案型別
很多作業系統支援多種檔案型別。例如,UNIX(同樣包括 OS X)和 Windows 都具有常規的檔案和目錄。除此之外,UNIX 還具有字元特殊檔案(character special file)
和 塊特殊檔案(block special file)
。常規檔案(Regular files)
是包含有使用者資訊的檔案。使用者一般使用的檔案大都是常規檔案,常規檔案一般包括 可執行檔案、文字檔案、影象檔案,從常規檔案讀取資料或將資料寫入時,核心會根據檔案系統的規則執行操作,是寫入可能被延遲,記錄日誌或者接受其他操作。
字元特殊檔案和輸入/輸出有關,用於序列 I/O 類裝置,如終端、印表機、網路等。塊特殊檔案用於磁碟類裝置。我們主要討論的是常規檔案。
常規檔案一般分為 ASCII
碼檔案或者二進位制檔案。ASCII 碼檔案由文字組成。在一些系統中,每行都會用回車符結束(ASCII碼是13,控制字元 CR,轉義字元\r
。),另外一些則會使用換行符(ASCII碼是10,控制字元LF,轉義字元\n
)。一些系統(比如 Windows)兩者都會使用。
ASCII 檔案的優點在於顯示
和 列印
,還可以用任何文字編輯器進行編輯。進一步來說,如果許多應用程式使用 ASCII 碼作為輸入和輸出,那麼很容易就能夠把多個程式連線起來,一個程式的輸出可能是另一個程式的輸入,就像管道一樣。
其他與 ASCII 不同的是二進位制檔案。打印出來的二進位制檔案是無法理解的。下面是一個二進位制檔案的格式,它取自早期的 UNIX 。儘管從技術上來看這個檔案只是位元組序列,但是作業系統只有在檔案格式正確的情況下才會執行。
這個檔案有五個段:檔案頭、徵文、資料、重定位位和符號表。檔案頭以 魔數(magic number)
為開始,表明這個檔案是一個可執行檔案(以防止意外執行非此格式的檔案)。然後是檔案各個部分的大小,開始執行的標誌以及一些標誌位。程式本身的正文和資料在檔案頭
後面,他們被載入到記憶體中或者重定位會根據重定位位
進行判斷。符號表則用於除錯
。
二進位制檔案的另外一種形式是存檔檔案
,它由已編譯但沒有連結的庫過程(模組)組合而成。每個檔案都以模組頭開始,其中記錄了名稱、建立日期、所有者、保護碼和檔案大小。和可執行檔案一樣,模組頭也都是二進位制數,將它們複製到印表機將會產生亂碼。
所有的作業系統必須至少能夠識別一種檔案型別:它自己的可執行檔案。以前的 TOPS-20 系統(用於DECsystem 20)甚至要檢查要執行的任何檔案的建立時間,為了定位資原始檔來檢查自動檔案建立後是否被修改過。如果被修改過了,那麼就會自動編譯檔案。在 UNIX 中,就是在 shell 中嵌入 make
程式。此時作業系統要求使用者必須採用固定的副檔名,從而確定哪個源程式生成哪個二進位制檔案。
什麼是 make 程式?在軟體發展過程中,make 程式是一個自動編譯的工具,它通過讀取稱為
Makefiles
的檔案來自動從原始碼構建可執行程式和庫,該檔案指定了如何匯出目標程式。儘管整合開發環境和特定於語言的編譯器功能也可以用於管理構建過程,但 Make 仍被廣泛使用,尤其是在 Unix 和類似 Unix 的作業系統中使用。
當程式從檔案中讀寫資料時,請求會轉到核心處理程式(kernel driver)
。如果檔案是常規檔案,則資料由檔案系統驅動程式處理,並且通常儲存在磁碟或其他儲存介質上的某塊區域中,從檔案中讀取的資料就是之前在該位置寫入的資料。
當資料讀取或寫入到裝置檔案時,請求會被裝置驅動程式處理。每個裝置檔案都有一個關聯的編號,該編號標示要使用的裝置驅動程式。裝置處理資料的工作是它自己的事兒。
塊裝置
也叫做塊特殊檔案,它的行為通常與普通檔案相似:它們是位元組陣列,並且在給定位置讀取的值是最後寫入該位置的值。來自塊裝置的資料可以快取在記憶體中,並從快取中讀取;寫入可以被緩衝。塊裝置通常是可搜尋的,塊裝置的概念是,相應的硬體可以一次讀取或者寫入整個塊,例如磁碟上的一個扇區字元裝置
也稱為字元特殊檔案,它的行為類似於管道、串列埠。將位元組寫入字元裝置可能會導致它在螢幕上顯示,在串列埠上輸出,轉換為聲音。
目錄(Directories)
是管理檔案系統結構的系統檔案。它是用於在計算機上儲存檔案的位置。目錄位於分層檔案系統
中,例如 Linux,MS-DOS 和 UNIX。
它顯示所有本地和子目錄(例如,cdn 目錄中的 big 目錄)。當前目錄是 C 盤驅動器的根目錄
。之所以稱為根目錄,是因為該目錄下沒有任何內容,而其他目錄都在該目錄下分支
。
檔案訪問
早期的作業系統只有一種訪問方式:序列訪問(sequential access)
。在這些系統中,程序可以按照順序讀取所有的位元組或檔案中的記錄,但是不能跳過並亂序執行它們。順序訪問檔案是可以返回到起點的,需要時可以多次讀取該檔案。當儲存介質是磁帶而不是磁碟時,順序訪問檔案很方便。
在使用磁碟來儲存檔案時,可以不按照順序讀取檔案中的位元組或者記錄,或者按照關鍵字而不是位置來訪問記錄。這種能夠以任意次序進行讀取的稱為隨機訪問檔案(random access file)
。許多應用程式都需要這種方式。
隨機訪問檔案對許多應用程式來說都必不可少,例如,資料庫系統。如果乘客打電話預定某航班機票,訂票程式必須能夠直接訪問航班記錄,而不必先讀取其他航班的成千上萬條記錄。
有兩種方法可以指示從何處開始讀取檔案。第一種方法是直接使用 read
從頭開始讀取。另一種是用一個特殊的 seek
操作設定當前位置,在 seek 操作後,從這個當前位置順序地開始讀檔案。UNIX 和 Windows 使用的是後面一種方式。
檔案屬性
檔案包括檔名和資料。除此之外,所有的作業系統還會儲存其他與檔案相關的資訊,如檔案建立的日期和時間、檔案大小。我們可以稱這些為檔案的屬性(attributes)
。有些人也喜歡把它們稱作 元資料(metadata)
。檔案的屬性在不同的系統中差別很大。檔案的屬性只有兩種狀態:設定(set)
和 清除(clear)
。下面是一些常用的屬性
屬性 | 含義 |
---|---|
保護 | 誰可以訪問檔案、以什麼方式存取檔案 |
密碼(口令) | 訪問檔案所需要的密碼(口令) |
建立者 | 建立檔案者的 ID |
所有者 | 當前所有者 |
只讀標誌 | 0 表示讀/寫,1 表示只讀 |
隱藏標誌 | 0 表示正常,1 表示不再列表中顯示 |
系統標誌 | 0 表示普通檔案,1 表示系統檔案 |
存檔標誌 | 0 表示已經備份,1 表示需要備份 |
ASCII / 二進位制標誌 | 0 表示 ASCII 檔案,1 表示二進位制檔案 |
隨機訪問標誌 | 0 表示只允許順序訪問,1 表示隨機訪問 |
臨時標誌 | 0 表示正常,1 表示程序退出時刪除該檔案 |
加鎖標誌 | 0 表示未加鎖,1 表示加鎖 |
記錄長度 | 一個記錄中的位元組數 |
鍵的位置 | 每個記錄中的鍵的偏移量 |
鍵的長度 | 鍵欄位的位元組數 |
建立時間 | 建立檔案的日期和時間 |
最後一次存取時間 | 上一次訪問檔案的日期和時間 |
最後一次修改時間 | 上一次修改檔案的日期和時間 |
當前大小 | 檔案的位元組數 |
最大長度 | 檔案可能增長到的位元組數 |
沒有一個系統能夠同時具有上面所有的屬性,但每個屬性都在某個系統中採用。
前面四個屬性(保護,口令,建立者,所有者)與檔案保護有關,它們指出了誰可以訪問這個檔案,誰不能訪問這個檔案。
保護(File Protection)
: 用於保護計算機上有價值資料的方法。檔案保護是通過密碼保護檔案或者僅僅向特定使用者或組提供許可權來實現。
在一些系統中,使用者必須給出口令才能訪問檔案。標誌(flags)
是一些位或者短屬效能夠控制或者允許特定屬性。
隱藏檔案位(hidden flag)
表示該檔案不在檔案列表中出現。存檔標誌位(archive flag)
用於記錄檔案是否備份過,由備份程式清除該標誌位;若檔案被修改,作業系統則設定該標誌位。用這種方法,備份程式可以知道哪些檔案需要備份。臨時標誌位(temporary flag)
允許檔案被標記為是否允許自動刪除當程序終止時。
記錄長度(record-length)
、鍵的位置(key-position)
和鍵的長度(key-length)
等欄位只能出現在用關鍵字查詢記錄的檔案中。它們提供了查詢關鍵字所需要的資訊。
不同的時間欄位記錄了檔案的建立時間、最近一次訪問時間以及最後一次修改時間,它們的作用不同。例如,目標檔案生成後被修改的原始檔需要重新編譯生成目標檔案。這些欄位提供了必要的資訊。
當前大小欄位指出了當前的檔案大小,一些舊的大型機作業系統要求在建立檔案時指定檔案呢最大值,以便讓作業系統提前保留最大儲存值。但是一些伺服器和個人計算機卻不用設定此功能。
檔案操作
使用檔案的目的是用來儲存資訊並方便以後的檢索。對於儲存和檢索,不同的系統提供了不同的操作。以下是與檔案有關的最常用的一些系統呼叫:
Create
,建立不包含任何資料的檔案。呼叫的目的是表示檔案即將建立,並對檔案設定一些屬性。Delete
,當檔案不再需要,必須刪除它以釋放記憶體空間。為此總會有一個系統呼叫來刪除檔案。Open
,在使用檔案之前,必須先開啟檔案。這個呼叫的目的是允許系統將屬性和磁碟地址列表儲存到主存中,用來以後的快速訪問。Close
,當所有程序完成時,屬性和磁碟地址不再需要,因此應關閉檔案以釋放表空間。很多系統限制程序開啟檔案的個數,以此達到鼓勵使用者關閉不再使用的檔案。磁碟以塊為單位寫入,關閉檔案時會強制寫入最後一塊
,即使這個塊空間內部還不滿。Read
,資料從檔案中讀取。通常情況下,讀取的資料來自檔案的當前位置。呼叫者必須指定需要讀取多少資料,並且提供存放這些資料的緩衝區。Write
,向檔案寫資料,寫操作一般也是從檔案的當前位置開始進行。如果當前位置是檔案的末尾,則會直接追加進行寫入。如果當前位置在檔案中,則現有資料被覆蓋,並且永遠消失。append
,使用 append 只能向檔案末尾新增資料。seek
,對於隨機訪問的檔案,要指定從何處開始獲取資料。通常的方法是用 seek 系統呼叫把當前位置指標指向檔案中的特定位置。seek 呼叫結束後,就可以從指定位置開始讀寫資料了。get attributes
,程序執行時通常需要讀取檔案屬性。set attributes
,使用者可以自己設定一些檔案屬性,甚至是在檔案建立之後,實現該功能的是 set attributes 系統呼叫。rename
,使用者可以自己更改已有檔案的名字,rename 系統呼叫用於這一目的。
目錄
檔案系統通常提供目錄(directories)
或者 資料夾(folders)
用於記錄檔案的位置,在很多系統中目錄本身也是檔案,下面我們會討論關於檔案,他們的組織形式、屬性和可以對檔案進行的操作。
一級目錄系統
目錄系統最簡單的形式是有一個能夠包含所有檔案的目錄。這種目錄被稱為根目錄(root directory)
,由於根目錄的唯一性,所以其名稱並不重要。在最早期的個人計算機中,這種系統很常見,部分原因是因為只有一個使用者。下面是一個單層目錄系統的例子
該目錄中有四個檔案。這種設計的優點在於簡單,並且能夠快速定位檔案,畢竟只有一個地方可以檢索。這種目錄組織形式現在一般用於簡單的嵌入式裝置(如數碼相機和某些行動式音樂播放器)上使用。
層次目錄系統
對於簡單的應用而言,一般都用單層目錄方式,但是這種組織形式並不適合於現代計算機,因為現代計算機含有成千上萬個檔案和資料夾。如果都放在根目錄下,查詢起來會非常困難。為了解決這一問題,出現了層次目錄系統(Hierarchical Directory Systems)
,也稱為目錄樹
。通過這種方式,可以用很多目錄把檔案進行分組。進而,如果多個使用者共享同一個檔案伺服器,比如公司的網路系統,每個使用者可以為自己的目錄樹擁有自己的私人根目錄。這種方式的組織結構如下
根目錄含有目錄 A、B 和 C ,分別屬於不同的使用者,其中兩個使用者個字建立了子目錄
。使用者可以建立任意數量的子目錄,現代檔案系統都是按照這種方式組織的。
路徑名
當目錄樹組織檔案系統時,需要有某種方法指明檔名。常用的方法有兩種,第一種方式是每個檔案都會用一個絕對路徑名(absolute path name)
,它由根目錄到檔案的路徑組成。舉個例子,/usr/ast/mailbox
意味著根目錄包含一個子目錄usr
,usr 下面包含了一個 mailbox
。絕對路徑名總是以 /
開頭,並且是唯一的。在UNIX中,路徑的元件由/
分隔。在Windows中,分隔符為\
。 在 MULTICS 中,它是>
。 因此,在這三個系統中,相同的路徑名將被編寫如下
Windows \usr\ast\mailbox
UNIX /usr/ast/mailbox
MULTICS >usr>ast>mailbox
不論使用哪種方式,如果路徑名的第一個字元是分隔符,那就是絕對路徑。
另外一種指定檔名的方法是 相對路徑名(relative path name)
。它常常和 工作目錄(working directory)
(也稱作 當前目錄(current directory)
)一起使用。使用者可以指定一個目錄作為當前工作目錄。例如,如果當前目錄是 /usr/ast
,那麼絕對路徑 /usr/ast/mailbox
可以直接使用 mailbox
來引用。也就是說,如果工作目錄是 /usr/ast
,則 UNIX 命令
cp /usr/ast/mailbox /usr/ast/mailbox.bak
和命令
cp mailbox mailbox.bak
具有相同的含義。相對路徑通常情況下更加方便和簡潔。而它實現的功能和絕對路徑安全相同。
一些程式需要訪問某個特定的檔案而不必關心當前的工作目錄是什麼。在這種情況下,應該使用絕對路徑名。
支援層次目錄結構的大多數作業系統在每個目錄中有兩個特殊的目錄項.
和 ..
,長讀作 dot
和 dotdot
。dot 指的是當前目錄,dotdot 指的是其父目錄(在根目錄中例外,在根目錄中指向自己)。可以參考下面的程序樹來檢視如何使用。
一個程序的工作目錄是 /usr/ast
,它可採用 ..
沿樹向上,例如,可用命令
cp ../lib/dictionary .
把檔案 usr/lib/dictionary
複製到自己的目錄下,第一個路徑告訴系統向上找(到 usr 目錄),然後向下到 lib
目錄,找到 dictionary 檔案
第二個引數 .
指定當前的工作目錄,當 cp 命令用目錄名作為最後一個引數時,則把全部的檔案複製到該目錄中。當然,對於上述複製,鍵入
cp /usr/lib/dictionary .
是更常用的方法。使用者這裡採用 .
可以避免鍵入兩次 dictionary 。無論如何,鍵入
cp /usr/lib/dictionary dictionary
也可正常工作,就像鍵入
cp /usr/lib/dictionary /usr/lib/dictionary
一樣。所有這些命令都能夠完成同樣的工作。
目錄操作
不同檔案中管理目錄的系統呼叫的差別比管理檔案的系統呼叫差別大。為了瞭解這些系統呼叫有哪些以及它們怎樣工作,下面給出一個例子(取自 UNIX)。
Create
,建立目錄,除了目錄項.
和..
外,目錄內容為空。Delete
,刪除目錄,只有空目錄可以刪除。只包含.
和..
的目錄被認為是空目錄,這兩個目錄項通常不能刪除opendir
,目錄內容可被讀取。例如,未列出目錄中的全部檔案,程式必須先開啟該目錄,然後讀其中全部檔案的檔名。與開啟和讀檔案相同,在讀目錄前,必須先開啟檔案。closedir
,讀目錄結束後,應該關閉目錄用於釋放內部表空間。readdir
,系統呼叫 readdir 返回開啟目錄的下一個目錄項。以前也採用 read 系統呼叫來讀取目錄,但是這種方法有一個缺點:程式設計師必須瞭解和處理目錄的內部結構。相反,不論採用哪一種目錄結構,readdir 總是以標準格式返回一個目錄項。rename
,在很多方面目錄和檔案都相似。檔案可以更換名稱,目錄也可以。link
,連結技術允許在多個目錄中出現同一個檔案。這個系統呼叫指定一個存在的檔案和一個路徑名,並建立從該檔案到路徑所指名字的連結。這樣,可以在多個目錄中出現同一個檔案。有時也被稱為硬連結(hard link)
。unlink
,刪除目錄項。如果被解除連結的檔案只出現在一個目錄中,則將它從檔案中刪除。如果它出現在多個目錄中,則只刪除指定路徑名的連結,依然保留其他路徑名的連結。在 UNIX 中,用於刪除檔案的系統呼叫就是 unlink。
檔案系統的實現
在對檔案有了基本認識之後,現在是時候把目光轉移到檔案系統的實現
上了。之前使用者關心的一直都是檔案是怎樣命名的、可以進行哪些操作、目錄樹是什麼,如何找到正確的檔案路徑等問題。而設計人員關心的是檔案和目錄是怎樣儲存的、磁碟空間是如何管理的、如何使檔案系統得以流暢執行的問題,下面我們就來一起討論一下這些問題。
檔案系統佈局
檔案系統儲存在磁碟
中。大部分的磁碟能夠劃分出一到多個分割槽,叫做磁碟分割槽(disk partitioning)
或者是磁碟分片(disk slicing)
。每個分割槽都有獨立的檔案系統,每塊分割槽的檔案系統可以不同。磁碟的 0 號分割槽稱為 主引導記錄(Master Boot Record, MBR)
,用來引導(boot)
計算機。在 MBR 的結尾是分割槽表(partition table)
。每個分割槽表給出每個分割槽由開始到結束的地址。系統管理員使用一個稱為分割槽編輯器的程式來建立,調整大小,刪除和操作分割槽。這種方式的一個缺點是很難適當調整分割槽的大小,導致一個分割槽具有很多可用空間,而另一個分割槽幾乎完全被分配。
MBR 可以用在 DOS 、Microsoft Windows 和 Linux 作業系統中。從 2010 年代中期開始,大多數新計算機都改用 GUID 分割槽表(GPT)分割槽方案。
下面是一個用 GParted
進行分割槽的磁碟,表中的分割槽都被認為是 活動的(active)
。
當計算機開始引 boot 時,BIOS 讀入並執行 MBR。
引導塊
MBR 做的第一件事就是確定活動分割槽
,讀入它的第一個塊,稱為引導塊(boot block)
並執行。引導塊中的程式將載入分割槽中的作業系統。為了一致性,每個分割槽都會從引導塊開始,即使引導塊不包含作業系統。引導塊佔據檔案系統的前 4096 個位元組,從磁碟上的位元組偏移量 0 開始。引導塊可用於啟動作業系統。
在計算機中,引導就是啟動計算機的過程,它可以通過硬體(例如按下電源按鈕)或者軟體命令的方式來啟動。開機後,電腦的 CPU 還不能執行指令,因為此時沒有軟體在主存中,所以一些軟體必須先被載入到記憶體中,然後才能讓 CPU 開始執行。也就是計算機開機後,首先會進行軟體的裝載過程。
重啟電腦的過程稱為
重新引導(rebooting)
,從休眠或睡眠狀態返回計算機的過程不涉及啟動。
除了從引導塊開始之外,磁碟分割槽的佈局是隨著檔案系統的不同而變化的。通常檔案系統會包含一些屬性,如下
超級塊
緊跟在引導塊後面的是 超級塊(Superblock)
,超級塊 的大小為 4096 位元組,從磁碟上的位元組偏移 4096 開始。超級塊包含檔案系統的所有關鍵引數
- 檔案系統的大小
- 檔案系統中的資料塊數
- 指示檔案系統狀態的標誌
- 分配組大小
在計算機啟動或者檔案系統首次使用時,超級塊會被讀入記憶體。
空閒空間塊
接著是檔案系統中空閒塊
的資訊,例如,可以用點陣圖或者指標列表的形式給出。
BitMap 點陣圖或者 Bit vector 位向量
點陣圖或位向量是一系列位或位的集合,其中每個位對應一個磁碟塊,該位可以採用兩個值:0和1,0表示已分配該塊,而1表示一個空閒塊。下圖中的磁碟上給定的磁碟塊例項(分配了綠色塊)可以用16位的位圖表示為:0000111000000110。
使用連結串列進行管理
在這種方法中,空閒磁碟塊連結在一起,即一個空閒塊包含指向下一個空閒塊的指標。第一個磁碟塊的塊號儲存在磁碟上的單獨位置,也快取在記憶體中。
碎片
這裡不得不提一個叫做碎片(fragment)
的概念,也稱為片段。一般零散的單個數據通常稱為片段。 磁碟塊可以進一步分為固定大小的分配單元,片段只是在驅動器上彼此不相鄰的檔案片段。如果你不理解這個概念就給你舉個例子。比如你用 Windows 電腦建立了一個檔案,你會發現這個檔案可以儲存在任何地方,比如存在桌面上,存在磁碟中的資料夾中或者其他地方。你可以開啟檔案,編輯檔案,刪除檔案等等。你可能以為這些都在一個地方發生,但是實際上並不是,你的硬碟驅動器可能會將檔案中的一部分儲存在一個區域內,另一部分儲存在另外一個區域,在你開啟檔案時,硬碟驅動器會迅速的將檔案的所有部分彙總在一起,以便其他計算機系統可以使用它。
inode
然後在後面是一個 inode(index node)
,也稱作索引節點。它是一個數組的結構,每個檔案有一個 inode,inode 非常重要,它說明了檔案的方方面面。每個索引節點都儲存物件資料的屬性和磁碟塊位置
有一種簡單的方法可以找到它們 ls -lai
命令。讓我們看一下根檔案系統:
inode 節點主要包括了以下資訊
- 模式/許可權(保護)
- 所有者 ID
- 組 ID
- 檔案大小
- 檔案的硬連結數
- 上次訪問時間
- 最後修改時間
- inode 上次修改時間
檔案分為兩部分,索引節點和塊。一旦建立後,每種型別的塊數是固定的。你不能增加分割槽上 inode 的數量,也不能增加磁碟塊的數量。
緊跟在 inode 後面的是根目錄,它存放的是檔案系統目錄樹的根部。最後,磁碟的其他部分存放了其他所有的目錄和檔案。
檔案的實現
最重要的問題是記錄各個檔案分別用到了哪些磁碟塊。不同的系統採用了不同的方法。下面我們會探討一下這些方式。分配背後的主要思想是有效利用檔案空間
和快速訪問檔案
,主要有三種分配方案
- 連續分配
- 連結串列分配
- 索引分配
連續分配
最簡單的分配方案是把每個檔案作為一連串連續資料塊儲存在磁碟上。因此,在具有 1KB 塊的磁碟上,將為 50 KB 檔案分配 50 個連續塊。
上面展示了 40 個連續的記憶體塊。從最左側的 0 塊開始。初始狀態下,還沒有裝載檔案,因此磁碟是空的。接著,從磁碟開始處(塊 0 )處開始寫入佔用 4 塊長度的記憶體 A 。然後是一個佔用 6 塊長度的記憶體 B,會直接在 A 的末尾開始寫。
注意每個檔案都會在新的檔案塊開始寫,所以如果檔案 A 只佔用了 3 又 1/2
個塊,那麼最後一個塊的部分記憶體會被浪費。在上面這幅圖中,總共展示了 7 個檔案,每個檔案都會從上個檔案的末尾塊開始寫新的檔案塊。
連續的磁碟空間分配有兩個優點。
第一,連續檔案儲存實現起來比較簡單,只需要記住兩個數字就可以:一個是第一個塊的檔案地址和檔案的塊數量。給定第一個塊的編號,可以通過簡單的加法找到任何其他塊的編號。
第二點是讀取效能比較強,可以通過一次操作從檔案中讀取整個檔案。只需要一次尋找第一個塊。後面就不再需要尋道時間和旋轉延遲,所以資料會以全頻寬進入磁碟。
因此,連續的空間分配具有實現簡單
、高效能
的特點。
不幸的是,連續空間分配也有很明顯的不足。隨著時間的推移,磁碟會變得很零碎。下圖解釋了這種現象
這裡有兩個檔案 D 和 F 被刪除了。當刪除一個檔案時,此檔案所佔用的塊也隨之釋放,就會在磁碟空間中留下一些空閒塊。磁碟並不會在這個位置擠壓掉空閒塊,因為這會複製空閒塊之後的所有檔案,可能會有上百萬的塊,這個量級就太大了。
剛開始的時候,這個碎片不是問題,因為每個新檔案都會在之前檔案的結尾處進行寫入。然而,磁碟最終會被填滿,因此要麼壓縮磁碟、要麼重新使用空閒塊的空間。壓縮磁碟的開銷太大,因此不可行;後者會維護一個空閒列表,這個是可行的。但是這種情況又存在一個問題,為空閒塊匹配合適大小的檔案,需要知道該檔案的最終大小
。
想象一下這種設計的結果會是怎樣的。使用者啟動 word 程序建立文件。應用程式首先會詢問最終建立的文件會有多大。這個問題必須回答,否則應用程式就不會繼續執行。如果空閒塊的大小要比檔案的大小小,程式就會終止。因為所使用的磁碟空間已經滿了。那麼現實生活中,有沒有使用連續分配記憶體的介質出現呢?
CD-ROM
就廣泛的使用了連續分配方式。
CD-ROM(Compact Disc Read-Only Memory)
即只讀光碟,也稱作只讀儲存器。是一種在電腦上使用的光碟。這種光碟只能寫入資料一次,資訊將永久儲存在光碟上,使用時通過光碟驅動器讀出資訊。
然而 DVD 的情況會更加複雜一些。原則上,一個 90分鐘
的電影能夠被編碼成一個獨立的、大約 4.5 GB 的檔案。但是檔案系統所使用的 UDF(Universal Disk Format)
格式,使用一個 30 位的數來代表檔案長度,從而把檔案大小限制在 1 GB。所以,DVD 電影一般儲存在 3、4個連續的 1 GB 空間內。這些構成單個電影中的檔案塊稱為擴充套件區(extends)
。
就像我們反覆提到的,歷史總是驚人的相似,許多年前,連續分配由於其簡單
和高效能
被實際使用在磁碟檔案系統中。後來由於使用者不希望在建立檔案時指定檔案的大小,於是放棄了這種想法。但是隨著 CD-ROM 、DVD、藍光光碟等光學介質的出現,連續分配又流行起來。從而得出結論,技術永遠沒有過時性
,現在看似很老的技術,在未來某個階段可能又會流行起來。
連結串列分配
第二種儲存檔案的方式是為每個檔案構造磁碟塊連結串列,每個檔案都是磁碟塊的連結列表,就像下面所示
每個塊的第一個字作為指向下一塊的指標,塊的其他部分存放資料。如果上面這張圖你看的不是很清楚的話,可以看看整個的連結串列分配方案
與連續分配方案不同,這一方法可以充分利用每個磁碟塊。除了最後一個磁碟塊外,不會因為磁碟碎片而浪費儲存空間。同樣,在目錄項中,只要儲存了第一個檔案塊,那麼其他檔案塊也能夠被找到。
另一方面,在連結串列的分配方案中,儘管順序讀取非常方便,但是隨機訪問卻很困難(這也是陣列和連結串列資料結構的一大區別)。
還有一個問題是,由於指標會佔用一些位元組,每個磁碟塊實際儲存資料的位元組數並不再是 2 的整數次冪。雖然這個問題並不會很嚴重,但是這種方式降低了程式執行效率。許多程式都是以長度為 2 的整數次冪來讀寫磁碟,由於每個塊的前幾個位元組被指標所使用,所以要讀出一個完成的塊大小資訊,就需要當前塊的資訊和下一塊的資訊拼湊而成,因此就引發了查詢和拼接的開銷。
使用記憶體表進行連結串列分配
由於連續分配和連結串列分配都有其不可忽視的缺點。所以提出了使用記憶體中的表來解決分配問題。取出每個磁碟塊的指標字,把它們放在記憶體的一個表中,就可以解決上述連結串列的兩個不足之處。下面是一個例子
上圖表示了連結串列形成的磁碟塊的內容。這兩個圖中都有兩個檔案,檔案 A 依次使用了磁碟塊地址 4、7、 2、 10、 12,檔案 B 使用了6、3、11 和 14。也就是說,檔案 A 從地址 4 處開始,順著連結串列走就能找到檔案 A 的全部磁碟塊。同樣,從第 6 塊開始,順著鏈走到最後,也能夠找到檔案 B 的全部磁碟塊。你會發現,這兩個連結串列都以不屬於有效磁碟編號的特殊標記(-1)結束。記憶體中的這種表格稱為 檔案分配表(File Application Table,FAT)
。
使用這種組織方式,整個塊都可以存放資料。進而,隨機訪問也容易很多。雖然仍要順著鏈在記憶體中查詢給定的偏移量,但是整個鏈都存放在記憶體中,所以不需要任何磁碟引用。與前面的方法相同,不管檔案有多大,在目錄項中只需記錄一個整數(起始塊號),按照它就可以找到檔案的全部塊。
這種方式存在缺點,那就是必須要把整個連結串列放在記憶體中。對於 1TB 的磁碟和 1KB 的大小的塊,那麼這張表需要有 10 億項。。。每一項對應於這 10 億個磁碟塊中的一塊。每項至少 3 個位元組,為了提高查詢速度,有時需要 4 個位元組。根據系統對空間或時間的優化方案,這張表要佔用 3GB 或 2.4GB 的記憶體。FAT 的管理方式不能較好地擴充套件並應用於大型磁碟中。而這正是最初 MS-DOS 檔案比較實用,並仍被各個 Windows 版本所安全支援。
inode
最後一個記錄各個檔案分別包含哪些磁碟塊的方法是給每個檔案賦予一個稱為 inode(索引節點)
的資料結構,每個檔案都與一個 inode
進行關聯,inode 由整數進行標識。
下面是一個簡單例子的描述。
給出 inode 的長度,就能夠找到檔案中的所有塊。
相對於在記憶體中使用表的方式而言,這種機制具有很大的優勢。即只有在檔案開啟時,其 inode 才會在記憶體中。如果每個 inode 需要 n 個位元組,最多 k 個檔案同時開啟,那麼 inode 佔有總共開啟的檔案是 kn 位元組。僅需預留這麼多空間。
這個陣列要比我們上面描述的 FAT(檔案分配表)
佔用的空間小的多。原因是用於儲存所有磁碟塊的連結列表的表的大小與磁碟本身成正比。如果磁碟有 n 個塊,那麼這個表也需要 n 項。隨著磁碟空間的變大,那麼該表也隨之線性增長
。相反,inode 需要節點中的陣列,其大小和可能需要開啟的最大檔案個數成正比。它與磁碟是 100GB、4000GB 還是 10000GB 無關。
inode 的一個問題是如果每個節點都會有固定大小的磁碟地址,那麼檔案增長到所能允許的最大容量外會發生什麼?一個解決方案是最後一個磁碟地址不指向資料塊,而是指向一個包含額外磁碟塊地址的地址,如上圖所示。一個更高階的解決方案是:有兩個或者更多包含磁碟地址的塊,或者指向其他存放地址的磁碟塊的磁碟塊。Windows 的 NTFS 檔案系統採用了相似的方法,所不同的僅僅是大的 inode 也可以表示小的檔案。
NTFS 的全稱是
New Technology File System
,是微軟公司開發的專用系統檔案,NTFS 取代 FAT(檔案分配表) 和HPFS(高效能檔案系統)
,並在此基礎上進一步改進。例如增強對元資料的支援,使用更高階的資料結構以提升效能、可靠性和磁碟空間利用率等。
目錄的實現
檔案只有開啟後才能夠被讀取。在檔案開啟後,作業系統會使用使用者提供的路徑名來定位磁碟中的目錄。目錄項提供了查詢檔案磁碟塊所需要的資訊。根據系統的不同,提供的資訊也不同,可能提供的資訊是整個檔案的磁碟地址,或者是第一個塊的數量(兩個連結串列方案)或 inode的數量。不過不管用那種情況,目錄系統的主要功能就是 將檔案的 ASCII 碼的名稱對映到定位資料所需的資訊上。
與此關係密切的問題是屬性應該存放在哪裡。每個檔案系統包含不同的檔案屬性,例如檔案的所有者和建立時間,需要儲存的位置。一種顯而易見的方法是直接把檔案屬性存放在目錄中。有一些系統恰好是這麼做的,如下。
在這種簡單的設計中,目錄有一個固定大小的目錄項列表,每個檔案對應一項,其中包含一個固定長度的檔名,檔案屬性的結構體以及用以說明磁碟塊位置的一個或多個磁碟地址。
對於採用 inode 的系統,會把 inode 儲存在屬性中而不是目錄項中。在這種情況下,目錄項會更短:僅僅只有檔名稱和 inode 數量。這種方式如下所示
到目前為止,我們已經假設檔案具有較短的、固定長度的名字。在 MS-DOS 中,具有 1 - 8 個字元的基本名稱和 1 - 3 個字元的可拓展名稱。在 UNIX 版本 7 中,檔案有 1 - 14 個字元,包括任何拓展。然而,幾乎所有的現代作業系統都支援可變長度的副檔名。這是如何實現的呢?
最簡單的方式是給予檔名一個長度限制,比如 255 個字元,然後使用上圖中的設計,併為每個檔名保留 255 個字元空間。這種處理很簡單,但是浪費了大量的目錄空間,因為只有很少的檔案會有那麼長的檔名稱。所以,需要一種其他的結構來處理。
一種可選擇的方式是放棄所有目錄項大小相同的想法。在這種方法中,每個目錄項都包含一個固定部分,這個固定部分通常以目錄項的長度開始,後面是固定格式的資料,通常包括所有者、建立時間、保護資訊和其他屬性。這個固定長度的頭的後面是一個任意長度的實際檔名,如下圖所示
上圖是 SPARC 機器使用正序放置。
處理機中的一串字元存放的順序有
正序(big-endian)
和逆序(little-endian)
之分。正序存放的就是高位元組在前低位元組在後,而逆序存放的就是低位元組在前高位元組在後。
這個例子中,有三個檔案,分別是 project-budget
、personnel
和 foo
。每個檔名以一個特殊字元(通常是 0 )結束,用矩形中的叉進行表示。為了使每個目錄項從字的邊界開始,每個檔名被填充成整數個字,如下圖所示
這個方法的缺點是當檔案被移除後,就會留下一塊固定長度的空間,而新新增進來的檔案大小不一定和空閒空間大小一致。
這個問題與我們上面探討的連續磁碟檔案的問題是一樣的,由於整個目錄在記憶體中,所以只有對目錄進行緊湊拼接
操作才可節省空間。另一個問題是,一個目錄項可能會分佈在多個頁上,在讀取檔名時可能發生缺頁中斷。
處理可變長度檔名字的另外一種方法是,使目錄項自身具有固定長度,而將檔名放在目錄末尾的堆疊中。如上圖所示的這種方式。這種方法的優點是當目錄項被移除後,下一個檔案將能夠正常匹配移除檔案的空間。當然,必須要對堆
進行管理,因為在處理檔名的時候也會發生缺頁異常。
到目前為止的所有設計中,在需要查詢檔名時,所有的方案都是線性的從頭到尾對目錄進行搜尋。對於特別長的目錄,線性搜尋的效率很低。提高檔案檢索效率的一種方式是在每個目錄上使用雜湊表(hash table)
,也叫做散列表。我們假設表的大小為 n,在輸入檔名時,檔名被雜湊在 0 和 n - 1 之間,例如,它被 n 除,並取餘數。或者對構成檔名字的字求和或類似某種方法。
無論採用哪種方式,在新增一個檔案時都要對與雜湊值相對 應的散列表進行檢查。如果沒有使用過,就會將一個指向目錄項的指標指向這裡。檔案目錄項緊跟著雜湊表後面。如果已經使用過,就會構造一個連結串列(這種構造方式是不是和 HashMap 使用的資料結構一樣?),連結串列的表頭指標存放在表項中,並通過雜湊值將所有的表項相連。
查詢檔案的過程和新增類似,首先對檔名進行雜湊處理,在雜湊表中查詢是否有這個雜湊值,如果有的話,就檢查這條鏈上所有的雜湊項,檢視檔名是否存在。如果雜湊不在鏈上,那麼檔案就不在目錄中。
使用雜湊表的優勢是查詢非常迅速
,缺點是管理起來非常複雜
。只有在系統中會有成千上萬個目錄項存在時,才會考慮使用散列表作為解決方案。
另外一種在大量目錄中加快查詢指令目錄的方法是使用快取
,快取查詢的結果。在開始查詢之前,會首先檢查檔名是否在快取中。如果在快取中,那麼檔案就能立刻定位。當然,只有在較少的檔案下進行多次查詢,快取才會發揮最大功效。
共享檔案
當多個使用者在同一個專案中工作時,他們通常需要共享檔案。如果這個共享檔案同時出現在多個使用者目錄下,那麼他們協同工作起來就很方便。下面的這張圖我們在上面提到過,但是有一個更改的地方,就是 C 的一個檔案也出現在了 B 的目錄下。
如果按照如上圖的這種組織方式而言,那麼 B 的目錄與該共享檔案的聯絡稱為 連結(link)
。那麼檔案系統現在就是一個 有向無環圖(Directed Acyclic Graph, 簡稱 DAG)
,而不是一棵樹了。
在圖論中,如果一個有向圖從任意頂點出發無法經過若干條邊回到該點,則這個圖是一個
有向無環圖
,我們不會在此著重探討關於圖論的東西,大家可以自行 google。
將檔案系統組織成為有向無環圖會使得維護複雜化,但也是必須要付出的代價。
共享檔案
很方便,但這也會帶來一些問題。如果目錄中包含磁碟地址,則當連結檔案時,必須把 C 目錄中的磁碟地址複製到 B 目錄中。如果 B 或者 C 隨後又向檔案中新增內容,則僅在執行追加的使用者的目錄中顯示新寫入的資料塊。這種變更將會對其他使用者不可見,從而破壞了共享的目的。
有兩種方案可以解決這種問題。
第一種解決方案,磁碟塊不列入目錄中,而是會把磁碟塊放在與檔案本身相關聯的小型資料結構中。目錄將指向這個小型資料結構。這是
UNIX
中使用的方式(小型資料結構就是 inode)。在第二種解決方案中,通過讓系統建立一個型別為
LINK
的新檔案,並把該檔案放在 B 的目錄下,使得 B 與 C 建立連結。新的檔案中只包含了它所連結的檔案的路徑名。當 B 想要讀取檔案時,作業系統會檢查 B 的目錄下存在一個型別為 LINK 的檔案,進而找到該連結的檔案和路徑名,然後再去讀檔案,這種方式稱為符號連結(symbolic linking)
。
上面的每一種方法都有各自的缺點,在第一種方式中,B 連結到共享檔案時,inode 記錄檔案的所有者為 C。建立一個連結並不改變所有關係,如下圖所示。
第一開始的情況如圖 a 所示,此時 C 的目錄的所有者是 C ,當目錄 B 連結到共享檔案時,並不會改變 C 的所有者關係,只是把計數 + 1,所以此時 系統知道目前有多少個目錄指向這個檔案。然後 C 嘗試刪除這個檔案,這個時候有個問題,如果 C 把檔案移除並清除了 inode 的話,那麼 B 會有一個目錄項指向無效的節點。如果 inode 以後分配給另一個檔案,則 B 的連結指向一個錯誤的檔案。系統通過 inode 可知檔案仍在被引用,但是沒有辦法找到該檔案的全部目錄項以刪除它們。指向目錄的指標不能儲存在 inode 中,原因是有可能有無數個這樣的目錄。
所以我們能做的就是刪除 C 的目錄項,但是將 inode 保留下來,並將計數設定為 1 ,如上圖 c 所示。c 表示的是隻有 B 有指向該檔案的目錄項,而該檔案的前者是 C 。如果系統進行記賬操作的話,那麼 C 將繼續為該檔案付賬直到 B 決定刪除它,如果是這樣的話,只有到計數變為 0 的時刻,才會刪除該檔案。
對於符號連結
,以上問題不會發生,只有真正的檔案所有者才有一個指向 inode 的指標。連結到該檔案上的使用者只有路徑名,沒有指向 inode 的指標。當檔案所有者刪除檔案時,該檔案被銷燬。以後若試圖通過符號連結訪問該檔案將會失敗,因為系統不能找到該檔案。刪除符號連結不會影響該檔案。
符號連結的問題是需要額外的開銷。必須讀取包含路徑的檔案,然後要一個部分接一個部分地掃描路徑,直到找到 inode 。這些操作也許需要很多次額外的磁碟訪問。此外,每個符號連結都需要額外的 inode ,以及額外的一個磁碟塊用於儲存路徑,雖然如果路徑名很短,作為一種優化,系統可以將它儲存在 inode 中。符號連結有一個優勢,即只要簡單地提供一個機器的網路地址以及檔案在該機器上駐留的路徑,就可以連線全球任何地方機器上的檔案。
還有另一個由連結帶來的問題,在符號連結和其他方式中都存在。如果允許連結,檔案有兩個或多個路徑。查詢一指定目錄及其子目錄下的全部檔案的程式將多次定位到被連結的檔案。例如,一個將某一目錄及其子目錄下的檔案轉存到磁帶上的程式有可能多次複製一個被連結的檔案。進而,如果接著把磁帶讀入另一臺機器,除非轉出程式具有智慧,否則被連結的檔案將被兩次複製到磁碟上,而不是隻是被連結起來。
日誌結構檔案系統
技術的改變會給當前的檔案系統帶來壓力。這種情況下,CPU 會變得越來越快,磁碟會變得越來越大並且越來越便宜(但不會越來越快)。記憶體容量也是以指數級增長。但是磁碟的尋道時間(除了固態盤,因為固態盤沒有尋道時間)並沒有獲得提高。
這些因素結合起來意味著許多系統檔案中出現效能瓶頸。為此,Berkeley
設計了一種全新的檔案系統,試圖緩解這個問題,這個檔案系統就是 日誌結構檔案系統(Log-structured File System, LFS)
。
日誌結構檔案系統由 Rosenblum
和 Ousterhout
於90年代初引入,旨在解決以下問題。
不斷增長的系統記憶體
順序 I/O 效能勝過隨機 I/O 效能
- 現有低效率的檔案系統
檔案系統不支援 RAID(虛擬化)
另一方面,當時的檔案系統不論是 UNIX 還是 FFS,都有大量的隨機讀寫(在 FFS 中建立一個新檔案至少需要5次隨機寫),因此成為整個系統的效能瓶頸。同時因為 Page cache
的存在,作者認為隨機讀不是主要問題:隨著越來越大的記憶體,大部分的讀操作都能被 cache,因此 LFS 主要要解決的是減少對硬碟的隨機寫操作。
在這種設計中,inode 甚至具有與 UNIX 中相同的結構,但是現在它們分散在整個日誌中,而不是位於磁碟上的固定位置。所以,inode 很定位。為了能夠找到 inode ,維護了一個由 inode 索引的 inode map(inode 對映)
。表項 i 指向磁碟中的第 i 個 inode 。這個對映儲存在磁碟中,但是也儲存在快取中,因此,使用最頻繁的部分大部分時間都在記憶體中。
日誌結構檔案系統主要使用四種資料結構:Inode、Inode Map、Segment、Segment Usage Table。
相關推薦
簡直不要太硬了!一文帶你徹底理解檔案系統
所有的應用程式都需要儲存和檢索資訊。程序執行時,它能夠在自己的儲存空間記憶體儲一定量的資訊。然而,儲存容量受虛擬地址空間大小的限制。對於一些應用程式來說,儲存空間的大小是充足的,但是對於其他一些應用程式,比如航空訂票系統、銀行系統、企業記賬系統來說,這些容量又顯得太小了。 第二個問題是,當程序終止時資訊會
一文帶你徹底理解 JavaScript 原型物件
一、什麼是原型 原型是Javascript中的繼承的基礎,JavaScript的繼承就是基於原型的繼承。 1.1 函式的原型物件 在JavaScript中,我們建立一個函式A(就是宣告一個函式), 那麼瀏覽器就會在記憶體中建立一個物件B,而且每個函式都預設會有一個屬性 prototype 指向了這個物件( 即
什麼,這些人你還不認識?!一文帶你有姿勢地侃深度學習大佬
大資料文摘作品編譯:餘志文,笪潔瓊,錢天培近幾年間,深度學習的興起造就了一批超級巨星。一向在學術
面試都在問的「微服務」「RPC」「服務治理」「下一代微服務」一文帶你徹底搞懂!
❝ 文章每週持續更新,各位的「三連」是對我最大的肯定。可以微信搜尋公眾號「 後端技術學堂 」第一時間閱讀(一般比部落格早更新一到兩篇) ❞ 單體式應用程式 與微服務相對的另一個概念是傳統的「單體式應用程式」( Monolithic application ),單體式應用內部包含了所有需要的服務。而且各個服務功
一文讓你徹底理解 Java NIO 核心元件
背景知識 同步、非同步、阻塞、非阻塞 首先,這幾個概念非常容易搞混淆,但NIO中又有涉及,所以總結一下[1]。 同步:API呼叫返回時呼叫者就知道操作的結果如何了(實際讀取/寫入了多少位元組)。 非同步:相對於同步,API呼叫返回時呼叫者不知道操作的結果,後面才會回撥通知結果
【機器學習筆記】:一文讓你徹底理解準確率,精準率,召回率,真正率,假正率,ROC/AUC
作者:xiaoyu 微信公眾號:Python資料科學 非經作者允許,禁止任何商業轉載。 ROC/AUC作為機器學習的評估指標非常重要,也是面試中經常出現的問題(80%都會問到)。其實,理解它並不是非常難,但是好多朋友都遇到了一個相同的問題,那就是:每次看書的時候
一文帶你徹底瞭解大資料處理引擎Flink記憶體管理
摘要: Flink是jvm之上的大資料處理引擎。 Flink是jvm之上的大資料處理引擎,jvm存在java物件儲存密度低、full gc時消耗效能,gc存在stw的問題,同時omm時會影響穩定性。同時針對頻繁序列化和反序列化問題flink使用堆內堆外記憶體可以直接在一些場景下操作二進位制資料,減少序列化反序
吐血整理!這篇帶你徹底理解主存中儲存單元地址的分配
在閱讀本文之前,建議沒有基礎的讀者先閱讀下主存的基本組成結構: [五分鐘理解主儲存器的基本組成結構](https://blog.csdn.net/weixin_41695995/article/details/105009429) ## 儲存單元的字地址: 我們來看張圖: ![在這裡插入圖片描述](ht
怒懟某自媒體培訓機構,吃相不要太難看了!!!
bfc 為什麽 內容 打開網頁 寫博客 註意 轉載 看到了 好聽 首先說明,這篇博客非技術,純粹是吐槽+點名懟某個培訓機構。下面就開噴了,各位看官誤笑。。。 首先說說事情的起因:今天快下班時候,一個同事拿著手機找我,上面顯示的是某軟件測試培訓機構的一篇文章,標題和UC的
厲害了!一文看懂各大網際網路支付系統整體架構
在網際網路產品運營中,有很多小夥伴或許會遇到這樣的困擾:產品好不容易推出來了,流量成本節節攀升,使用者的活躍度、留存度卻持續下降。 因此在瞬息萬變的網際網路產品環境中,需要研發接入支付系統來加入商業行為的閉環,支付系統能夠幫助企業更好地實現商業化,利用那些為使用者而生的支付體系產品
一文帶你入門Java Stream流,太強了
兩個星期以前,就有讀者強烈要求我寫一篇 Java Stream 流的文章,我說市面上不是已經有很多了嗎,結果你猜他怎麼說:“就想看你寫的啊!”你看你看,多麼蒼白的喜歡啊。那就“勉為其難”寫一篇吧,嘻嘻。 單從“Stream”這個單詞上來看,它似乎和 java.io 包下的 InputStream 和 Ou
一文帶你了解激光雷達重要指標及參數
因此 一個 https 速度 .com p s 展示 jpg left 博客轉載自:https://www.leiphone.com/news/201801/oySuWNzftbNrWwpv.html 雷鋒網(公眾號:雷鋒網)按:本文作者SLAMTEC(思嵐科技公號slam
獨家 | 一文帶你讀懂特徵工程!
作者:Bhalchandra Madhekar 翻譯:陳之炎校對:張玲本文約1800字,建議閱讀
漫畫區塊鏈,一文帶你秒懂她!
伯特最近也在學習區塊鏈相關的知識,對其基本概念和運作方式算是有了基本的瞭解。我關注區塊鏈以及比特
一文帶你了解不一樣的肖特基二極管
.com process 由於 驅動 技術 一段 存在 電子產品 pin 肖特基二極管一種使用到電源電路裏的電源IC,一直成為大眾談論的資本。在眾多的電子產品裏都離不開肖特基二極管的身影,但是對於接觸不多的用戶來說,免不了會有這樣那樣的疑問困惑,下面就讓立深鑫帶你一起去了解
一文帶你搞懂什麼是測試開發!
01 開始前說點什麼 需要說明的是,原文發表於作者的公眾號中,文章篇幅雖長,但內容樸實、且能幫助讀者進一步理解測試開發工作,請讀者耐心品完~ 1. 自我反省 公眾號開通了也有兩年多了,除了剛開通的那段時間發文比
一文帶你瞭解 OAuth2 協議與 Spring Security OAuth2 整合!
OAuth 2.0 允許第三方應用程式訪問受限的HTTP資源的授權協議,像平常大家使用Github、Google賬號來登陸其他系統時使用的就是 OAuth 2.0 授權框架,下圖就是使用Github賬號登陸Coding系統的授權頁面圖: 類似使用 OAuth 2.0 授權的還有很多,本文將介紹 OAuth
一文帶你深度解析騰訊雲直播答題方案
exc com erp 同學 col 測試 的確 影響 cep 歡迎大家前往雲+社區,獲取更多騰訊海量技術實踐幹貨哦~ 作者:騰訊視頻雲 進入2018年最火的新鮮事物無疑就是“直播答題”了,動輒上百萬的獎金更是吸引了大量用戶的參與。一場直播動輒幾百萬的獎金,每人可以分到
一文帶你吃透執行緒池
微信公眾號:[Amos部落格] 內容目錄 TreadPoolexecutor原始碼解析 類關係圖 Executor介面 ExecutorService介面 AbstractExecutorService 成員變數
一文帶你快速瞭解最火的數字經濟(大資料、人工智慧等都有)
人工智慧行業應用加速(暴富機會由“網際網路+”轉向AI+) “網際網路+”紅利已開發將盡,未來,新的暴富紅利將由“人工智慧”接棒。從產業演進看,科技巨頭正加速全球化併購,打造AI生態閉環,開源化也將成為全球性趨勢。開源化使得人工智慧的行業運用門檻急遽降低,未來幾年將迎來人工智慧行業應用浪潮。 2