Linux文件系統(tǒng)十問
關(guān)于文件系統(tǒng),相信大家都不陌生。身為攻城獅的我們幾乎天天都會與之打交道,但是細(xì)深剖一下,其中又有多少是我們理解深度不夠的呢。那么讓我們一起來看一下下面這一組 Linux 文件系統(tǒng)相關(guān)的問題吧:
1、機械磁盤隨機讀寫時速度非常慢,操作系統(tǒng)是采用什么技巧來提高隨機讀寫的性能的? 2、touch 一個新的空文件占用磁盤空間嗎?占用的話占用多少? 3、新建一個空目錄占用磁盤空間嗎?占用多少?和新建一個文件相比,哪個占用的更大? 4、你知道文件名是記錄在磁盤的什么地方嗎? 5、文件名最長多長?受什么制約? 6、文件名太長了會影響系統(tǒng)性能嗎?為什么會產(chǎn)生影響? 7、一個目錄下最多能建立多少個文件? 8、新建一個內(nèi)容大小 1 k 的文件,實際會占用多大的磁盤空間? 9、向操作系統(tǒng)發(fā)起讀取文件 2 Byte 的命令,操作系統(tǒng)實際會讀取多少呢? 10、我們使用文件時要怎么樣來能提高磁盤IO速度?
如果你能想也不用想的就回答上來百分八十的問題,那么請關(guān)掉本篇文章吧。如果不能,而且你也像作者一樣對有窺探操作系統(tǒng)隱私的嗜好,那么就請隨我一起來探索文件系統(tǒng)的這些有趣的地方,相信理解了這些之后對我們手中的工作會有很大的幫助。這篇文章實驗所用文件系統(tǒng)是 ext 系的。
一、磁盤構(gòu)成及分區(qū)
1、磁盤物理結(jié)構(gòu)
還是先從最基本的磁盤物理結(jié)構(gòu)說起吧,注意本文只討論機械磁盤,SSD 不在本文討論范圍之內(nèi)。我們?nèi)祟惞芾砣魏问挛锟偸橇?xí)慣先劃分出一定的結(jié)構(gòu),在此規(guī)則的基礎(chǔ)上進行管理。軍隊分軍、師、旅、團和營。公司分事業(yè)群、部門、中心和小組。然后對于管理磁盤,分磁盤面、磁頭、磁道、柱面和扇區(qū)。
磁盤面:磁盤是由一疊磁盤面組成,見圖。
磁頭(Heads):每個磁頭對應(yīng)一個磁盤面,負(fù)責(zé)該磁盤面上的數(shù)據(jù)的讀寫。。
磁道(Track):每個盤面會圍繞圓心劃分出多個同心圓圈,每個圓圈叫做一個磁道。
柱面(Cylinders):所有盤片上的同一位置的磁道組成的立體叫做一個柱面。
扇區(qū)(Sector):以磁道為單位管理磁盤仍然太大,所以計算機前輩們又把每個磁道劃分出了多個扇區(qū),見下右圖

本人愛上 Linux 的一個原因就是只要你愿意下功夫,你就能把 Linux 的內(nèi)部邏輯徹底鋪開來看,這點比 Windows 要好太多了。Linux 上可以通過 fdisk 命令,來查看當(dāng)前系統(tǒng)使用的磁盤的這些物理信息。

以上是我本人的一臺虛擬機的磁盤物理信息??梢钥闯鑫业拇疟P有 255 個 heads,也就是說共有 255 個盤面。3263 個 cylinders,也就是說每個盤面上都有 3263 個磁道, 63 sectors/track 說的是每個磁道上共有 63 個扇區(qū)。命令結(jié)果也給出了 Sector size 的值是 512 bytes。那我們動筆算一下該磁盤的大小吧。
255 盤面 ?* 3263 柱面 * 63 扇區(qū) * 每個扇區(qū) 512 bytes = 26839088640 byte。
結(jié)果是 26.8 G,和磁盤的總大小基本相符(至于fdisk給出的詳細(xì)結(jié)果相差了約4M的大小,筆者也沒有弄徹底明白,有興趣的讀者可以繼續(xù)研究)。
不過要注意一點就是上面的盤面等數(shù)據(jù)是邏輯上的,是物理盤面映射轉(zhuǎn)化而來的,這個轉(zhuǎn)化關(guān)系我現(xiàn)在還沒搜到特別好的資料。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


