這是Java建設者第 79 篇原創長文html
全部的應用程序都須要存儲和檢索信息。進程運行時,它可以在本身的存儲空間內存儲必定量的信息。然而,存儲容量受虛擬地址空間大小的限制。對於一些應用程序來講,存儲空間的大小是充足的,可是對於其餘一些應用程序,好比航空訂票系統、銀行系統、企業記帳系統來講,這些容量又顯得過小了。node
第二個問題是,當進程終止時信息會丟失。對於一些應用程序(例如數據庫),信息會長久保留。在這些進程終止時,相關的信息應該保留下來,是不能丟失的。甚至這些應用程序崩潰後,信息也應該保留下來。程序員
第三個問題是,一般須要不少進程在同一時刻訪問這些信息。解決這種問題的方式是把這些信息單獨保留在各自的進程中。web
所以,對於長久存儲的信息咱們有三個基本需求:算法
必需要有可能存儲的大量的信息shell
信息必須可以在進程終止時保留數據庫
必須可以使多個進程同時訪問有關信息數組
磁盤(Magnetic disk) 一直是用來長久保存信息的設備。近些年來,固態硬盤逐漸流行起來。
緩存
固態硬盤不只沒有易損壞的移動部件,並且可以提供快速的隨機訪問。相比而言,雖然磁帶和光盤也被普遍使用,可是它們的性能相對較差,一般應用於備份。咱們會在後面探討磁盤,如今姑且把磁盤看成一種大小固定塊的線性序列好了,而且支持以下操做安全
讀塊 k
事實上磁盤支持更多的操做,可是隻要有了讀寫操做,原則上就可以解決長期存儲的問題。
然而,磁盤還有一些不便於實現的操做,特別是在有不少程序或者多用戶使用的大型系統上(如服務器)。在這種狀況下,很容易產生一些問題,例如
你如何找到這些信息?
你如何保證一個用戶不會讀取另一個用戶的數據?
你怎麼知道哪些塊是空閒的?等等問題
咱們能夠針對這些問題提出一個新的抽象 - 文件。進程和線程的抽象、地址空間和文件都是操做系統的重要概念。若是你能真正深刻了解這三個概念,那麼你就走上了成爲操做系統專家的道路。
文件(Files)是由進程建立的邏輯信息單元。一個磁盤會包含幾千甚至幾百萬個文件,每一個文件是獨立於其餘文件的。事實上,若是你能把每一個文件都看做一個獨立的地址空間,那麼你就能夠真正理解文件的概念了。
進程可以讀取已經存在的文件,並在須要時從新建立他們。存儲在文件中的信息必須是持久的,這也就是說,不會由於進程的建立和終止而受影響。一個文件只能在當用戶明確刪除的時候才能消失。儘管讀取和寫入都是最基本的操做,但還有許多其餘操做,咱們將在下面介紹其中的一些。
文件由操做系統進行管理,有關文件的構造、命名、訪問、使用、保護、實現和管理方式都是操做系統設計的主要內容。從整體上看,操做系統中處理文件的部分稱爲 文件系統(file system),這就是咱們所討論的。
從用戶角度來講,用戶一般會關心文件是由什麼組成的,如何給文件進行命名,如何保護文件,以及能夠對文件進行哪些操做等等。儘管是用鏈表仍是用位圖記錄內存空閒區並非用戶所關心的主題,而這些對系統設計人員來講相當重要。下面咱們就來探討一下這些主題
1
文件
1.1
文件命名
文件是一種抽象機制,它提供了一種方式用來存儲信息以及在後面進行讀取。可能任何一種機制最重要的特性就是管理對象的命名方式。在建立一個文件後,它會給文件一個命名。當進程終止時,文件會繼續存在,而且其餘進程能夠使用名稱訪問該文件。
文件命名規則對於不一樣的操做系統來講是不同的,可是全部現代操做系統都容許使用 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 程序壓縮完成。一些經常使用的文件擴展名以及含義以下圖所示
在 UNIX 系統中,文件擴展名只是一種約定,操做系統並不強制採用。
名爲 file.txt 的文件是文本文件,這個文件名更多的是提醒全部者,而不是給計算機傳遞信息。可是另外一方面,C 編譯器可能要求它編譯的文件以.c 結尾,不然它會拒絕編譯。然而,操做系統並不關心這一點。
對於能夠處理多種類型的程序,約定就顯得及其有用。例如 C 編譯器能夠編譯、連接多種文件,包括 C 文件和彙編語言文件。這時擴展名就頗有必要,編譯器利用它們區分哪些是 C 文件,哪些是彙編文件,哪些是其餘文件。所以,擴展名對於編譯器判斷哪些是 C 文件,哪些是彙編文件以及哪些是其餘文件變得相當重要。
與 UNIX 相反,Windows 就會關注擴展名並對擴展名賦予了新的含義。用戶(或進程) 能夠在操做系統中註冊擴展名,而且規定哪一個程序可以擁有擴展名。當用戶雙擊某個文件名時,擁有該文件名的程序就啓動並運行文件。例如,雙擊 file.docx 啓動了 Word 程序,並以 file.docx 做爲初始文件。
1.2
文件結構
文件的構造有多種方式。下圖列出了經常使用的三種構造方式
上圖中的 a 是一種無結構的字節序列,操做系統不關心序列的內容是什麼,操做系統能看到的就是字節(bytes)。其文件內容的任何含義只在用戶程序中進行解釋。UNIX 和 Windows 都採用這種辦法。
把文件當作字節序列提供了最大的靈活性。用戶程序能夠向文件中寫任何內容,而且能夠經過任何方便的形式命名。操做系統不會爲爲用戶寫入內容提供幫助,固然也不會干擾阻塞你。對於想作特殊操做的用戶來講,後者是十分重要的。全部的 UNIX 版本(包括 Linux 和 OS X)和 Windows 都使用這種文件模型。
圖 b 表示在文件結構上的第一步改進。在這個模型中,文件是具備固定長度記錄的序列,每一個記錄都有其內部結構。把文件做爲記錄序列的核心思想是:讀操做返回一個記錄,而寫操做重寫或者追加一個記錄。第三種文件結構如上圖 c 所示。在這種組織結構中,文件由一顆記錄樹構成,記錄樹的長度不必定相同,每一個記錄樹都在記錄中的固定位置包含一個key 字段。這棵樹按 key 進行排序,從而能夠對特定的 key 進行快速查找。
在記錄樹的結構中,能夠取出下一個記錄,可是最關鍵的仍是根據 key 搜索指定的記錄。如上圖 c 所示,用戶能夠讀出指定的 pony 記錄,而沒必要關心記錄在文件中的確切位置。用戶也能夠在文件中添加新的記錄。可是用戶不能決定添加到何處位置,添加到何處位置是由操做系統決定的。
1.3
文件類型
不少操做系統支持多種文件類型。例如,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 盤驅動器的根目錄。之因此稱爲根目錄,是由於該目錄下沒有任何內容,而其餘目錄都在該目錄下分支。
1.4
文件訪問
早期的操做系統只有一種訪問方式:序列訪問(sequential access)。在這些系統中,進程能夠按照順序讀取全部的字節或文件中的記錄,可是不能跳過並亂序執行它們。順序訪問文件是能夠返回到起點的,須要時能夠屢次讀取該文件。當存儲介質是磁帶而不是磁盤時,順序訪問文件很方便。
在使用磁盤來存儲文件時,能夠不按照順序讀取文件中的字節或者記錄,或者按照關鍵字而不是位置來訪問記錄。這種可以以任意次序進行讀取的稱爲隨機訪問文件(random access file)。許多應用程序都須要這種方式。
隨機訪問文件對許多應用程序來講都必不可少,例如,數據庫系統。若是乘客打電話預約某航班機票,訂票程序必須可以直接訪問航班記錄,而沒必要先讀取其餘航班的成千上萬條記錄。
有兩種方法能夠表示從何處開始讀取文件。第一種方法是直接使用 read 從頭開始讀取。另外一種是用一個特殊的 seek 操做設置當前位置,在 seek 操做後,從這個當前位置順序地開始讀文件。UNIX 和 Windows 使用的是後面一種方式。
1.5
文件屬性
文件包括文件名和數據。除此以外,全部的操做系統還會保存其餘與文件相關的信息,如文件建立的日期和時間、文件大小。咱們能夠稱這些爲文件的屬性(attributes)。有些人也喜歡把它們稱做 元數據(metadata)。文件的屬性在不一樣的系統中差異很大。文件的屬性只有兩種狀態:設置(set) 和 清除(clear)。下面是一些經常使用的屬性
沒有一個系統可以同時具備上面全部的屬性,但每一個屬性都在某個系統中採用。
前面四個屬性(保護,口令,建立者,全部者)與文件保護有關,它們指出了誰能夠訪問這個文件,誰不能訪問這個文件。
保護(File Protection):用於保護計算機上有價值數據的方法。文件保護是經過密碼保護文件或者僅僅向特定用戶或組提供權限來實現。
在一些系統中,用戶必須給出口令才能訪問文件。標誌(flags)是一些位或者短屬性可以控制或者容許特定屬性。
隱藏文件位(hidden flag)表示該文件不在文件列表中出現。
存檔標誌位(archive flag)用於記錄文件是否備份過,由備份程序清除該標誌位;若文件被修改,操做系統則設置該標誌位。用這種方法,備份程序能夠知道哪些文件須要備份。
記錄長度(record-length)、鍵的位置(key-position)和鍵的長度(key-length)等字段只能出如今用關鍵字查找記錄的文件中。它們提供了查找關鍵字所須要的信息。
不一樣的時間字段記錄了文件的建立時間、最近一次訪問時間以及最後一次修改時間,它們的做用不一樣。例如,目標文件生成後被修改的源文件須要從新編譯生成目標文件。這些字段提供了必要的信息。
當前大小字段指出了當前的文件大小,一些舊的大型機操做系統要求在建立文件時指定文件最大值,以便讓操做系統提早保留最大存儲值。可是一些服務器和我的計算機卻不用設置此功能。
1.6
文件操做
使用文件的目的是用來存儲信息並方便之後的檢索。對於存儲和檢索,不一樣的系統提供了不一樣的操做。如下是與文件有關的最經常使用的一些系統調用:
Create,建立不包含任何數據的文件。調用的目的是表示文件即將創建,並對文件設置一些屬性。
Delete,當文件再也不須要,必須刪除它以釋放內存空間。爲此總會有一個系統調用來刪除文件。
Open,在使用文件以前,必須先打開文件。這個調用的目的是容許系統將屬性和磁盤地址列表保存到主存中,用來之後的快速訪問。
Close,當全部進程完成時,屬性和磁盤地址再也不須要,所以應關閉文件以釋放表空間。不少系統限制進程打開文件的個數,以此達到鼓勵用戶關閉再也不使用的文件。磁盤以塊爲單位寫入,關閉文件時會強制寫入最後一塊,即便這個塊空間內部還不滿。
Read,數據從文件中讀取。一般狀況下,讀取的數據來自文件的當前位置。調用者必須指定須要讀取多少數據,而且提供存放這些數據的緩衝區。
Write,向文件寫數據,寫操做通常也是從文件的當前位置開始進行。若是當前位置是文件的末尾,則會直接追加進行寫入。若是當前位置在文件中,則現有數據被覆蓋,而且永遠消失。
append,使用 append 只能向文件末尾添加數據。
seek,對於隨機訪問的文件,要指定從何處開始獲取數據。一般的方法是用 seek 系統調用把當前位置指針指向文件中的特定位置。seek 調用結束後,就能夠從指定位置開始讀寫數據了。
get attributes,進程運行時一般須要讀取文件屬性。
set attributes,用戶能夠本身設置一些文件屬性,甚至是在文件建立以後,實現該功能的是 set attributes 系統調用。
2
目錄
文件系統一般提供目錄(directories) 或者 文件夾(folders) 用於記錄文件的位置,在不少系統中目錄自己也是文件,下面咱們會討論關於文件,他們的組織形式、屬性和能夠對文件進行的操做。
2.1
一級目錄系統
目錄系統最簡單的形式是有一個可以包含全部文件的目錄。這種目錄被稱爲根目錄(root directory),因爲根目錄的惟一性,因此其名稱並不重要。在最先期的我的計算機中,這種系統很常見,部分緣由是由於只有一個用戶。下面是一個單層目錄系統的例子
該目錄中有四個文件。這種設計的優勢在於簡單,而且可以快速定位文件,畢竟只有一個地方能夠檢索。這種目錄組織形式如今通常用於簡單的嵌入式設備(如數碼相機和某些便攜式音樂播放器)上使用。
2.2
層次目錄系統
對於簡單的應用而言,通常都用單層目錄方式,可是這種組織形式並不適合於現代計算機,由於現代計算機含有成千上萬個文件和文件夾。若是都放在根目錄下,查找起來會很是困難。爲了解決這一問題,出現了層次目錄系統(Hierarchical Directory Systems),也稱爲目錄樹。經過這種方式,能夠用不少目錄把文件進行分組。進而,若是多個用戶共享同一個文件服務器,好比公司的網絡系統,每一個用戶能夠爲本身的目錄樹擁有本身的私人根目錄。這種方式的組織結構以下
根目錄含有目錄 A、B 和 C ,分別屬於不一樣的用戶,其中兩個用戶個字建立了子目錄。用戶能夠建立任意數量的子目錄,現代文件系統都是按照這種方式組織的。
2.3
路徑名
當目錄樹組織文件系統時,須要有某種方法指明文件名。經常使用的方法有兩種,第一種方式是每一個文件都會用一個絕對路徑名(absolute path name),它由根目錄到文件的路徑組成。舉個例子,/usr/ast/mailbox 意味着根目錄包含一個子目錄usr,usr 下面包含了一個 mailbox。絕對路徑名老是以 / 開頭,而且是惟一的。在 UNIX 中,路徑的組件由/分隔。在 Windows 中,分隔符爲\。在 MULTICS 中,它是>。所以,在這三個系統中,相同的路徑名將被編寫以下
1Windows \usr\ast\mailbox 2UNIX /usr/ast/mailbox 3MULTICS >usr>ast>mailbox
不論使用哪一種方式,若是路徑名的第一個字符是分隔符,那就是絕對路徑。
另一種指定文件名的方法是 相對路徑名(relative path name)。它經常和 工做目錄(working directory) (也稱做 當前目錄(current directory))一塊兒使用。用戶能夠指定一個目錄做爲當前工做目錄。例如,若是當前目錄是 /usr/ast,那麼絕對路徑 /usr/ast/mailbox能夠直接使用 mailbox 來引用。也就是說,若是工做目錄是 /usr/ast,則 UNIX 命令
1cp /usr/ast/mailbox /usr/ast/mailbox.bak
和命令
1cp 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
同樣。全部這些命令都可以完成一樣的工做。
2.4
目錄操做
不一樣文件中管理目錄的系統調用的差異比管理文件的系統調用差異大。爲了瞭解這些系統調用有哪些以及它們怎樣工做,下面給出一個例子(取自 UNIX)。
Create,建立目錄,除了目錄項 . 和 .. 外,目錄內容爲空。
Delete,刪除目錄,只有空目錄能夠刪除。只包含 . 和 .. 的目錄被認爲是空目錄,這兩個目錄項一般不能刪除
opendir,目錄內容可被讀取。例如,未列出目錄中的所有文件,程序必須先打開該目錄,而後讀其中所有文件的文件名。與打開和讀文件相同,在讀目錄前,必須先打開文件。
closedir,讀目錄結束後,應該關閉目錄用於釋放內部表空間。
readdir,系統調用 readdir 返回打開目錄的下一個目錄項。之前也採用 read 系統調用來讀取目錄,可是這種方法有一個缺點:程序員必須瞭解和處理目錄的內部結構。相反,不論採用哪種目錄結構,readdir 老是以標準格式返回一個目錄項。
rename,在不少方面目錄和文件都類似。文件能夠更換名稱,目錄也能夠。
link,連接技術容許在多個目錄中出現同一個文件。這個系統調用指定一個存在的文件和一個路徑名,並創建從該文件到路徑所指名字的連接。這樣,能夠在多個目錄中出現同一個文件。有時也被稱爲硬連接(hard link)。
3
文件系統的實現
在對文件有了基本認識以後,如今是時候把目光轉移到文件系統的實現上了。以前用戶關心的一直都是文件是怎樣命名的、能夠進行哪些操做、目錄樹是什麼,如何找到正確的文件路徑等問題。而設計人員關心的是文件和目錄是怎樣存儲的、磁盤空間是如何管理的、如何使文件系統得以流暢運行的問題,下面咱們就來一塊兒討論一下這些問題。
3.1
文件系統佈局
文件系統存儲在磁盤中。大部分的磁盤可以劃分出一到多個分區,叫作磁盤分區(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 後面的是根目錄,它存放的是文件系統目錄樹的根部。最後,磁盤的其餘部分存放了其餘全部的目錄和文件。
3.2
文件的實現
最重要的問題是記錄各個文件分別用到了哪些磁盤塊。不一樣的系統採用了不一樣的方法。下面咱們會探討一下這些方式。分配背後的主要思想是有效利用文件空間和快速訪問文件 ,主要有三種分配方案
連續分配
鏈表分配
連續分配
最簡單的分配方案是把每一個文件做爲一連串連續數據塊存儲在磁盤上。所以,在具備 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 電影通常存儲在 三、4 個連續的 1 GB 空間內。這些構成單個電影中的文件塊稱爲擴展區(extends)。
就像咱們反覆提到的,歷史老是驚人的類似,許多年前,連續分配因爲其簡單和高性能被實際使用在磁盤文件系統中。後來因爲用戶不但願在建立文件時指定文件的大小,因而放棄了這種想法。可是隨着 CD-ROM 、DVD、藍光光盤等光學介質的出現,連續分配又流行起來。從而得出結論,技術永遠沒有過期性,如今看似很老的技術,在將來某個階段可能又會流行起來。
鏈表分配
第二種存儲文件的方式是爲每一個文件構造磁盤塊鏈表,每一個文件都是磁盤塊的連接列表,就像下面所示
每一個塊的第一個字做爲指向下一塊的指針,塊的其餘部分存放數據。若是上面這張圖你看的不是很清楚的話,能夠看看整個的鏈表分配方案
與連續分配方案不一樣,這一方法能夠充分利用每一個磁盤塊。除了最後一個磁盤塊外,不會由於磁盤碎片而浪費存儲空間。一樣,在目錄項中,只要存儲了第一個文件塊,那麼其餘文件塊也可以被找到。
另外一方面,在鏈表的分配方案中,儘管順序讀取很是方便,可是隨機訪問卻很困難(這也是數組和鏈表數據結構的一大區別)。
還有一個問題是,因爲指針會佔用一些字節,每一個磁盤塊實際存儲數據的字節數並再也不是 2 的整數次冪。雖然這個問題並不會很嚴重,可是這種方式下降了程序運行效率。許多程序都是以長度爲 2 的整數次冪來讀寫磁盤,因爲每一個塊的前幾個字節被指針所使用,因此要讀出一個完成的塊大小信息,就須要當前塊的信息和下一塊的信息拼湊而成,所以就引起了查找和拼接的開銷。
使用內存表進行鏈表分配
因爲連續分配和鏈表分配都有其不可忽視的缺點。因此提出了使用內存中的表來解決分配問題。取出每一個磁盤塊的指針字,把它們放在內存的一個表中,就能夠解決上述鏈表的兩個不足之處。下面是一個例子
上圖表示了鏈表造成的磁盤塊的內容。這兩個圖中都有兩個文件,文件 A 依次使用了磁盤塊地址 四、七、 二、 十、 12,文件 B 使用了六、三、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(高性能文件系統) ,並在此基礎上進一步改進。例如加強對元數據的支持,使用更高級的數據結構以提高性能、可靠性和磁盤空間利用率等。
3.3
目錄的實現
文件只有打開後纔可以被讀取。在文件打開後,操做系統會使用用戶提供的路徑名來定位磁盤中的目錄。目錄項提供了查找文件磁盤塊所須要的信息。根據系統的不一樣,提供的信息也不一樣,可能提供的信息是整個文件的磁盤地址,或者是第一個塊的數量(兩個鏈表方案)或 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 使用的數據結構同樣?),鏈表的表頭指針存放在表項中,並經過哈希值將全部的表項相連。
查找文件的過程和添加相似,首先對文件名進行哈希處理,在哈希表中查找是否有這個哈希值,若是有的話,就檢查這條鏈上全部的哈希項,查看文件名是否存在。若是哈希不在鏈上,那麼文件就不在目錄中。
使用哈希表的優點是查找很是迅速,缺點是管理起來很是複雜。只有在系統中會有成千上萬個目錄項存在時,纔會考慮使用散列表做爲解決方案。
另一種在大量目錄中加快查找指令目錄的方法是使用緩存,緩存查找的結果。在開始查找以前,會首先檢查文件名是否在緩存中。若是在緩存中,那麼文件就能馬上定位。固然,只有在較少的文件下進行屢次查找,緩存纔會發揮最大功效。
3.4
共享文件
當多個用戶在同一個項目中工做時,他們一般須要共享文件。若是這個共享文件同時出如今多個用戶目錄下,那麼他們協同工做起來就很方便。下面的這張圖咱們在上面提到過,可是有一個更改的地方,就是 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 中。符號連接有一個優點,即只要簡單地提供一個機器的網絡地址以及文件在該機器上駐留的路徑,就能夠鏈接全球任何地方機器上的文件。
還有另外一個由連接帶來的問題,在符號連接和其餘方式中都存在。若是容許連接,文件有兩個或多個路徑。查找一指定目錄及其子目錄下的所有文件的程序將屢次定位到被連接的文件。例如,一個將某一目錄及其子目錄下的文件轉存到磁帶上的程序有可能屢次複製一個被連接的文件。進而,若是接着把磁帶讀入另外一臺機器,除非轉出程序具備智能,不然被連接的文件將被兩次複製到磁盤上,而不是隻是被連接起來。
3.5
日誌結構文件系統
技術的改變會給當前的文件系統帶來壓力。這種狀況下,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。
到目前爲止,全部寫入最初都緩存在內存中,而且追加在日誌末尾,全部緩存的寫入都按期在單個段中寫入磁盤。因此,如今打開文件也就意味着用映射定位文件的索引節點。一旦 inode 被定位後,磁盤塊的地址就可以被找到。全部這些塊自己都將位於日誌中某處的分段中。
真實狀況下的磁盤容量是有限的,因此最終日誌會佔滿整個磁盤空間,這種狀況下就會出現沒有新的磁盤塊被寫入到日誌中。幸運的是,許多現有段可能具備再也不須要的塊。例如,若是一個文件被覆蓋了,那麼它的 inode 將被指向新的塊,可是舊的磁盤塊仍在先前寫入的段中佔據着空間。
爲了處理這個問題,LFS 有一個清理(clean)線程,它會循環掃描日誌並對日誌進行壓縮。首先,經過查看日誌中第一部分的信息來查看其中存在哪些索引節點和文件。它會檢查當前 inode 的映射來查看 inode 是否在當前塊中,是否仍在被使用。若是不是,該信息將被丟棄。若是仍然在使用,那麼 inode 和塊就會進入內存等待寫回到下一個段中。而後原來的段被標記爲空閒,以便日誌能夠用來存放新的數據。用這種方法,清理線程遍歷日誌,從後面移走舊的段,而後將有效的數據放入內存等待寫到下一個段中。由此一來整個磁盤會造成一個大的環形緩衝區,寫線程將新的段寫在前面,而清理線程則清理後面的段。
3.6
日誌文件系統
雖然日誌結構系統的設計很優雅,可是因爲它們和現有的文件系統不相匹配,所以尚未普遍使用。不過,從日誌文件結構系統衍生出來一種新的日誌系統,叫作日誌文件系統,它會記錄系統下一步將要作什麼的日誌。微軟的 NTFS 文件系統、Linux 的 ext3 就使用了此日誌。OS X 將日誌系統做爲可供選項。爲了看清它是如何工做的,咱們下面討論一個例子,好比 移除文件 ,這個操做在 UNIX 中須要三個步驟完成:
在目錄中刪除文件
釋放 inode 到空閒 inode 池
在 Windows 中,也存在相似的步驟。不存在系統崩潰時,這些步驟的執行順序不會帶來問題。可是一旦系統崩潰,就會帶來問題。假如在第一步完成後系統崩潰。inode 和文件塊將不會被任何文件得到,也不會再分配;它們只存在於廢物池中的某個地方,並所以減小了可利用的資源。若是崩潰發生在第二步後,那麼只有磁盤塊會丟失。日誌文件系統保留磁盤寫入期間對文件系統所作的更改的日誌或日誌,該日誌可用於快速重建可能因爲系統崩潰或斷電等事件而發生的損壞。
通常文件系統崩潰後必須運行 fsck(文件系統一致性檢查)實用程序。
爲了讓日誌可以正確工做,被寫入的日誌操做必須是 冪等的(idempotent),它意味着只要有必要,它們就能夠重複執行不少次,並不會帶來破壞。像操做 更新位表並標記 inode k 或者塊 n 是空閒的 能夠重複執行任意次。一樣地,查找一個目錄而且刪除全部叫 foobar 的項也是冪等的。相反,把從 inode k 新釋放的塊加入空閒表的末端不是冪等的,由於它們可能已經被釋放並存放在那裏了。
爲了增長可靠性,一個文件系統能夠引入數據庫中 原子事務(atomic transaction) 的概念。使用這個概念,一組動做能夠被界定在開始事務和結束事務操做之間。這樣,文件系統就會知道它必須完成全部的動做,要麼就一個不作。
3.7
虛擬文件系統
即便在同一臺計算機上或者在同一個操做系統下,都會使用不少不一樣的文件系統。Windows 中的主要文件系統是 NTFS 文件系統,但不是說 Windows 只有 NTFS 操做系統,它還有一些其餘的例如舊的 FAT -32 或FAT -16 驅動器或分區,其中包含仍須要的數據,閃存驅動器,舊的 CD-ROM 或 DVD(每一個都有本身的獨特文件系統)。Windows 經過指定不一樣的盤符來處理這些不一樣的文件系統,好比 C:,D: 等。盤符能夠顯示存在也能夠隱式存在,若是你想找指定位置的文件,那麼盤符是顯示存在;若是當一個進程打開一個文件時,此時盤符是隱式存在,因此 Windows 知道向哪一個文件系統傳遞請求。
相比之下,UNIX 採用了一種不一樣的方式,即 UNIX 把多種文件系統整合到一個統一的結構中。一個 Linux 系統能夠使用 ext2 做爲根文件系統,ext3 分區裝載在 /usr 下,另外一塊採用 Reiser FS 文件系統的硬盤裝載到 /home下,以及一個 ISO 9660 的 CD - ROM 臨時裝載到 /mnt 下。從用戶的觀點來看,只有一個文件系統層級,可是事實上它們是由多個文件系統組合而成,對於用戶和進程是不可見的。
UNIX 操做系統使用一種 虛擬文件系統(Virtual File System, VFS) 來嘗試將多種文件系統構成一個有序的結構。關鍵的思想是抽象出全部文件系統都共有的部分,並將這部分代碼放在一層,這一層再調用具體文件系統來管理數據。下面是一個 VFS 的系統結構
仍是那句經典的話,在計算機世界中,任何解決不了的問題均可以加個代理來解決。全部和文件相關的系統調用在最初的處理上都指向虛擬文件系統。這些來自用戶進程的調用,都是標準的 POSIX 系統調用,好比 open、read、write 和 seek 等。VFS 對用戶進程有一個 上層 接口,這個接口就是著名的 POSIX 接口。
VFS 也有一個對於實際文件的 下層 接口,就是上圖中標記爲 VFS 的接口。這個接口包含許多功能調用,這樣 VFS 能夠使每個文件系統完成任務。所以,要建立一個能夠與 VFS 一塊兒使用的新文件系統,新文件系統的設計者必須確保它提供了 VFS 要求的功能。一個明顯的例子是從磁盤讀取特定的塊,而後將其放入文件系統的緩衝區高速緩存中,而後返回指向該塊的指針的函數。所以,VFS 具備兩個不一樣的接口:上一個到用戶進程,下一個到具體文件系統。
當系統啓動時,根文件系統在 VFS 中註冊。另外,當裝載其餘文件時,無論在啓動時仍是在操做過程當中,它們也必須在 VFS 中註冊。當一個文件系統註冊時,根文件系統註冊到 VFS。另外,在引導時或操做期間掛載其餘文件系統時,它們也必須向 VFS 註冊。當文件系統註冊時,其基本做用是提供 VFS 所需功能的地址列表、調用向量表、或者 VFS 對象。所以一旦文件系統註冊到 VFS,它就知道從哪裏開始讀取數據塊。
裝載文件系統後就能夠使用它了。好比,若是一個文件系統裝載到 /usr 而且一個進程調用它:
1open("/usr/include/unistd.h",O_RDONLY)
當解析路徑時, VFS 看到新的文件系統被掛載到 /usr,而且經過搜索已經裝載文件系統的超級塊來肯定它的超塊。而後它找到它所轉載的文件的根目錄,在那裏查找路徑 include/unistd.h。而後 VFS 建立一個 vnode 並調用實際文件系統,以返回全部的在文件 inode 中的信息。這個信息和其餘信息一塊兒複製到 vnode (內存中)。而這些其餘信息中最重要的是指向包含調用 vnode 操做的函數表的指針,好比 read、write 和 close 等。
當 vnode 被建立後,爲了進程調用,VFS 在文件描述符表中建立一個表項,並將它指向新的 vnode,最後,VFS 向調用者返回文件描述符,因此調用者能夠用它去 read、write 或者 close 文件。
當進程用文件描述符進行一個讀操做時,VFS 經過進程表和文件描述符肯定 vnode 的位置,並跟隨指針指向函數表,這樣就調用了處理 read 函數,運行在實際系統中的代碼並獲得所請求的塊。VFS 不知道請求時來源於本地硬盤、仍是來源於網絡中的遠程文件系統、CD-ROM 、USB 或者其餘介質,全部相關的數據結構以下圖所示
從調用者進程號和文件描述符開始,進而是 vnode,讀函數指針,而後是對實際文件系統的訪問函數定位。
4
文件系統的管理和優化
可以使文件系統工做是一回事,可以使文件系統高效、穩定的工做是另外一回事,下面咱們就來探討一下文件系統的管理和優化。
4.1
磁盤空間管理
文件一般存在磁盤中,因此如何管理磁盤空間是一個操做系統的設計者須要考慮的問題。在文件上進行存有兩種策略:分配 n 個字節的連續磁盤空間;或者把文件拆分紅多個並不必定連續的塊。在存儲管理系統中,主要有分段管理和 分頁管理 兩種方式。
正如咱們所看到的,按連續字節序列存儲文件有一個明顯的問題,當文件擴大時,有可能須要在磁盤上移動文件。內存中分段也有一樣的問題。不一樣的是,相對於把文件從磁盤的一個位置移動到另外一個位置,內存中段的移動操做要快不少。所以,幾乎全部的文件系統都把文件分割成固定大小的塊來存儲。
塊大小
一旦把文件分爲固定大小的塊來存儲,就會出現問題,塊的大小是多少?按照磁盤組織方式,扇區、磁道和柱面顯然均可以做爲分配單位。在分頁系統中,分頁大小也是主要因素。
擁有大的塊尺寸意味着每一個文件,甚至 1 字節文件,都要佔用一個柱面空間,也就是說小文件浪費了大量的磁盤空間。另外一方面,小塊意味着大部分文件將會跨越多個塊,所以須要屢次搜索和旋轉延遲才能讀取它們,從而下降了性能。所以,若是分配的塊太大會浪費空間;分配的塊過小會浪費時間。
記錄空閒塊
一旦指定了塊大小,下一個問題就是怎樣跟蹤空閒塊。有兩種方法被普遍採用,以下圖所示
第一種方法是採用磁盤塊鏈表,鏈表的每一個塊中包含很可能多的空閒磁盤塊號。對於 1 KB 的塊和 32 位的磁盤塊號,空閒表中每一個塊包含有 255 個空閒的塊號。考慮 1 TB 的硬盤,擁有大概十億個磁盤塊。爲了存儲所有地址塊號,若是每塊能夠保存 255 個塊號,則須要將近 400 萬個塊。一般,空閒塊用於保存空閒列表,所以存儲基本上是空閒的。
另外一種空閒空間管理的技術是位圖(bitmap),n 個塊的磁盤須要 n 位位圖。在位圖中,空閒塊用 1 表示,已分配的塊用 0 表示。對於 1 TB 硬盤的例子,須要 10 億位表示,即須要大約 130 000 個 1 KB 塊存儲。很明顯,和 32 位鏈表模型相比,位圖須要的空間更少,由於每一個塊使用 1 位。只有當磁盤快滿的時候,鏈表須要的塊纔會比位圖少。
若是空閒塊是長期連續的話,那麼空閒列表能夠改爲記錄連續分塊而不是單個的塊。每一個塊都會使用 8 位、16 位、32 位的計數來與每一個塊相聯,來記錄連續空閒塊的數量。最好的狀況是一個空閒塊能夠用兩個數字來表示:第一個空閒塊的地址和空閒塊的計數。另外一方面,若是磁盤嚴重碎片化,那麼跟蹤連續分塊要比跟蹤單個分塊運行效率低,由於不只要存儲地址,還要存儲數量。
這種狀況說明了一個操做系統設計者常常遇到的一個問題。有許多數據結構和算法能夠用來解決問題,可是選擇一個最好的方案須要數據的支持,而這些數據是設計者沒法預先擁有的。只有在系統部署完畢真正使用使用後纔會得到。
如今,回到空閒鏈表的方法,只有一個指針塊保存在內存中。建立文件時,所須要的塊從指針塊中取出。當它用完時,將從磁盤中讀取一個新的指針塊。相似地,刪除文件時,文件的塊將被釋放並添加到主存中的指針塊中。當塊被填滿時,寫回磁盤。
在某些特定的狀況下,這個方法致使了沒必要要的磁盤 IO,以下圖所示
上面內存中的指針塊僅有兩個空閒塊,若是釋放了一個含有三個磁盤塊的文件,那麼該指針塊就會溢出,必須將其寫入磁盤,那麼就會產生以下圖的這種狀況。
若是如今寫入含有三個塊的文件,已滿的指針不得再也不次讀入,這將會回到上圖 a 中的狀況。若是有三個塊的文件只是做爲臨時文件被寫入,在釋放它時,須要進行另外一次磁盤寫操做以將完整的指針塊寫回到磁盤。簡而言之,當指針塊幾乎爲空時,一系列短暫的臨時文件可能會致使大量磁盤 I/O。
避免大部分磁盤 I/O 的另外一種方法是拆分完整的指針塊。這樣,當釋放三個塊時,變化再也不是從 a - b,而是從 a - c,以下圖所示
如今,系統能夠處理一系列臨時文件,而不須要進行任何磁盤 I/O。若是內存中指針塊滿了,就寫入磁盤,半滿的指針塊從磁盤中讀入。這裏的思想是:要保持磁盤上的大多數指針塊爲滿的狀態(減小磁盤的使用),可是在內存中保留了一個半滿的指針塊。這樣,就能夠既處理文件的建立又同時能夠處理文件的刪除操做,而不會爲空閒表進行磁盤 I/O。
對於位圖,會在內存中只保留一個塊,只有在該塊滿了或空了的情形下,纔到磁盤上取另外一個塊。經過在位圖的單一塊上進行全部的分配操做,磁盤塊會緊密的彙集在一塊兒,從而減小了磁盤臂的移動。因爲位圖是一種固定大小的數據結構,因此若是內核是分頁的,就能夠把位圖放在虛擬內存中,在須要時將位圖的頁面調入。
4.2
磁盤配額
爲了防止一些用戶佔用太多的磁盤空間,多用戶操做一般提供一種磁盤配額(enforcing disk quotas)的機制。系統管理員爲每一個用戶分配最大的文件和塊分配,而且操做系統確保用戶不會超過其配額。咱們下面會談到這一機制。
在用戶打開一個文件時,操做系統會找到文件屬性和磁盤地址,並把它們送入內存中的打開文件表。其中一個屬性告訴文件全部者是誰。任何有關文件的增長都會記到全部者的配額中。
第二張表包含了每一個用戶當前打開文件的配額記錄,即便是其餘人打開該文件也同樣。如上圖所示,該表的內容是從被打開文件的全部者的磁盤配額文件中提取出來的。當全部文件關閉時,該記錄被寫回配額文件。
當在打開文件表中創建一新表項時,會產生一個指向全部者配額記錄的指針。每次向文件中添加一個塊時,文件全部者所用數據塊的總數也隨之增長,並會同時增長硬限制和軟限制的檢查。能夠超出軟限制,但硬限制不能夠超出。當已達到硬限制時,再往文件中添加內容將引起錯誤。一樣,對文件數目也存在相似的檢查。
什麼是硬限制和軟限制?硬限制是軟限制的上限。軟限制是爲會話或進程實際執行的限制。這容許管理員(或用戶)將硬限制設置爲容許它們但願容許的最大使用上限。而後,其餘用戶和進程能夠根據須要使用軟限制將其資源使用量自限制到更低的上限。
當一個用戶嘗試登錄,系統將檢查配額文件以查看用戶是否超出了文件數量或磁盤塊數量的軟限制。若是違反了任一限制,則會顯示警告,保存的警告計數減 1,若是警告計數爲 0 ,表示用戶屢次忽略該警告,於是將不容許該用戶登陸。要想再獲得登陸的許可,就必須與系統管理員協商。
若是用戶在退出系統時消除所超過的部分,他們就能夠再一次終端會話期間超過其軟限制,但不管什麼狀況下都不會超過硬限制。
4.3
文件系統備份
文件系統的毀壞要比計算機的損壞嚴重不少。不管是硬件仍是軟件的故障,只要計算機文件系統被破壞,要恢復起來都是及其困難的,甚至是不可能的。由於文件系統沒法抵禦破壞,於是咱們要在文件系統在被破壞以前作好數據備份,可是備份也不是那麼容易,下面咱們就來探討備份的過程。
許多人認爲爲文件系統作備份是不值得的,而且很浪費時間,直到有一天他們的磁盤壞了,他們才意識到事情的嚴重性。相對來講,公司在這方面作的就很到位。磁帶備份主要要處理好如下兩個潛在問題中的一個
這個問題主要是因爲外部條件的緣由形成的,好比磁盤破裂,水災火災等。
第二個問題一般是因爲用戶意外的刪除了本來須要還原的文件。這種狀況發生的很頻繁,使得 Windows 的設計者們針對 刪除 命令專門設計了特殊目錄,這就是 回收站(recycle bin),也就是說,在刪除文件的時候,文件自己並不真正從磁盤上消失,而是被放置到這個特殊目錄下,等之後須要的時候能夠還原回去。文件備份更主要是指這種狀況,可以容許幾天以前,幾周以前的文件從原來備份的磁盤進行還原。
作文件備份很耗費時間並且也很浪費空間,這會引發下面幾個問題。首先,是要備份整個文件仍是僅備份一部分呢?通常來講,只是備份特定目錄及其下的所有文件,而不是備份整個文件系統。
其次,對上次未修改過的文件再進行備份是一種浪費,於是產生了一種增量轉儲(incremental dumps) 的思想。最簡單的增量轉儲的形式就是週期性的作全面的備份,而天天只對增量轉儲完成後發生變化的文件作單個備份。
週期性:好比一週或者一個月
稍微好一點的方式是隻備份最近一次轉儲以來更改過的文件。固然,這種作法極大的縮減了轉儲時間,但恢復起來卻更復雜,由於最近的全面轉儲先要所有恢復,隨後按逆序進行增量轉儲。爲了方便恢復,人們每每使用更復雜的轉儲模式。
第三,既然待轉儲的每每是海量數據,那麼在將其寫入磁帶以前對文件進行壓縮就頗有必要。可是,若是在備份過程當中出現了文件損壞的狀況,就會致使破壞壓縮算法,從而使整個磁帶沒法讀取。因此在備份前是否進行文件壓縮需慎重考慮。
第四,對正在使用的文件系統作備份是很難的。若是在轉儲過程當中要添加,刪除和修改文件和目錄,則轉儲結果可能不一致。所以,由於轉儲過程當中須要花費數個小時的時間,因此有必要在晚上將系統脫機進行備份,然而這種方式的接受程度並不高。因此,人們修改了轉儲算法,記下文件系統的瞬時快照,即複製關鍵的數據結構,而後須要把未來對文件和目錄所作的修改複製到塊中,而不是處處更新他們。
磁盤轉儲到備份磁盤上有兩種方案:物理轉儲和邏輯轉儲。物理轉儲(physical dump) 是從磁盤的 0 塊開始,依次將全部磁盤塊按照順序寫入到輸出磁盤,並在複製最後一個磁盤時中止。這種程序的萬無一失性是其餘程序所不具有的。
第二個須要考慮的是壞塊的轉儲。製造大型磁盤而沒有瑕疵是不可能的,因此也會存在一些壞塊(bad blocks)。有時進行低級格式化後,壞塊會被檢測出來並進行標記,這種狀況的解決辦法是用磁盤末尾的一些空閒塊所替換。
然而,一些塊在格式化後會變壞,在這種狀況下操做系統能夠檢測到它們。一般狀況下,它能夠經過建立一個由全部壞塊組成的文件來解決問題,確保它們不會出如今空閒池中而且永遠不會被分配。那麼此文件是徹底不可讀的。若是磁盤控制器將全部的壞塊從新映射,物理轉儲仍是可以正常工做的。
Windows 系統有分頁文件(paging files) 和 休眠文件(hibernation files) 。它們在文件還原時不發揮做用,同時也不該該在第一時間進行備份。
物理轉儲和邏輯轉儲
物理轉儲的主要優勢是簡單、極爲快速(基本上是以磁盤的速度運行),缺點是全量備份,不能跳過指定目錄,也不能增量轉儲,也不能恢復我的文件的請求。所以句大多數狀況下不會使用物理轉儲,而使用邏輯轉儲。
邏輯轉儲(logical dump)從一個或幾個指定的目錄開始,遞歸轉儲自指定日期開始後更改的文件和目錄。所以,在邏輯轉儲中,轉儲磁盤上有一系列通過仔細識別的目錄和文件,這使得根據請求輕鬆還原特定文件或目錄。
既然邏輯轉儲是最經常使用的方式,那麼下面就讓咱們研究一下邏輯轉儲的通用算法。此算法在 UNIX 系統上廣爲使用,以下圖所示
待轉儲的文件系統,其中方框表明目錄,圓圈表明文件。黃色的項目表是自上次轉儲以來修改過。每一個目錄和文件都被標上其 inode 號。
此算法會轉儲位於修改文件或目錄路徑上的全部目錄(也包括未修改的目錄),緣由有兩個。第一是可以在不一樣電腦的文件系統中恢復轉儲的文件。經過這種方式,轉儲和從新存儲的程序可以用來在兩個電腦之間傳輸整個文件系統。第二個緣由是可以對單個文件進行增量恢復。
邏輯轉儲算法須要維持一個 inode 爲索引的位圖(bitmap),每一個 inode 包含了幾位。隨着算法的進行,位圖中的這些位會被設置或清除。算法的執行分紅四個階段。第一階段從起始目錄(本例爲根目錄)開始檢查其中全部的目錄項。對每個修改過的文件,該算法將在位圖中標記其 inode。算法還會標記並遞歸檢查每個目錄(無論是否修改過)。
在第一階段結束時,全部修改過的文件和所有目錄都在位圖中標記了,以下圖所示
理論上來講,第二階段再次遞歸遍歷目錄樹,並去掉目錄樹中任何不包含被修改過的文件或目錄的標記。本階段執行的結果以下
注意,inode 編號爲 十、十一、1四、2七、29 和 30 的目錄已經被去掉了標記,由於它們所包含的內容沒有修改。它們也不會轉儲。相反,inode 編號爲 5 和 6 的目錄自己儘管沒有被修改過也要被轉儲,由於在新的機器上恢復當日的修改時須要這些信息。爲了提升算法效率,能夠將這兩階段的目錄樹遍歷合二爲一。
如今已經知道了哪些目錄和文件必須被轉儲了,這就是上圖 b 中標記的內容,第三階段算法將以節點號爲序,掃描這些 inode 並轉儲全部標記爲需轉儲的目錄,以下圖所示
爲了進行恢復,每一個被轉儲的目錄都用目錄的屬性(全部者、時間)做爲前綴。
最後,在第四階段,上圖中被標記的文件也被轉儲,一樣,由其文件屬性做爲前綴。至此,轉儲結束。
從轉儲磁盤上還原文件系統很是簡單。一開始,須要在磁盤上建立空文件系統。而後恢復最近一次的完整轉儲。因爲磁帶上最早出現目錄,因此首先恢復目錄,給出文件系統的框架(skeleton),而後恢復文件系統自己。在完整存儲以後是第一次增量存儲,而後是第二次重複這一過程,以此類推。
儘管邏輯存儲十分簡單,可是也會有一些棘手的問題。首先,既然空閒塊列表並非一個文件,那麼在全部被轉儲的文件恢復完畢以後,就須要從零開始從新構造。
另一個問題是關於連接。若是文件連接了兩個或者多個目錄,而文件只能還原一次,那麼而且全部指向該文件的目錄都必須還原。
還有一個問題是,UNIX 文件實際上包含了許多 空洞(holes)。打開文件,寫幾個字節,而後找到文件中偏移了必定距離的地址,又寫入更多的字節,這麼作是合法的。但二者之間的這些塊並不屬於文件自己,從而也不該該在其上進行文件轉儲和恢復。
最後,不管屬於哪個目錄,特殊文件,命名管道以及相似的文件都不該該被轉儲。
4.4
文件系統的一致性
影響可靠性的一個因素是文件系統的一致性。許多文件系統讀取磁盤塊、修改磁盤塊、再把它們寫回磁盤。若是系統在全部塊寫入以前崩潰,文件系統就會處於一種不一致(inconsistent)的狀態。若是某些還沒有寫回的塊是索引節點塊,目錄塊或包含空閒列表的塊,則此問題是很嚴重的。
爲了處理文件系統一致性問題,大部分計算機都會有應用程序來檢查文件系統的一致性。例如,UNIX 有 fsck;Windows 有 sfc,每當引導系統時(尤爲是在崩潰後),均可以運行該程序。
能夠進行兩種一致性檢查:塊的一致性檢查和文件的一致性檢查。爲了檢查塊的一致性,應用程序會創建兩張表,每一個包含一個計數器的塊,最初設置爲 0 。第一個表中的計數器跟蹤該塊在文件中出現的次數,第二張表中的計數器記錄每一個塊在空閒列表、空閒位圖中出現的頻率。
而後檢驗程序使用原始設備讀取全部的 inode,忽略文件的結構,只返回從零開始的全部磁盤塊。從 inode 開始,很容易找到文件中的塊數量。每當讀取一個塊時,該塊在第一個表中的計數器 + 1,應用程序會檢查空閒塊或者位圖來找到沒有使用的塊。空閒列表中塊的每次出現都會致使其在第二表中的計數器增長。
若是文件系統一致,則每個塊或者在第一個表計數器爲 1,或者在第二個表計數器中爲 1,以下圖所示
可是當系統崩潰後,這兩張表可能以下所示
其中,磁盤塊 2 沒有出如今任何一張表中,這稱爲 塊丟失(missing block)。儘管塊丟失不會形成實際的損害,但它的確浪費了磁盤空間,減小了磁盤容量。塊丟失的問題很容易解決,文件系統檢驗程序把他們加到空閒表中便可。
有可能出現的另一種狀況以下所示
其中,塊 4 在空閒表中出現了 2 次。這種解決方法也很簡單,只要從新創建空閒表便可。
最糟糕的狀況是在兩個或者多個文件中出現同一個數據塊,以下所示
好比上圖的磁盤塊 5,若是其中一個文件被刪除,塊 5 會被添加到空閒表中,致使一個塊同時處於使用和空閒的兩種狀態。若是刪除這兩個文件,那麼在空閒表中這個磁盤塊會出現兩次。
文件系統檢驗程序採起的處理方法是,先分配一磁盤塊,把塊 5 中的內容複製到空閒塊中,而後把它插入到其中一個文件中。這樣文件的內容未改變,雖然這些內容能夠確定是不對的,但至少保證了文件的一致性。這一錯誤應該報告給用戶,由用戶檢查受檢狀況。
除了檢查每一個磁盤塊計數的正確性以外,文件系統還會檢查目錄系統。這時候會用到一張計數器表,但這時是一個文件(而不是一個塊)對應於一個計數器。程序從根目錄開始檢驗,沿着目錄樹向下查找,檢查文件系統的每一個目錄。對每一個目錄中的文件,使其計數 + 1。
注意,因爲存在硬鏈接,一個文件可能出如今兩個或多個目錄中。而遇到符號連接是不計數的,不會對目標文件的計數器 + 1。
在檢驗程序完成後,會獲得一張由 inode 索引的表,說明每一個文件和目錄的包含關係。檢驗程序會將這些數字與存儲在文件 inode 中的連接數目作對比。若是 inode 節點的連接計數大戶目錄項個數,這時即便全部文件從目錄中刪除,這個計數仍然不是 0 ,inode 不會被刪除。這種錯誤不嚴重,卻由於存在不屬於任何目錄的文件而浪費了磁盤空間。
另外一種錯誤則是潛在的風險。若是同一個文件連接兩個目錄項,可是 inode 連接計數只爲 1,若是刪除了任何一個目錄項,對應 inode 連接計數變爲 0。當 inode 計數爲 0 時,文件系統標誌 inode 爲 未使用,並釋放所有的塊。這會致使其中一個目錄指向一未使用的 inode,而頗有可能其塊立刻就被分配給其餘文件。
4.5
文件系統性能
訪問磁盤的效率要比內存滿的多,是時候又祭出這張圖了
從內存讀一個 32 位字大概是 10ns,從硬盤上讀的速率大概是 100MB/S,對每一個 32 位字來講,效率會慢了四倍,另外,還要加上 5 - 10 ms 的尋道時間等其餘損耗,若是隻訪問一個字,內存要比磁盤快百萬數量級。因此磁盤優化是頗有必要的,下面咱們會討論幾種優化方式
高速緩存
最經常使用的減小磁盤訪問次數的技術是使用 塊高速緩存(block cache) 或者 緩衝區高速緩存(buffer cache)。高速緩存指的是一系列的塊,它們在邏輯上屬於磁盤,但實際上基於性能的考慮被保存在內存中。
管理高速緩存有不一樣的算法,經常使用的算法是:檢查所有的讀請求,查看在高速緩存中是否有所須要的塊。若是存在,可執行讀操做而無須訪問磁盤。若是檢查塊再也不高速緩存中,那麼首先把它讀入高速緩存,再複製到所需的地方。以後,對同一個塊的請求都經過高速緩存來完成。
高速緩存的操做以下圖所示
因爲在高速緩存中有許多塊,因此須要某種方法快速肯定所需的塊是否存在。經常使用方法是將設備和磁盤地址進行散列操做,而後,在散列表中查找結果。具備相同散列值的塊在一個鏈表中鏈接在一塊兒(這個數據結構是否是很像 HashMap?),這樣就能夠沿着衝突鏈查找其餘塊。
若是高速緩存已滿,此時須要調入新的塊,則要把原來的某一塊調出高速緩存,若是要調出的塊在上次調入後已經被修改過,則須要把它寫回磁盤。這種狀況與分頁很是類似,全部經常使用的頁面置換算法咱們以前已經介紹過,若是有不熟悉的小夥伴能夠參考 好比 FIFO 算法、第二次機會算法、LRU 算法、時鐘算法、老化算法等。它們都適用於高速緩存。
塊提早讀
第二個明顯提升文件系統的性能是,在須要用到塊以前,試圖提早將其寫入高速緩存,從而提升命中率。許多文件都是順序讀取。若是請求文件系統在某個文件中生成塊 k,文件系統執行相關操做而且在完成以後,會檢查高速緩存,以便肯定塊 k + 1 是否已經在高速緩存。若是不在,文件系統會爲 k + 1 安排一個預讀取,由於文件但願在用到該塊的時候可以直接從高速緩存中讀取。
固然,塊提早讀取策略只適用於實際順序讀取的文件。對隨機訪問的文件,提早讀絲絕不起做用。甚至還會形成阻礙。
減小磁盤臂運動
高速緩存和塊提早讀並非提升文件系統性能的惟一方法。另外一種重要的技術是把有可能順序訪問的塊放在一塊兒,固然最好是在同一個柱面上,從而減小磁盤臂的移動次數。當寫一個輸出文件時,文件系統就必須按照要求一次一次地分配磁盤塊。若是用位圖來記錄空閒塊,而且整個位圖在內存中,那麼選擇與前一塊最近的空閒塊是很容易的。若是用空閒表,而且鏈表的一部分存在磁盤上,要分配緊鄰的空閒塊就會困難不少。
不過,即便採用空閒表,也能夠使用 塊簇 技術。即不用塊而用連續塊簇來跟蹤磁盤存儲區。若是一個扇區有 512 個字節,有可能系統採用 1 KB 的塊(2 個扇區),但卻按每 2 塊(4 個扇區)一個單位來分配磁盤存儲區。這和 2 KB 的磁盤塊並不相同,由於在高速緩存中它仍然使用 1 KB 的塊,磁盤與內存數據之間傳送也是以 1 KB 進行,但在一個空閒的系統上順序讀取這些文件,尋道的次數能夠減小一半,從而使文件系統的性能大大改善。若考慮旋轉定位則能夠獲得這類方法的變體。在分配塊時,系統儘可能把一個文件中的連續塊存放在同一個柱面上。
在使用 inode 或任何相似 inode 的系統中,另外一個性能瓶頸是,讀取一個很短的文件也須要兩次磁盤訪問:一次是訪問 inode,一次是訪問塊。一般狀況下,inode 的放置以下圖所示
其中,所有 inode 放在靠近磁盤開始位置,因此 inode 和它所指向的塊之間的平均距離是柱面組的一半,這將會須要較長時間的尋道時間。
一個簡單的改進方法是,在磁盤中部而不是開始處存放 inode ,此時,在 inode 和第一個塊之間的尋道時間減爲原來的一半。另外一種作法是:將磁盤分紅多個柱面組,每一個柱面組有本身的 inode,數據塊和空閒表,如上圖 b 所示。
固然,只有在磁盤中裝有磁盤臂的狀況下,討論尋道時間和旋轉時間纔是有意義的。如今愈來愈多的電腦使用 固態硬盤(SSD),對於這些硬盤,因爲採用了和閃存一樣的製造技術,使得隨機訪問和順序訪問在傳輸速度上已經較爲相近,傳統硬盤的許多問題就消失了。可是也引起了新的問題。
磁盤碎片整理
在初始安裝操做系統後,文件就會被不斷的建立和清除,因而磁盤會產生不少的碎片,在建立一個文件時,它使用的塊會散佈在整個磁盤上,下降性能。刪除文件後,回收磁盤塊,可能會形成空穴。
磁盤性能能夠經過以下方式恢復:移動文件使它們相互挨着,並把全部的至少是大部分的空閒空間放在一個或多個大的連續區域內。Windows 有一個程序 defrag 就是作這個事兒的。Windows 用戶會常用它,SSD 除外。
磁盤碎片整理程序會在讓文件系統上很好地運行。Linux 文件系統(特別是 ext2 和 ext3)因爲其選擇磁盤塊的方式,在磁盤碎片整理上通常不會像 Windows 同樣困難,所以不多須要手動的磁盤碎片整理。並且,固態硬盤並不受磁盤碎片的影響,事實上,在固態硬盤上作磁盤碎片整理反卻是畫蛇添足,不只沒有提升性能,反而磨損了固態硬盤。因此碎片整理只會縮短固態硬盤的壽命。