【梳理】簡明操作系統原理 一個簡易文件系統的實現(內附文檔高清截圖)

參考教材:
Operating Systems: Three Easy Pieces
Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
在線閱讀:
http://pages.cs.wisc.edu/~remzi/OSTEP/
University of Wisconsin Madison 教授 Remzi Arpaci-Dusseau 認爲課本應該是免費的
————————————————————————————————————————
這是專業必修課《操作系統原理》的複習指引。
在本文的最後附有複習指導的高清截圖。需要掌握的概念在文檔截圖中以藍色標識,並用可讀性更好的字體顯示 Linux 命令和代碼。代碼部分語法高亮。
操作系統原理不是語言課,本複習指導對用到的編程語言的語法的講解也不會很細緻。如果不知道代碼中的一些關鍵字或函數的具體用法,你應該自行查找相關資料。

一個簡易文件系統的實現

在本章,我們實現一個名爲VSFS(Very simple file system)的文件系統。

1、構思一個文件系統,主要通過兩方面:
一是數據結構。在磁盤上需要採用什麼結構來存儲數據和元數據呢?
二是訪問方法。當進程發出open()、read()、write()等系統調用時,應當怎樣處理?需要讀寫哪些數據結構?處理效率如何?

2、首先我們設置一個塊大小:4 KB。這個塊大小比較常見。設置塊大小的原因主要有二:
(1)防止單次傳輸的數據量過小進而導致性能低下。
(2)整體進行管理。我們不可能爲每個字節記錄相應的用於管理的數據。

3、文件系統的結構我們設計成這樣:

首先是數據區(data region)。顧名思義,這部分自然用來存放數據。
接下來是索引節點(index node)區。索引節點包含了文件的元數據,包括文件大小、所在的塊、訪問權限、修改日期、訪問日期、文件類型等信息。所有的文件系統也有類似索引節點的數據結構。ib和db分別代表索引節點位映像(bitmap)、數據區位映像。位映像的每一位都對應一個塊,標記其是否已經使用。S區域叫做超級塊(super block),主要包括文件系統本身的基本信息,比如索引節點數、數據塊數、各區域的起始位置等。S區也很可能包含一個魔數,方便應用程序快速識別文件系統的類型。

4、Ext2的索引節點至少包含以下信息:

回到我們實現的VSFS上來。
一個索引節點需要包含一些指針,指向文件在磁盤上存儲的塊,稱爲直接指針(direct pointer)。如果文件比較大,就會把這個區域用完。所以我們需要想其它辦法。
一個常用的辦法是:把存儲直接指針的區域的一個指針改爲間接指針(indirect pointer)。當文件存儲的塊數比較多的時候,就分配一個新的塊,裏面包含了額外的指針。而間接指針指向這個新分配的區域。如果還是不夠,就把這個分配再做一次,直到直接指針的數量足夠爲止。
還可以使用二重間接指針(double indirect pointer):在索引節點中加入一個指針,指向一個塊,塊裏全部是指向數據區的直接指針。當然,還可以再進一步,使用三重間接指針(triple indirect pointer)。原理很相似,這裏不多說。
這種思想稱爲多級索引(multi-level index)。
Linux ext2和ext3,NetApp的WAFL都採用多級索引方式。而SGI XFS和Linux ext4則採用“指針+長度”的方式來描述數據在磁盤上的位置。一個“指針+長度”結構可以代替大量的直接指針來描述一段連續的數據的存儲位置。
研究表明,計算機中存儲的絕大多數文件都很小。所以,索引節點一般都會留有一些位置存儲直接指針;只有在存儲更大的文件的時候,才啓用多級索引或基於範圍(extent)的表示——(頭)指針與長度。多數時候,一個索引節點包含12個直接指針。當塊大小爲4 KB時,這種直接的表示方法最多可以表示不超過48 KB的小文件在磁盤上的分佈。
當然,計算機在發展,文件系統也是。一些新型的文件系統可能採用完全不同的方式來索引文件。

5、下面我們爲VSFS構造一個目錄的結構。目錄的本質是一張表,裏面存儲了該目錄下的各個文件的基本信息。我們採用這樣的結構:

目錄的每一項都有兩個成員:與該目錄關聯的文件的索引節點號,和該項記錄的長度。由上圖可以看出,這個目錄記錄了5項,分別是當前目錄、父目錄、foo、bar、foobar_is_a_pretty_longname的信息。strlen一項代表文件名的長度(文件名末尾的’\0’也算)。foobar_is_a_pretty_longname正好比foo、bar多出24個字符,導致記錄它的項的長度從12增加到了36。
如果刪除一個目錄下的文件,那麼目錄中對應的記錄就會被擦掉並留空。要有專門的方法來表示這種情況(例如將索引號記0)。當添加新的項時,可以直接寫入中間的空白部分。
文件系統常常把目錄也當作一種特殊的文件對待,所以一個目錄也有它的索引節點號。如果採用多級索引,那麼新申請的存儲間接指針的塊自然也會有索引節點號。
線性表並不是存儲目錄的唯一方法。例如XFS就採用B樹作爲目錄結構。