分區(qū)是操作系統(tǒng)對磁盤進行管理的第一步,這也是我們?nèi)魏我粋€計算機使用者都非常熟悉的概念。例如Windows下的C、D、E、F盤。那么請思考一下,
思考:前面的磁盤的詳細(xì)物理結(jié)構(gòu)已經(jīng)有了,如果讓你把整塊磁盤分成C、D等分區(qū),你會怎么分呢?
方案一:255 個盤面,C 盤是 0-100 盤面, D 盤是 101-200 個盤面, ……
方案二:3263 個柱面,C 盤 0-1000 個柱面,D 盤 1001-20001 個柱面, ……
對于以上的兩個方案,你會選擇哪一種呢??先說下磁盤 IO 時的過程。
第一步,首先是磁頭徑向移動來尋找數(shù)據(jù)所在的磁道。這部分時間叫尋道時間。
第二步,找到目標(biāo)磁道后通過盤面旋轉(zhuǎn),將目標(biāo)扇區(qū)移動到磁頭的正下方。
第三步,向目標(biāo)扇區(qū)讀取或者寫入數(shù)據(jù)。到此為止,一次磁盤 IO 完成。
故單次磁盤 IO 時間 = 尋道時間 + 旋轉(zhuǎn)延遲 + 存取時間。
對于旋轉(zhuǎn)延時,現(xiàn)在主流服務(wù)器上經(jīng)常使用的是1W轉(zhuǎn)/分鐘的磁盤,每旋轉(zhuǎn)一周所需的時間為60*1000/10000=6ms,故其旋轉(zhuǎn)延遲為(0-6ms)。對于存取時間,一般耗時較短,為零點幾 ms。對于尋道時間,現(xiàn)代磁盤大概在 3-15 ms,其中尋道時間大小主要受磁頭當(dāng)前所在位置和目標(biāo)磁道所在位置相對距離的影響。 其實采用哪一種,最主要看的是那種方式性能更快。因為同一分區(qū)下的數(shù)據(jù)經(jīng)常會一起讀取,假如采用第一種,那么這樣磁頭就需要在 3000 多個 track 間不停地跳來跳去,這樣磁盤的尋道時間就會翻倍,磁盤性能就會下降。而對于方案二,假如對于磁盤C,只需要在磁頭在 1-1000 個磁道間移動就可以了,大大降低了尋道時間。(實際上分區(qū)并不是從 0 開始的,磁盤的第一個磁道對應(yīng)的柱面會被用來安裝引導(dǎo)加載程序以及磁盤分區(qū)表)。所以,方案二的分區(qū)方式可以降低磁盤 IO 時間中的尋道時間部分,所以所有的操作系統(tǒng)采用的都是方案二,沒有用方案一的。
在Linux下使用過fdisk進行分區(qū)的話可以注意到以下信息。


這充分證明了操作系統(tǒng)是采用方案二的。
回到開篇問題 1,操作系統(tǒng)是采用什么技巧來降低隨機讀寫的性能問題的呢?操作系統(tǒng)通過按磁道對應(yīng)的柱面劃分分區(qū),來降低磁盤 IO 所花費的的尋道時間 ,進而提高磁盤的讀寫性能。
二、目錄與文件
1、引子
好了,磁盤基礎(chǔ)都說完了,那我們正式進入主題,開始我們 Linux 文件系統(tǒng)相關(guān)的討論吧。文件系統(tǒng)不就是目錄和文件嗎?這二位可是我們熟悉的不能再熟悉的家伙了??赡愦_認(rèn)它不是你的那位熟悉的陌生人么?我先來來創(chuàng)建個空目錄和空文件吧,查看結(jié)果如下圖:

我們都知道第五列顯示的是占用的空間大小,那么我來提個幾個小小的問題吧。
1)為什么目錄占用的空間是 4096?
2)為什么空文件占用的空間卻是 0?
3)如果空文件真占用 0 byte 空間,那么該文件的文件名、創(chuàng)建者以及權(quán)限-rw-rw-r—等文件夾相關(guān)的信息都存到哪兒去了?
2、我就不信空文件不占用空間
為了解開這個謎底,需要借助 df 命令。輸入 df –i,

Linux 結(jié)果中紅框位置處顯示的是 inodes 的相關(guān)信息,如果你對 inode 的概念不熟悉,你可以暫時把它當(dāng)成一個操作系統(tǒng)秘密管理的一個家伙,會占用空間就行了。接下來我 touch 一個空的文件后再次 df -i。

雖然前面操作系統(tǒng)告訴我們,一個新建的空文件占用的空間是 0。但是這個實驗卻證明操作系統(tǒng)“欺騙”了我們,它消耗掉了一個 inode。那么 inode 的節(jié)點大小是多少呢,使用 dumpe2fs 命令可以幫助我們查看到這個東東的實際大小。在輸出的結(jié)果中我們可以找到下面這行。

它告訴我們每個 inode 的大小是 256 Byte。當(dāng)然這個大小每臺機器都會不一樣,它實際上是在系統(tǒng)格式化磁盤的時候決定的。
好了,開篇第二個問題也有答案了。原來新建一個空的文件是會占用磁盤空間的,實際占用的是 256 Byte。哦,不,準(zhǔn)確的說法應(yīng)該是一個 inode size,具體的值是在格式化時決定的。
再說說新建空目錄吧,前面說了新建空目錄會占用4KB的磁盤空間。那么僅僅如此嗎?我們同樣在新建目錄前后都使用df –i來監(jiān)視系統(tǒng) inode 的占用。
原來目錄也是會占用一個 inode 節(jié)點的,第三個問題也有了答案了,新建一個空目錄會占用磁盤空間 4KB + inode size。哦,這個在你的系統(tǒng)上也不一定是4K,它實際上一個 block size。同樣在 dumpe2fs 下可以看到。

只不過我的磁盤在格式化時采用的是 4KB 的大小,呵呵!
3、神秘的空目錄的4KB
前面的謎團解開了,可以作為攻城獅的我對另外一個東西產(chǎn)生了好奇心。就是空目錄占用的那 4KB,這些空間是用來存什么的呢?好神秘呀。cd 到我們新建的目錄下查看。

我們再新建兩個空的文件,再查看下目錄的空間占用情況。

貌似,沒有什么新發(fā)現(xiàn)。因為空文件不占用 block,所以這里顯示的仍然是目錄占用的 block,和之前大小沒有變化。那么我繼續(xù)使用 php 腳本創(chuàng)建 100 個文件名長度為 32Byte 的空文件。

這時我們發(fā)現(xiàn)目錄占用的磁盤空間變大了,成了 3 個 Block 了。哈哈,這就解答了我們開篇的第四個問題,文件名是存在目錄占用的 block 中的。接下來我又還證明了每個目錄 block 中能保存的文件名個數(shù)是和文件名的長度有關(guān)的(好像有點廢話的意思,不過親手證明自己的猜想還是有點小爽的)。我又另外新建了個空目錄,創(chuàng)建了 100 個文件名長度為 32*3 個空文件,該臨時目錄占用的磁盤空間如下:

你可能會問我為什么文件名變成了 3 倍后,占用的 block 數(shù)目為什么沒有變成 3 倍。其實Linux文件系統(tǒng)關(guān)于文件的結(jié)構(gòu)體中除了文件名以外,還有其它的一些字段的,文件名變長3倍不會導(dǎo)致結(jié)構(gòu)體變大 3 倍的,這點可以參考 Linux 系統(tǒng)內(nèi)核相關(guān)書籍。
好了,到現(xiàn)在開篇問題 6 也有了答案了。文件名長了當(dāng)然會對系統(tǒng)性能產(chǎn)生影響,因為這可能會導(dǎo)致更多的磁盤 IO。很多程序員都喜歡將文件命名為有意義的長串,使人一看文件名就知道用途。當(dāng)然我沒說這樣不好,但是如果你的文件數(shù)量相當(dāng)大的時候,你就要考慮你的文件名是否導(dǎo)致你的目錄 block 占用太多了。
占用的空間倒是小事,磁盤很便宜,但是你得考慮下在目錄下查找文件時操作系統(tǒng)的感受,操作系統(tǒng)可需要用你你提供的文件名進行字符串比較,而且運氣不好的話需要將其名下所有 block 都搞一遍才行啊。(當(dāng)然了,你的文件名長度不變態(tài),而且數(shù)量沒有達到十萬數(shù)量級的話實際上這個開銷也不會太大,但是這個開銷你還是知道的為好)
至于開篇問題 5,文件名最長多長。實際上Linux操作系統(tǒng)就是為了避免程序員不節(jié)制地使用長文件名,強加了個限制,不得超過 255 byte。
另外,大家有沒有經(jīng)驗,在目錄下文件很多的時候,我們使用ls命令時會很慢。現(xiàn)在大家知道原因了吧,這時實際上操作系統(tǒng)在讀取當(dāng)前目錄的所有 block ,如果 block 比較多的話,可能得需要多次 IO 操作才能完成這個簡單的 ls 命令。我在自己的電腦某個目錄下創(chuàng)建了一 100W 個空文件,ls 命令 1 分鐘還沒出結(jié)果,被我 ctrl+c 掉了。在自己的項目中可不要這么干,雖然操作系統(tǒng)可以 cache 住你的目錄數(shù)據(jù),使你下次調(diào)用時會快很多,但我還是建議你單個目錄下文件數(shù)目不要過萬。否則你的程序在重啟后首次運行時可能會出現(xiàn)性能不佳的情況。
好了,回到開篇問題 7,你有答案了嗎?一個目錄下最多能建多少個文件,這個最多其實是受限于你目錄所在分區(qū)的 inode 數(shù)量,你有 100W 個 inode,你最多就可以新建 100W 個文件。但是,上面說了,單個目錄下文件數(shù)量最好不要過萬,否則會帶來系統(tǒng)性能的問題。
4、文件的block
再做個關(guān)于文件的實驗。我新建了個空目錄,并在其下新建了個文件,里面只寫了一個空格數(shù)據(jù),保存后 du 命令顯示如下:

這 8K 里有 4K 是目錄的,也就可以算出操作系統(tǒng)為只包含一個空格的文件分配了 4KB。其實文件的 block 比較簡單的了,不像目錄的 block 里會存很多文件系統(tǒng)的結(jié)構(gòu)體,文件的 block 里只會保存文件的數(shù)據(jù)。上面這個實驗表明,操作系統(tǒng)分配空間時是以 block 為最小單位。也就是說只要你的文件數(shù)據(jù)不為空,操作系統(tǒng)就至少會給你分配一個 block 來存儲,直到你超過了 4KB,操作系統(tǒng)再給你分配下一個 block,就是這樣。所以對于開篇問題 8,新建一個內(nèi)容大小為 1k 的文件,實際會占用 1個 block(一般為4k)和一個 inode(一般為256byte)。
其實文件系統(tǒng)在向磁盤發(fā)起 IO 請求的時候,也是以 block size 為單位的。哪怕你只向操作系統(tǒng)發(fā)起讀取文件的 2 Byte,但是操作系統(tǒng)會一次性給你讀取 4KB 回來。因此磁盤 IO 真的是很慢,而且我們只要訪問了這 2 Byte,確實很有可能接下來繼續(xù)訪問這 2byte 后面的內(nèi)容,這也就是程序局部性原理,所以操作系統(tǒng)索性一次性就多讀取些回來了。呵呵,這就是開篇問題9的答案。這就像我們?nèi)ス涑?,逛一次真的是很浪費時間,這可要比坑爹的磁盤 IO 也慢許多了。我們總不會逛了一圈超市就買了一個蘋果就回來了吧,我們肯定會多買些東西為家里以后的需求準(zhǔn)備著,反正買一堆東西比買一個蘋果也沒多花多少時間,何樂為不為呢,就是這個道理。
再說說開篇問題 10,我們攻城獅怎么樣設(shè)計你的文件能提高一些 IO 速度呢?那就是如果你知道你的要新建的文件大概會占用多大的空間的話,比如 1M。那么你新建文件時就順便和操作系統(tǒng)說一下,讓它幫你將文件的 size 預(yù)留下來。這樣實際上操作系統(tǒng)時會盡可能為你分配連續(xù)的 block,這樣你再讀取這個文件時,磁頭就省去很多尋道時間了,IO 速度就顯得快多了。
三、寫在后面的話
前面我們說的都是基于我自己的文件系統(tǒng),情形是一個 block size 是 4KB,一個 inode size 是 256byte,包括我虛擬機上的 inode 數(shù)量才只有 140 多萬個。這些值實際上不是固定的,你完全可以在格式化你的硬盤的時候設(shè)置成其它的值。設(shè)置的原則就是看你的硬盤的容量,以及你的用途。
如果你的文件都是大于 4KB,甚至是幾 M,幾 G 的文件,那么建議你的 block 還是盡可能的大一點吧,這樣 inode 里就能少記幾個地址。
如果你的文件大部分都是1K以下的,那么確實使用4K的block會造成一點點浪費,如果你的老板對成本要求異??量痰脑?,你可以適當(dāng)考慮把你的 block 設(shè)置得小一點。
另外,要關(guān)注你的文件系統(tǒng)的 inode。操作系統(tǒng)在查看目錄和文件占用的磁盤空間信息時把inode節(jié)點的占用給隱藏起來了,其用意在于為用戶提供一個白盒的環(huán)境,把數(shù)據(jù)占用的空間交給我們來認(rèn)知,而把 inode 信息隱藏起來為了降低我們理解操作系統(tǒng)的難度。而實際上,我們作為非普通用戶的開發(fā)人員應(yīng)該具備這個知情權(quán)。這個東東直接關(guān)系到你文件系統(tǒng)能創(chuàng)建文件數(shù)量。否則哪天等你發(fā)現(xiàn)線上機器磁盤還剩大把大把的空間,但就是 inode 使用光了,那時候就只有重新格式化或者遷移服務(wù)器了。這兩個操作想想都覺得苦逼啊,還是能避免就盡量避免吧。
思考題:我們大家有個經(jīng)驗就是目錄下小文件太多的情況下,往其它地方拷貝的話,速度會非常的慢,我們這時往往會把目錄壓縮一下再拷貝。現(xiàn)在你能說出這樣做為什么會快嗎?