6、如果用鏈表實現索引節點或等效的結構,會導致順序訪問和隨機訪問(尤其後者)的性能大幅降低。爲了提升性能,有的文件系統會在內存中保留這種表,而不是向剛纔說的那樣將指向下一片連續區域的指針存在專門的數據塊中。文件分配表(file allocation table,FAT)採用的就是這種基本結構。FAT將若干個連續扇區視爲一個簇(cluster),以簇爲單位來存取數據。表中每一項的索引是簇編號,而項的內容是所屬文件的下一個數據塊或EOF(End of File)標記。FAT與UNIX文件系統還有其它的不同,比如沒有索引節點,使用目錄中的項來存放元數據並指向文件的第一個數據塊,這也同樣爲實現硬鏈接提供了基礎。

7、位映像只是一種管理空閒空間的方法。一些早期的文件系統使用專門的空閒列表,用一個指針指向空閒空間的第一個塊,塊內具有指向下一個空閒塊的指針。當有塊需要被寫入時,相應的頭指針就要改變。
現代的文件系統則採用較複雜的結構。XFS使用B樹來記錄空閒塊。

8、如果使用位映像來標記空閒區域,那麼創建新文件時,申請新的索引節點和數據塊後,要把相應的位標爲已佔用。
文件系統在爲新文件分配空間時,常常使用預分配(pre-allocation)的啓發式策略:儘量查找一段連續的空閒區域。

9、下面通過一個例子演示VSFS如何實現讀取文件:

設要打開一個文件 /foo/bar,一共12 KB,正好佔了三個塊。
當調用open(bar)時,文件系統需要先找到根目錄的索引節點,然後依次查找foo目錄和文件bar的索引節點,來獲得諸如訪問權限和文件大小之類的基本信息。文件系統根據需要打開的文件的絕對路徑去查找需要的索引節點。通過索引節點號可以找到索引節點的位置。根沒有父目錄,要通過專門的方法來訪問根的索引節點(想一想:在數據結構的學習中,通過一個專門的變量指向二叉樹的根節點)。在UNIX文件系統中,根節點的索引節點號一般都是2。
找到根的索引節點後,就從索引節點中讀出根目錄的數據塊的分佈位置並進行訪問。此時,文件系統通過索引節點中的指針來讀取根目錄文件,查找包含foo的項。找到後,文件系統就去查找foo的索引節點。如此遞歸查找,直到找到bar的索引節點。找到以後,就將其讀入內存,檢查權限。權限檢查通過後,就分配一個文件描述符,並更新已打開文件表,將文件描述符返回給用戶。
打開以後,程序就可以進行read()和lseek()操作了。進行讀操作時,先從文件的索引節點中找到需要讀取的數據塊,讀取,然後在索引節點中修改最後訪問時間。同時,內存中的已打開文件表也要被更新,因爲要修改偏移量。
讀取完畢關閉文件的時候,工作量就少多了:只需要釋放文件描述符。關閉文件不引發磁盤IO。
open引發的IO數量與路徑的級數成正比例,因爲每訪問一級子目錄都需要讀取對應的索引節點和目錄文件。但是IO個數不是決定open操作的耗時的唯一因素。如果目錄中的文件非常多,讀完目錄文件就需要較長的時間。

10、我們繼續通過一個例子來演示VSFS的寫入文件操作:

如果需要創建新文件並寫入或追加內容,那麼還需要分配新的塊。創建文件時,需要更新索引節點的位映像,還要在新文件所在目錄寫入新的條目,所在目錄對應的索引節點也要更新,然後爲新文件創建索引節點。創建一個空文件不寫入數據區,但需要在找不到該文件的索引節點時創建新的索引節點。
寫入新文件時,因爲要在數據區寫入,所以索引節點和數據區的位映像都要更新。先後順序是:先讀取新文件的索引節點,在數據區位映像中查找一個數據塊,確認對應的塊空閒,就將這一位標記爲佔用,然後在數據區寫入數據,並更新索引節點。

11、由上面兩個例子我們可以看到,讀寫操作會產生很多IO。怎樣提升磁盤IO效率呢?答案是:緩存。非常多的文件系統在系統啓動時申請一部分內存空間作爲緩存。如果這段空間是靜態分配的,它們常常佔據內存的10 %。不過,現代的文件系統採用動態分配的方式。具體來說,是採用統一頁緩存(unified page cache)的方式,使得在內存和文件系統之間的分配方式更靈活。這部分緩存佔用的空間視需要而定。
存儲空間的靜態分配和動態分配各有優缺點。靜態分配更容易實現,而且性能更容易預測,還容易保證每個用戶都能儘量公平地分得空間。但動態分配則令空間利用率更好,但實現更復雜,並且有時還會導致性能降低:一些用戶因爲某段時間對空間需求較少,分配給它們的資源被分配給了其它用戶;而當這些用戶又需要更多空間時,可能需要等待其它用戶釋放資源。

12、出於性能的考慮,寫入緩存會延遲寫入。但是如果在存盤之前發生了系統故障或斷電,未存盤的數據就會丟失。如果確實需要立即將數據寫入磁盤,應用程序往往調用fsync(),或者通過直接I / O(Direct I / O)或Raw Disk方式繞過文件系統讀寫磁盤。不過,多數程序在寫入緩存上都遵循文件系統的安排。

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

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